diff --git a/.bazelrc b/.bazelrc index 591c03d7675..5325dfd50d8 100644 --- a/.bazelrc +++ b/.bazelrc @@ -11,6 +11,10 @@ build --per_file_copt="third-party/webrtc/.*\.cpp$","@-std=c++17" build --per_file_copt="third-party/webrtc/.*\.cc$","@-std=c++17" build --per_file_copt="third-party/webrtc/.*\.mm$","@-std=c++17" build --per_file_copt="submodules/LottieMeshSwift/LottieMeshBinding/Sources/.*\.mm$","@-std=c++17" +build --per_file_copt="submodules/TelegramUI/Components/LottieCpp/Sources/.*\.mm$","@-std=c++17" +build --per_file_copt="submodules/TelegramUI/Components/LottieCpp/Sources/.*\.cpp$","@-std=c++17" +build --per_file_copt="Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/.*\.cpp$","@-std=c++17" +build --per_file_copt="Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/.*\.mm$","@-std=c++17" build --swiftcopt=-whole-module-optimization diff --git a/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift b/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift index 7b1ef495f2e..f46c443f2b3 100644 --- a/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift +++ b/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift @@ -34,7 +34,9 @@ public func shouldSubscribeToCopyContent(message: Message) -> Bool { } public func routeToNicegramPremiumForCopyContent() { - PremiumUITgHelper.routeToPremium() + PremiumUITgHelper.routeToPremium( + source: .copyProtectedContent + ) } // MARK: - Bypass setting (Secret Menu) diff --git a/Nicegram/NGData/Sources/NGSettings.swift b/Nicegram/NGData/Sources/NGSettings.swift index 38cebcb1cdd..75d9d9a6028 100644 --- a/Nicegram/NGData/Sources/NGSettings.swift +++ b/Nicegram/NGData/Sources/NGSettings.swift @@ -67,6 +67,9 @@ public struct NGSettings { @NGStorage(key: "showNicegramTab", defaultValue: true) public static var showNicegramTab: Bool + @NGStorage(key: "showNicegramButtonInChat", defaultValue: true) + public static var showNicegramButtonInChat: Bool + @NGStorage(key: "sendWithEnter", defaultValue: false) public static var sendWithEnter: Bool diff --git a/Nicegram/NGData/Sources/NGWeb.swift b/Nicegram/NGData/Sources/NGWeb.swift index 3a275c97e22..e05120cc9b6 100644 --- a/Nicegram/NGData/Sources/NGWeb.swift +++ b/Nicegram/NGData/Sources/NGWeb.swift @@ -94,7 +94,7 @@ extension String { public func requestApi(_ path: String, pathParams: [String] = [], completion: @escaping (_ apiResult: [String: Any]?) -> Void) { let startTime = CFAbsoluteTimeGetCurrent() ngLog("DECLARING REQUEST \(path)") - var urlString = NGENV.ng_api_url + path + "/" + var urlString = "\(NGENV.ng_api_url)/\(path)/" for param in pathParams { urlString = urlString + String(param) + "/" } @@ -118,7 +118,7 @@ public func requestApi(_ path: String, pathParams: [String] = [], completion: @e } public func getNGSettings(_ userId: Int64, completion: @escaping (_ sync: Bool, _ rreasons: [String], _ allowed: [Int64], _ restricted: [Int64], _ premiumStatus: Bool, _ betaPremiumStatus: Bool) -> Void) { - requestApi("settings", pathParams: [String(userId)], completion: { (apiResponse) -> Void in + requestApi("v7/unblock-feature/settings", pathParams: [String(userId)], completion: { (apiResponse) -> Void in var syncChats = VARNGAPISETTINGS.SYNC_CHATS var restricitionReasons = VARNGAPISETTINGS.RESTRICTION_REASONS var allowedChats = VARNGAPISETTINGS.ALLOWED diff --git a/Nicegram/NGEnv/Sources/NGEnv.swift b/Nicegram/NGEnv/Sources/NGEnv.swift index eda2d2515ee..e4f4a3d26e9 100644 --- a/Nicegram/NGEnv/Sources/NGEnv.swift +++ b/Nicegram/NGEnv/Sources/NGEnv.swift @@ -5,14 +5,11 @@ public struct NGEnvObj: Decodable { public let app_review_login_code: String public let app_review_login_phone: String public let premium_bundle: String + public let ng_api_key: String public let ng_api_url: String public let moby_key: String public let privacy_url: String public let terms_url: String - public let reg_date_url: String - public let reg_date_key: String - public let esim_api_url: String - public let esim_api_key: String public let referral_bot: String public let remote_config_cache_duration_seconds: Double public let tapjoy_api_key: String @@ -20,6 +17,15 @@ public struct NGEnvObj: Decodable { public let applovin_api_key: String public let applovin_ad_unit_id: String public let websocket_url: URL + + public let wallet: Wallet + public struct Wallet: Decodable { + public let keychainGroupIdentifier: String + public let walletConnectProjectId: String + public let web3AuthBackupQuestion: String + public let web3AuthClientId: String + public let web3AuthVerifier: String + } } func parseNGEnv() -> NGEnvObj { diff --git a/Nicegram/NGLab/Sources/RegDate.swift b/Nicegram/NGLab/Sources/RegDate.swift index 2a4257d0df6..7513e274747 100644 --- a/Nicegram/NGLab/Sources/RegDate.swift +++ b/Nicegram/NGLab/Sources/RegDate.swift @@ -11,9 +11,6 @@ import SwiftSignalKit import NGEnv import NGDeviceCheck -fileprivate let regDateUrlString = NGENV.reg_date_url -fileprivate let regDateKey = NGENV.reg_date_key - public enum RegDateError { case generic case badDeviceToken @@ -47,15 +44,12 @@ struct RegDate: Codable { public func requestRegDate(jsonData: Data) -> Signal { return Signal { subscriber in let completed = Atomic(value: false) - var request = URLRequest(url: URL(string: regDateUrlString)!) - + + var request = URLRequest(url: URL(string: "\(NGENV.ng_api_url)/v7/regdate")!) + request.httpMethod = "POST" - request.setValue(regDateKey, forHTTPHeaderField: "x-api-key") -// request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") -// request.setValue(deviceToken, forHTTPHeaderField: "Device-Token") -// request.setValue("\(requestByUserId)", forHTTPHeaderField: "User-Id") -// request.setValue("Bearer \(ngLabData[1])", forHTTPHeaderField: "Authorization") - + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + // insert json data to the request request.httpBody = jsonData as Data request.timeoutInterval = 10 @@ -68,18 +62,6 @@ public func requestRegDate(jsonData: Data) -> Signal { let decoder = JSONDecoder() let gitData = try decoder.decode(RegDate.self, from: data) subscriber.putNext(gitData.data.date) -// let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] -// if let result = String(data: data, encoding: .utf8) { -// if let timeInterval = TimeInterval(result) { -// let date = Date(timeIntervalSince1970: timeInterval) -// subscriber.putNext(date) -// subscriber.putCompletion() -// } else { -// subscriber.putError(.generic) -// } -// } else { -// subscriber.putError(.generic) -// } } catch { subscriber.putError(.generic) } diff --git a/Nicegram/NGOnboarding/BUILD b/Nicegram/NGOnboarding/BUILD deleted file mode 100644 index c78088bce4b..00000000000 --- a/Nicegram/NGOnboarding/BUILD +++ /dev/null @@ -1,19 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "NGOnboarding", - module_name = "NGOnboarding", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils", - "//Nicegram/NGData:NGData", - "//Nicegram/NGStrings:NGStrings", - "@swiftpkg_nicegram_assistant_ios//:FeatPremiumUI", - "@swiftpkg_nicegram_assistant_ios//:NGAiChat", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/Nicegram/NGOnboarding/Sources/OnboardingFlow.swift b/Nicegram/NGOnboarding/Sources/OnboardingFlow.swift deleted file mode 100644 index 19155853786..00000000000 --- a/Nicegram/NGOnboarding/Sources/OnboardingFlow.swift +++ /dev/null @@ -1,48 +0,0 @@ -import FeatPremiumUI -import Foundation -import UIKit -import NGAiChat -import NGData -import NGStrings - -public func onboardingController(onComplete: @escaping () -> Void) -> UIViewController { - let controller = OnboardingViewController( - items: onboardingPages(), - onComplete: { - if isPremium() { - onComplete() - } else if #available(iOS 13.0, *) { - PremiumUITgHelper.routeToPremium(onComplete: onComplete) - } else { - onComplete() - } - } - ) - - return controller -} - -private func onboardingPages() -> [OnboardingPageViewModel] { - let aiPageIndex = 6 - - var pages = Array(1...6) - - let isAiAvailable: Bool - if #available(iOS 13.0, *) { - let getAiAvailabilityUseCase = AiChatContainer.shared.getAiAvailabilityUseCase() - isAiAvailable = getAiAvailabilityUseCase() - } else { - isAiAvailable = false - } - if !isAiAvailable { - pages.removeAll { $0 == aiPageIndex } - } - - return pages.map { index in - OnboardingPageViewModel( - title: l("NicegramOnboarding.\(index).Title"), - description: l("NicegramOnboarding.\(index).Desc"), - videoURL: Bundle.main.url(forResource: "Nicegram_Onboarding-DS_v\(index)", withExtension: "mp4")! - ) - } -} diff --git a/Nicegram/NGOnboarding/Sources/OnboardingViewController.swift b/Nicegram/NGOnboarding/Sources/OnboardingViewController.swift deleted file mode 100644 index d555424d73c..00000000000 --- a/Nicegram/NGOnboarding/Sources/OnboardingViewController.swift +++ /dev/null @@ -1,284 +0,0 @@ -import UIKit -import UIKitRuntimeUtils -import SnapKit -import NGCoreUI -import NGStrings - -class OnboardingViewController: UIViewController { - - // MARK: - UI Elements - - private let pagesStack = UIStackView() - private let scrollView = UIScrollView() - private let pageControl = UIStackView() - private let nextButton = CustomButton() - - override var preferredStatusBarStyle: UIStatusBarStyle { - return .lightContent - } - - override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - return UIDevice.current.userInterfaceIdiom == .phone ? .portrait : .all - } - - // MARK: - Handlers - - private let onComplete: () -> Void - - // MARK: - Logic - - private let items: [OnboardingPageViewModel] - - // MARK: - Lifecycle - - init(items: [OnboardingPageViewModel], onComplete: @escaping () -> Void) { - self.items = items - self.onComplete = onComplete - - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func loadView() { - self.view = UIView() - setupUI() - } - - override func viewDidLoad() { - super.viewDidLoad() - - scrollView.delegate = self - - display(items: self.items) - display(buttonTitle: l("NicegramOnboarding.Continue")) - - nextButton.touchUpInside = { [weak self] in - self?.goToNextPage() - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - UIApplication.shared.internalSetStatusBarHidden(true, animation: animated ? .fade : .none) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - updateViewAccordingToCurrentScrollOffset() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - UIApplication.shared.internalSetStatusBarHidden(false, animation: animated ? .fade : .none) - } -} - -extension OnboardingViewController { - func display(items: [OnboardingPageViewModel]) { - pagesStack.removeAllArrangedSubviews() - pageControl.removeAllArrangedSubviews() - - for item in items { - let pageView = OnboardingPageView() - pageView.display(item) - - pagesStack.addArrangedSubview(pageView) - pageView.snp.makeConstraints { make in - make.width.equalTo(scrollView) - } - - let pageIndicator = UIView() - pageIndicator.layer.cornerRadius = 4 - pageIndicator.snp.makeConstraints { make in - make.width.equalTo(8) - } - pageControl.addArrangedSubview(pageIndicator) - } - } - - func display(buttonTitle: String) { - nextButton.display(title: buttonTitle, image: nil) - } -} - -extension OnboardingViewController: UIScrollViewDelegate { - func scrollViewDidScroll(_ scrollView: UIScrollView) { - updateViewAccordingToCurrentScrollOffset() - } -} - -// MARK: - Private Functions - -private extension OnboardingViewController { - func goToNextPage() { - let scrollViewWidth = scrollView.frame.width - guard !scrollViewWidth.isZero else { return } - - let currentPage = (scrollView.contentOffset.x + (0.5 * scrollViewWidth)) / scrollViewWidth - - let nextPage = Int(currentPage) + 1 - guard items.indices.contains(nextPage) else { - onComplete() - return - } - - let visibleRect = CGRect( - origin: CGPoint( - x: scrollViewWidth * CGFloat(nextPage), - y: 0 - ), - size: scrollView.frame.size - ) - scrollView.scrollRectToVisible(visibleRect, animated: true) - } - - func updateViewAccordingToCurrentScrollOffset() { - let offset = scrollView.contentOffset - let scrollViewSize = scrollView.frame.size - guard !scrollViewSize.width.isZero else { return } - - let pageViews = (pagesStack.arrangedSubviews as? [OnboardingPageView]) ?? [] - for (index, pageView) in pageViews.enumerated() { - let pageFrame = pageView.frame - let visibleRect = CGRect(origin: offset, size: scrollViewSize) - let intersection = pageFrame.intersection(visibleRect) - - let fractionWidth = intersection.width / scrollViewSize.width - - let pageIndicatorColor = linearInterpolatedColor(from: Constants.inactivePageIndicatorColor, to: Constants.activePageIndicatorColor, fraction: fractionWidth) - let pageIndicatorWidth = Constants.inactivePageIndicatorWidth + (Constants.activePageIndicatorWidth - Constants.inactivePageIndicatorWidth) * fractionWidth - - let pageIndicator = pageControl.arrangedSubviews[index] - pageIndicator.backgroundColor = pageIndicatorColor - pageIndicator.snp.updateConstraints { make in - make.width.equalTo(pageIndicatorWidth) - } - - pageControl.layoutIfNeeded() - - var pageView = pageView - if UIView.userInterfaceLayoutDirection(for: scrollView.semanticContentAttribute) == .rightToLeft { - pageView = pageViews[pageViews.count - index - 1] - } - if fractionWidth >= 0.5 { - pageView.playVideo() - } else { - pageView.pauseVideo() - } - } - } -} - -// MARK: - Constants - -private extension OnboardingViewController { - struct Constants { - static let inactivePageIndicatorColor = UIColor.hex("333334") - static let activePageIndicatorColor = UIColor.white - - static let inactivePageIndicatorWidth = CGFloat(8) - static let activePageIndicatorWidth = CGFloat(24) - } -} - -// MARK: - Setup UI - -private extension OnboardingViewController { - func setupUI() { - view.backgroundColor = .black - - scrollView.showsHorizontalScrollIndicator = false - scrollView.isPagingEnabled = true - - pageControl.spacing = 8 - - nextButton.applyMainActionStyle() - - for view in [scrollView, pagesStack, pageControl] { - if UIView.userInterfaceLayoutDirection(for: view.semanticContentAttribute) == .rightToLeft { - view.transform = CGAffineTransform(rotationAngle: .pi) - } - } - - let scrollContentView = UIView() - - scrollContentView.addSubview(pagesStack) - pagesStack.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - - scrollView.addSubview(scrollContentView) - scrollContentView.snp.makeConstraints { make in - make.edges.equalToSuperview() - make.height.equalToSuperview() - make.width.equalToSuperview().priority(1) - } - - view.addSubview(scrollView) - view.addSubview(pageControl) - view.addSubview(nextButton) - - nextButton.snp.makeConstraints { make in - make.leading.trailing.equalToSuperview().inset(16) - make.height.equalTo(54) - make.bottom.equalTo(view.safeAreaLayoutGuide).inset(50) - } - - pageControl.snp.makeConstraints { make in - make.centerX.equalToSuperview() - make.bottom.equalTo(nextButton.snp.top).offset(-32) - make.height.equalTo(8) - } - - scrollView.snp.makeConstraints { make in - make.top.leading.trailing.equalToSuperview() - make.bottom.equalTo(pageControl.snp.top).offset(-32) - } - } -} - -private extension CustomButton { - func applyMainActionStyle() { - foregroundColor = .white - backgroundColor = .legacyBlue - layer.cornerRadius = 6 - configureTitleLabel { label in - label.font = .systemFont(ofSize: 16, weight: .semibold) - } - } -} - -private func linearInterpolatedColor(from: UIColor, to: UIColor, fraction: CGFloat) -> UIColor { - let f = min(max(0, fraction), 1) - - guard let c1 = from.getComponents(), - let c2 = to.getComponents() else { - return from - } - - let r = c1.r + (c2.r - c1.r) * f - let g = c1.g + (c2.g - c1.g) * f - let b = c1.b + (c2.b - c1.b) * f - let a = c1.a + (c2.a - c1.a) * f - - return UIColor(red: r, green: g, blue: b, alpha: a) -} - -private extension UIColor { - func getComponents() -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat)? { - let c = cgColor.components ?? [] - if c.count == 2 { - return (r: c[0], g: c[0], b: c[0], a: c[1]) - } else if c.count == 4 { - return (r: c[0], g: c[1], b: c[2], a: c[3]) - } else { - return nil - } - } -} diff --git a/Nicegram/NGOnboarding/Sources/Views/OnboardingPageView.swift b/Nicegram/NGOnboarding/Sources/Views/OnboardingPageView.swift deleted file mode 100644 index 863ff3ec52f..00000000000 --- a/Nicegram/NGOnboarding/Sources/Views/OnboardingPageView.swift +++ /dev/null @@ -1,108 +0,0 @@ -import AVKit -import NGCoreUI -import SnapKit -import UIKit - -struct OnboardingPageViewModel { - let title: String - let description: String - let videoURL: URL -} - -class OnboardingPageView: UIView { - - // MARK: - UI Elements - - private let videoView = UIView() - private let titleLabel = UILabel() - private let descriptionLabel = UILabel() - private let playerLayer = AVPlayerLayer() - - // MARK: - Logic - - private var playerLooperHolder: AnyObject? - - // MARK: - Lifecycle - - override init(frame: CGRect) { - super.init(frame: frame) - - titleLabel.font = .systemFont(ofSize: 24, weight: .bold) - titleLabel.textColor = .white - titleLabel.numberOfLines = 0 - titleLabel.textAlignment = .center - - descriptionLabel.font = .systemFont(ofSize: 14, weight: .regular) - descriptionLabel.textColor = .subtitle4 - descriptionLabel.numberOfLines = 0 - descriptionLabel.textAlignment = .center - - playerLayer.videoGravity = .resizeAspectFill - - let descriptionContainer = UIView() - descriptionContainer.addSubview(descriptionLabel) - descriptionLabel.snp.makeConstraints { make in - make.top.equalToSuperview() - make.leading.trailing.equalToSuperview().inset(16) - make.bottom.lessThanOrEqualToSuperview() - } - - videoView.layer.addSublayer(playerLayer) - - addSubview(videoView) - addSubview(titleLabel) - addSubview(descriptionContainer) - - descriptionContainer.snp.makeConstraints { make in - make.bottom.equalToSuperview() - make.leading.trailing.equalToSuperview() - make.height.greaterThanOrEqualTo(80) - make.height.equalTo(80).priority(1) - } - - titleLabel.snp.makeConstraints { make in - make.bottom.equalTo(descriptionLabel.snp.top).offset(-12) - make.leading.trailing.equalToSuperview().inset(60) - } - - videoView.snp.makeConstraints { make in - make.leading.trailing.top.equalToSuperview() - make.bottom.equalTo(titleLabel.snp.top) - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - super.layoutSubviews() - - playerLayer.frame = videoView.bounds - } - - // MARK: - Public Functions - - func display(_ item: OnboardingPageViewModel) { - titleLabel.text = item.title - descriptionLabel.text = item.description - - let asset = AVAsset(url: item.videoURL) - let playerItem = AVPlayerItem(asset: asset) - let player = AVQueuePlayer(playerItem: playerItem) - if #available(iOS 10.0, *) { - let looper = AVPlayerLooper(player: player, templateItem: playerItem) - self.playerLooperHolder = looper - } - - self.playerLayer.player = player - } - - func playVideo() { - self.playerLayer.player?.play() - } - - func pauseVideo() { - self.playerLayer.player?.pause() - } -} diff --git a/Nicegram/NGUI/Sources/AiChatSettingsController.swift b/Nicegram/NGUI/Sources/AiChatSettingsController.swift index 1872367f20f..4c287a93bd8 100644 --- a/Nicegram/NGUI/Sources/AiChatSettingsController.swift +++ b/Nicegram/NGUI/Sources/AiChatSettingsController.swift @@ -2,7 +2,6 @@ import AccountContext import Display import ItemListUI import NGAiChatUI -import NGRepoUser import SwiftSignalKit private final class AiChatSettingsControllerArguments {} @@ -13,22 +12,19 @@ private enum AiChatSettingsControllerSection: Int32 { @available(iOS 13.0, *) private enum AiChatSettingsControllerEntry: ItemListNodeEntry { - case showInChat(Bool) case clearHistory var section: ItemListSectionId { switch self { - case .showInChat, .clearHistory: + case .clearHistory: return AiChatSettingsControllerSection.main.rawValue } } var stableId: Int32 { switch self { - case .showInChat: - return 1 case .clearHistory: - return 2 + return 1 } } @@ -38,10 +34,6 @@ private enum AiChatSettingsControllerEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { switch self { - case let .showInChat(value): - return ItemListSwitchItem(presentationData: presentationData, title: AiChatUITgHelper.settingsShowInChat, value: value, sectionId: self.section, style: .blocks) { value in - RepoUserTgHelper.resolvePreferencesRepository().displayAiBotInChat = value - } case .clearHistory: return ItemListActionItem(presentationData: presentationData, title: AiChatUITgHelper.settingsClearHistory, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { Task { await AiChatUITgHelper.presentConfirmClearHistory() } @@ -52,11 +44,8 @@ private enum AiChatSettingsControllerEntry: ItemListNodeEntry { @available(iOS 13.0, *) private func controllerEntries() -> [AiChatSettingsControllerEntry] { - let preferencesRepository = RepoUserTgHelper.resolvePreferencesRepository() - var entries: [AiChatSettingsControllerEntry] = [] - entries.append(.showInChat(preferencesRepository.displayAiBotInChat)) entries.append(.clearHistory) return entries diff --git a/Nicegram/NGUI/Sources/NicegramSettingsController.swift b/Nicegram/NGUI/Sources/NicegramSettingsController.swift index 0f32b58e427..dda92f6f046 100644 --- a/Nicegram/NGUI/Sources/NicegramSettingsController.swift +++ b/Nicegram/NGUI/Sources/NicegramSettingsController.swift @@ -77,6 +77,7 @@ private enum NicegramSettingsControllerSection: Int32 { private enum EasyToggleType { + case showNicegramButtonInChat case sendWithEnter case showProfileId case showRegDate @@ -517,6 +518,8 @@ private enum NicegramSettingsControllerEntry: ItemListNodeEntry { return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, sectionId: section, style: .blocks, updated: { value in ngLog("[easyToggle] \(index) \(toggleType) invoked with \(value)", LOGTAG) switch (toggleType) { + case .showNicegramButtonInChat: + NGSettings.showNicegramButtonInChat = value case .sendWithEnter: NGSettings.sendWithEnter = value case .showProfileId: @@ -702,6 +705,9 @@ private func nicegramSettingsControllerEntries(presentationData: PresentationDat var toggleIndex: Int32 = 1 // MARK: Other Toggles (Easy) + entries.append(.easyToggle(toggleIndex, .showNicegramButtonInChat, l("ShowNicegramButtonInChat"), NGSettings.showNicegramButtonInChat)) + toggleIndex += 1 + entries.append(.easyToggle(toggleIndex, .sendWithEnter, l("SendWithKb"), NGSettings.sendWithEnter)) toggleIndex += 1 diff --git a/Nicegram/NGUtils/BUILD b/Nicegram/NGUtils/BUILD index e0953c60ed4..5e44b5faf3e 100644 --- a/Nicegram/NGUtils/BUILD +++ b/Nicegram/NGUtils/BUILD @@ -10,6 +10,7 @@ swift_library( "//submodules/AccountContext:AccountContext", "@FirebaseSDK//:FirebaseAnalytics", "@swiftpkg_nicegram_assistant_ios//:NGAnalytics", + "@swiftpkg_nicegram_wallet_ios//:NicegramWallet", ], visibility = [ "//visibility:public", diff --git a/Nicegram/NGUtils/Sources/WalletUtils.swift b/Nicegram/NGUtils/Sources/WalletUtils.swift new file mode 100644 index 00000000000..205cad25f32 --- /dev/null +++ b/Nicegram/NGUtils/Sources/WalletUtils.swift @@ -0,0 +1,62 @@ +import AccountContext +import NicegramWallet +import Postbox +import TelegramCore + +public struct WalletTgUtils {} + +public extension WalletTgUtils { + static func contactIdToPeerId(_ id: WalletContactId) -> PeerId? { + guard let int64Id = Int64(id.id) else { + return nil + } + return PeerId( + namespace: ._internalFromInt32Value(id.namespace), + id: ._internalFromInt64Value(int64Id) + ) + } + + static func peerById( + _ id: PeerId, + context: AccountContext + ) async -> EnginePeer? { + try? await context.engine.data + .get( + TelegramEngine.EngineData.Item.Peer.Peer( + id: id + ) + ) + .awaitForFirstValue() + } + + static func peerToWalletContact( + peer: EnginePeer + ) -> WalletContact { + let username: String + if let addressName = peer.addressName, !addressName.isEmpty { + username = "@\(addressName)" + } else { + username = "" + } + + return WalletContact( + id: WalletContactId( + namespace: peer.id.namespace._internalGetInt32Value(), + id: String(peer.id.id._internalGetInt64Value()) + ), + name: peer.compactDisplayTitle, + username: username + ) + } + + static func peerToWalletContact( + id: PeerId, + context: AccountContext + ) async -> WalletContact? { + if let peer = await peerById(id, context: context) { + peerToWalletContact(peer: peer) + } else { + nil + } + } +} diff --git a/Package.resolved b/Package.resolved index 19d40770947..9e0a14c2782 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,21 +1,84 @@ { "pins" : [ + { + "identity" : "anycodable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Flight-School/AnyCodable", + "state" : { + "revision" : "862808b2070cd908cb04f9aafe7de83d35f81b05", + "version" : "0.6.7" + } + }, + { + "identity" : "bigdecimal", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Zollerboy1/BigDecimal.git", + "state" : { + "revision" : "04d17040e4615fbfda3a882b9881f6841f4bf557", + "version" : "1.0.2" + } + }, + { + "identity" : "bigint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/attaswift/BigInt", + "state" : { + "revision" : "0ed110f7555c34ff468e72e1686e59721f2b0da6", + "version" : "5.3.0" + } + }, + { + "identity" : "core-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/denis15yo/core-swift.git", + "state" : { + "branch" : "release/1.0.0", + "revision" : "20b7275f60ad80634f056905d7f18292294cd510" + } + }, + { + "identity" : "cryptoswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", + "state" : { + "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", + "version" : "1.8.2" + } + }, + { + "identity" : "curvelib.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tkey/curvelib.swift", + "state" : { + "revision" : "7dad3bf1793de263f83406c08c18c9316abf082f", + "version" : "0.1.2" + } + }, { "identity" : "factory", "kind" : "remoteSourceControl", - "location" : "https://github.com/hmlongco/Factory", + "location" : "https://github.com/hmlongco/Factory.git", "state" : { "revision" : "587995f7d5cc667951d635fbf6b4252324ba0439", "version" : "2.3.2" } }, + { + "identity" : "fetch-node-details-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/fetch-node-details-swift.git", + "state" : { + "revision" : "bf2f0759da5c5c80765773b08c2756045edf608f", + "version" : "5.2.0" + } + }, { "identity" : "floatingpanel", "kind" : "remoteSourceControl", "location" : "https://github.com/scenee/FloatingPanel", "state" : { - "revision" : "8f2be39bf49b4d5e22bbf7bdde69d5b76d0ecd2a", - "version" : "2.8.2" + "revision" : "22d46c526084724a718b8c39ab77f12452712cc7", + "version" : "2.8.3" } }, { @@ -27,6 +90,15 @@ "revision" : "afc958017ee4feefd3c61c8e2cddf81d079d2e39" } }, + { + "identity" : "keychain-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/evgenyneu/keychain-swift.git", + "state" : { + "revision" : "d108a1fa6189e661f91560548ef48651ed8d93b9", + "version" : "20.0.0" + } + }, { "identity" : "lnextensionexecutor", "kind" : "remoteSourceControl", @@ -51,7 +123,25 @@ "location" : "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", "state" : { "branch" : "develop", - "revision" : "667bceccb7102dff96ba018b22c5fa1a6d30c9c6" + "revision" : "0985fd5dfae1676121c54c31fe2817059d5bf784" + } + }, + { + "identity" : "nicegram-wallet-ios", + "kind" : "remoteSourceControl", + "location" : "git@bitbucket.org:mobyrix/nicegram-wallet-ios.git", + "state" : { + "branch" : "develop", + "revision" : "241a210ee1b5cb9cb95d9245a4ed384973ee2ef2" + } + }, + { + "identity" : "qrcode", + "kind" : "remoteSourceControl", + "location" : "https://github.com/WalletConnect/QRCode", + "state" : { + "revision" : "263f280d2c8144adfb0b6676109846cfc8dd552b", + "version" : "14.3.1" } }, { @@ -68,8 +158,26 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SDWebImage/SDWebImage.git", "state" : { - "revision" : "f6afa0132961d593f07970d84e2d8b588c29ea04", - "version" : "5.19.1" + "revision" : "b8523c1642f3c142b06dd98443ea7c48343a4dfd", + "version" : "5.19.3" + } + }, + { + "identity" : "session-manager-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Web3Auth/session-manager-swift.git", + "state" : { + "revision" : "c89d9205a1ce38cd6c6374b906a9039d9cc03f05", + "version" : "3.1.1" + } + }, + { + "identity" : "single-factor-auth-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Web3Auth/single-factor-auth-swift.git", + "state" : { + "revision" : "8baa2b8cf55b0a38cb98c412bea1c6597adb78ba", + "version" : "4.0.0" } }, { @@ -81,6 +189,15 @@ "version" : "5.7.1" } }, + { + "identity" : "starscream", + "kind" : "remoteSourceControl", + "location" : "https://github.com/daltoniam/Starscream.git", + "state" : { + "revision" : "c6bfd1af48efcc9a9ad203665db12375ba6b145a", + "version" : "4.0.8" + } + }, { "identity" : "subscriptionanalytics-ios", "kind" : "remoteSourceControl", @@ -95,8 +212,71 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "46989693916f56d1186bd59ac15124caef896560", - "version" : "1.3.1" + "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "ee97538f5b81ae89698fd95938896dec5217b148", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "1ddbea1ee34354a6a2532c60f98501c35ae8edfa", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-openapi-runtime", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-openapi-runtime", + "state" : { + "revision" : "a51b3bd6f2151e9a6f792ca6937a7242c4758768", + "version" : "0.3.6" + } + }, + { + "identity" : "swift-openapi-urlsession", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-openapi-urlsession.git", + "state" : { + "revision" : "9229842c63e9fc3bbd32c661d8274b4d9d8715f1", + "version" : "0.3.1" + } + }, + { + "identity" : "swift-qrcode-generator", + "kind" : "remoteSourceControl", + "location" : "https://github.com/dagronf/swift-qrcode-generator", + "state" : { + "revision" : "5ca09b6a2ad190f94aa3d6ddef45b187f8c0343b", + "version" : "1.0.3" + } + }, + { + "identity" : "swiftimagereadwrite", + "kind" : "remoteSourceControl", + "location" : "https://github.com/dagronf/SwiftImageReadWrite", + "state" : { + "revision" : "5596407d1cf61b953b8e658fa8636a471df3c509", + "version" : "1.1.6" } }, { @@ -108,6 +288,69 @@ "version" : "0.16.4" } }, + { + "identity" : "tkey-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tkey/tkey-ios.git", + "state" : { + "revision" : "c107450f0675351a9a1eaaefe60bcfa285ff1f9e", + "version" : "0.2.1" + } + }, + { + "identity" : "ton-api-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tonkeeper/ton-api-swift", + "state" : { + "revision" : "1988939fe0ce6db6bc587cfe7c9d15dc3bca1d69", + "version" : "0.1.5" + } + }, + { + "identity" : "ton-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/denis15yo/ton-swift.git", + "state" : { + "branch" : "main", + "revision" : "e4c3def222afc125f7ee83c1569004e31f0cd05c" + } + }, + { + "identity" : "torus-utils-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/torus-utils-swift.git", + "state" : { + "revision" : "4c17ef5166c162455d0a37115c033eeff8cb282d", + "version" : "8.0.1" + } + }, + { + "identity" : "tweetnacl-swiftwrap", + "kind" : "remoteSourceControl", + "location" : "https://github.com/bitmark-inc/tweetnacl-swiftwrap", + "state" : { + "revision" : "f8fd111642bf2336b11ef9ea828510693106e954", + "version" : "1.1.0" + } + }, + { + "identity" : "wallet-core", + "kind" : "remoteSourceControl", + "location" : "https://github.com/trustwallet/wallet-core.git", + "state" : { + "revision" : "94116a24445c2052edbc7203baf68296c68ce8f4", + "version" : "4.0.46" + } + }, + { + "identity" : "walletconnectswiftv2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/denis15yo/WalletConnectSwiftV2.git", + "state" : { + "branch" : "develop", + "revision" : "1eacd732e321c9511859d7e73303d61d82af4d46" + } + }, { "identity" : "xcodeedit", "kind" : "remoteSourceControl", diff --git a/Telegram/BUILD b/Telegram/BUILD index e90ab68129a..8d79fe2d06f 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -141,9 +141,11 @@ genrule( ], ) -minimum_os_version = "12.0" +minimum_os_version = "14.0" minimum_watchos_version="9.0" +notificationServiceExtensionVersion = "v1" + empty_languages = [ "ar", "be", @@ -455,6 +457,9 @@ plist_fragment( ng ngcn ncg + tc + nicegram-tc + wc {google_client_scheme} @@ -594,6 +599,17 @@ store_signin_fragment = """ """ signin_fragment = store_signin_fragment if telegram_bundle_id in store_bundle_ids else "" +# MARK: Nicegram, keychain_access_groups_fragment +keychain_access_groups_fragment = """ + keychain-access-groups + + {telegram_team_id}.{telegram_bundle_id} + +""".format( + telegram_team_id=telegram_team_id, + telegram_bundle_id=telegram_bundle_id +) + plist_fragment( name = "TelegramEntitlements", extension = "entitlements", @@ -608,6 +624,8 @@ plist_fragment( carplay_fragment, communication_notifications_fragment, signin_fragment, + # MARK: Nicegram, keychain_access_groups_fragment + keychain_access_groups_fragment ]) ) @@ -1254,7 +1272,7 @@ swift_library( "Share/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramUI", @@ -1341,7 +1359,7 @@ swift_library( "NotificationContent/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramUI:TelegramUI", @@ -1436,7 +1454,7 @@ swift_library( "WidgetKitWidget/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":WidgetAssets", @@ -1571,7 +1589,7 @@ swift_library( "SiriIntents/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = glob([ "SiriIntents/*.lproj/Intents.intentdefinition" @@ -1674,7 +1692,7 @@ swift_library( "BroadcastUpload/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramUI:TelegramUI", @@ -1762,31 +1780,12 @@ plist_fragment( """.format( - telegram_bundle_id = telegram_bundle_id, + telegram_bundle_id = telegram_bundle_id ) ) -'''genrule( - name = "SetMinOsVersionNotificationServiceExtension", - cmd_bash = -""" - name=NotificationServiceExtension.appex - cat $(location PatchMinOSVersion.source.sh) | sed -e "s/<<>>/10\\.0/g" | sed -e "s/<<>>/$$name/g" > $(location SetMinOsVersionNotificationServiceExtension.sh) -""", - srcs = [ - "PatchMinOSVersion.source.sh", - ], - outs = [ - "SetMinOsVersionNotificationServiceExtension.sh", - ], - executable = True, - visibility = [ - "//visibility:public", - ] -)''' - ios_extension( - name = "NotificationServiceExtension", + name = "NotificationServiceExtension" + notificationServiceExtensionVersion, bundle_id = "{telegram_bundle_id}.NotificationService".format( telegram_bundle_id = telegram_bundle_id, ), @@ -1802,7 +1801,6 @@ ios_extension( ":AppNameInfoPlist", ], minimum_os_version = minimum_os_version, # maintain the same minimum OS version across extensions - #ipa_post_processor = ":SetMinOsVersionNotificationServiceExtension", provisioning_profile = select({ ":disableProvisioningProfilesSetting": None, "//conditions:default": "@build_configuration//provisioning:NotificationService.mobileprovision", @@ -2439,7 +2437,7 @@ ios_application( "//conditions:default": [ ":ShareExtension", ":NotificationContentExtension", - ":NotificationServiceExtension", + ":NotificationServiceExtension" + notificationServiceExtensionVersion, ":IntentsExtension", ":WidgetExtension", ":BroadcastUploadExtension", @@ -2536,7 +2534,7 @@ swift_library( "Tests/TelegramCoreBuildTest/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":WidgetAssets", diff --git a/Telegram/NotificationContent/NotificationViewController.swift b/Telegram/NotificationContent/NotificationViewController.swift index 935bdaf770c..14b959da1ca 100644 --- a/Telegram/NotificationContent/NotificationViewController.swift +++ b/Telegram/NotificationContent/NotificationViewController.swift @@ -38,7 +38,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil), useBetaFeatures: !buildConfig.isAppStoreBuild), setPreferredContentSize: { [weak self] size in + self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, tokenType: nil, tokenEnvironment: nil, signatureDict: nil), useBetaFeatures: !buildConfig.isAppStoreBuild), setPreferredContentSize: { [weak self] size in self?.preferredContentSize = size }) } diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index e8f1ec98414..1673172196c 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -745,7 +745,7 @@ private final class NotificationServiceHandler { Logger.shared.logToConsole = loggingSettings.logToConsole Logger.shared.redactSensitiveData = loggingSettings.redactSensitiveData - let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false) + let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, tokenType: nil, tokenEnvironment: nil, signatureDict: nil)), externalRequestVerificationStream: .never(), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false) let isLockedMessage: String? if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { diff --git a/Telegram/Share/ShareRootController.swift b/Telegram/Share/ShareRootController.swift index 0808a033f40..f030a358c40 100644 --- a/Telegram/Share/ShareRootController.swift +++ b/Telegram/Share/ShareRootController.swift @@ -49,7 +49,7 @@ class ShareRootController: UIViewController { let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil), useBetaFeatures: !buildConfig.isAppStoreBuild, makeTempContext: { accountManager, appLockContext, applicationBindings, InitialPresentationDataAndSettings, networkArguments in + self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, tokenType: nil, tokenEnvironment: nil, signatureDict: nil), useBetaFeatures: !buildConfig.isAppStoreBuild, makeTempContext: { accountManager, appLockContext, applicationBindings, InitialPresentationDataAndSettings, networkArguments in return makeTempContext( sharedContainerPath: appGroupUrl.path, rootPath: rootPath, diff --git a/Telegram/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift index 174004ccefa..9a76a29e032 100644 --- a/Telegram/SiriIntents/IntentHandler.swift +++ b/Telegram/SiriIntents/IntentHandler.swift @@ -181,7 +181,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo if let accountCache = accountCache { account = .single(accountCache) } else { - account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters) + account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, tokenType: nil, tokenEnvironment: nil, signatureDict: nil)), externalRequestVerificationStream: .never(), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters) |> mapToSignal { account -> Signal in if let account = account { switch account { diff --git a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v1.mp4 b/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v1.mp4 deleted file mode 100644 index bba9a5f0304..00000000000 Binary files a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v1.mp4 and /dev/null differ diff --git a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v2.mp4 b/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v2.mp4 deleted file mode 100644 index 074ad6fec6e..00000000000 Binary files a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v2.mp4 and /dev/null differ diff --git a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v3.mp4 b/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v3.mp4 deleted file mode 100644 index a309c9ad2a5..00000000000 Binary files a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v3.mp4 and /dev/null differ diff --git a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v4.mp4 b/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v4.mp4 deleted file mode 100644 index c693143b931..00000000000 Binary files a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v4.mp4 and /dev/null differ diff --git a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v5.mp4 b/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v5.mp4 deleted file mode 100644 index 044907157de..00000000000 Binary files a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v5.mp4 and /dev/null differ diff --git a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v6.mp4 b/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v6.mp4 deleted file mode 100644 index 5d20101af3c..00000000000 Binary files a/Telegram/Telegram-iOS/Resources/Nicegram/Onboarding/Nicegram_Onboarding-DS_v6.mp4 and /dev/null differ diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 8499e21f294..cf39a2058ce 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -5630,6 +5630,7 @@ Sorry for the inconvenience."; "Conversation.ContextMenuOpenProfile" = "Open Profile"; "Conversation.ContextMenuSendMessage" = "Send Message"; "Conversation.ContextMenuMention" = "Mention"; +"Conversation.ContextMenuSearchMessages" = "Search Messages"; "Conversation.ContextMenuOpenChannelProfile" = "Open Profile"; "Conversation.ContextMenuOpenChannel" = "Open Channel"; @@ -7798,6 +7799,7 @@ Sorry for the inconvenience."; "Premium.Purchase.ErrorNetwork" = "Please check your internet connection and try again."; "Premium.Purchase.ErrorNotAllowed" = "The device is not not allowed to make the payment."; "Premium.Purchase.ErrorCantMakePayments" = "In-app purchases are not allowed on this device."; +"Premium.Purchase.ErrorTryLater" = "An error occurred. Please try again."; "Premium.Restore.Success" = "Done"; "Premium.Restore.ErrorUnknown" = "An error occurred. Please try again."; @@ -12047,6 +12049,7 @@ Sorry for the inconvenience."; "MediaEditor.CreateNewPack" = "New Sticker Set"; "MediaEditor.ReplaceSticker" = "Replace Sticker"; "MediaEditor.AddToStickerPack" = "Add to Sticker Set"; +"MediaEditor.SetAsIntroSticker" = "Set as Greeting Sticker"; "MediaEditor.NewStickerPack.Title" = "New Sticker Set"; "MediaEditor.NewStickerPack.Text" = "Choose a name for your sticker set."; @@ -12158,6 +12161,7 @@ Sorry for the inconvenience."; "Chat.AdminActionSheet.BanFooterMultiple" = "Fully ban users"; "Chat.AdminActionSheet.RestrictFooterMultiple" = "Partially restrict users"; "Chat.AdminActionSheet.PermissionsSectionHeader" = "WHAT CAN THIS USER DO?"; +"Chat.AdminActionSheet.PermissionsSectionHeaderMultiple" = "WHAT CAN THESE USERS DO?"; "Chat.AdminActionSheet.ActionButton" = "Proceed"; "Chat.AdminAction.ToastMessagesDeletedTitleSingle" = "Message Deleted"; @@ -12178,7 +12182,135 @@ Sorry for the inconvenience."; "Chat.NavigationNoTopics" = "You have no unread topics"; -"Chat.NextSuggestedChannelSwipeProgress" = "Swipe up to go to the next channel"; +"Chat.NextSuggestedChannelSwipeProgress" = "Pull up to go to the next channel"; "Chat.NextSuggestedChannelSwipeAction" = "Release to go to the next channel"; -"Chat.NextUnreadTopicSwipeProgress" = "Swipe up to go to the next topic"; +"Chat.NextUnreadTopicSwipeProgress" = "Pull up to go to the next topic"; "Chat.NextUnreadTopicSwipeAction" = "Release to go to the next topic"; + +"EmojiPacksSettings.ArchivedPacks" = "Archived Stickers"; +"EmojiPacksSettings.ArchivedPacks.Info" = "You can have up to 200 sticker sets installed.\nUnused stickers are archived when you add more."; + +"EmojiPacks.UnarchiveEmojiPacksConfirmation_1" = "Unarchive %@ Pack"; +"EmojiPacks.UnarchiveEmojiPacksConfirmation_any" = "Unarchive %@ Packs"; + +"HashtagSearch.ThisChat" = "This Chat"; +"HashtagSearch.MyMessages" = "My Messages"; +"HashtagSearch.PublicPosts" = "Public Posts"; +"HashtagSearch.SearchPlaceholder" = "Hashtag search"; + +"HashtagSearch.NoRecentQueries" = "Enter a hashtag to find messages\ncontaining it."; +"HashtagSearch.ClearRecent" = "Clear History"; + +"HashtagSearch.NoResults" = "No Results"; +"HashtagSearch.NoResultsQueryDescription" = "There were no results for %@.\nTry another hashtag."; + +"Chat.Context.Phone.AddToContacts" = "Add to Contacts"; +"Chat.Context.Phone.CreateNewContact" = "Create New Contact"; +"Chat.Context.Phone.AddToExistingContact" = "Add to Existing Contact"; +"Chat.Context.Phone.SendMessage" = "Send Message"; +"Chat.Context.Phone.TelegramVoiceCall" = "Telegram Voice Call"; +"Chat.Context.Phone.TelegramVideoCall" = "Telegram Video Call"; +"Chat.Context.Phone.InviteToTelegram" = "Invite to Telegram"; +"Chat.Context.Phone.CallViaCarrier" = "Call via Carrier"; +"Chat.Context.Phone.CopyNumber" = "Copy Number"; +"Chat.Context.Phone.NotOnTelegram" = "This number is not on Telegram."; +"Chat.Context.Phone.ViewProfile" = "View Profile"; + +"Message.FactCheck" = "Fact Check"; +"Message.FactCheck.WhatIsThis" = "what's this?"; + +"Conversation.FactCheck.InnerDescription" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country (%@) responsible for combating misinformation."; +"Conversation.FactCheck.Description" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country (%@) responsible for combating misinformation."; + +"FactCheck.Title" = "Fact Check"; +"FactCheck.Placeholder" = "Add Fact Check"; +"FactCheck.Remove" = "Remove"; + +"Conversation.ContextMenuAddFactCheck" = "Add Fact Check"; +"Conversation.ContextMenuEditFactCheck" = "Edit Fact Check"; + +"Stars.Intro.Title" = "Telegram Stars"; +"Stars.Intro.Description" = "Buy Stars to unlock content and services in miniapps on Telegram."; +"Stars.Intro.Balance" = "Balance"; +"Stars.Intro.YourBalance" = "your balance"; +"Stars.Intro.Buy" = "Buy More Stars"; +"Stars.Intro.AllTransactions" = "All Transactions"; +"Stars.Intro.Incoming" = "Incoming"; +"Stars.Intro.Outgoing" = "Outgoing"; + +"Stars.Intro.Transaction.AppleTopUp.Title" = "Stars Top-Up"; +"Stars.Intro.Transaction.AppleTopUp.Subtitle" = "via App Store"; +"Stars.Intro.Transaction.GoogleTopUp.Title" = "Stars Top-Up"; +"Stars.Intro.Transaction.GoogleTopUp.Subtitle" = "via Play Market"; +"Stars.Intro.Transaction.PremiumBotTopUp.Title" = "Stars Top-Up"; +"Stars.Intro.Transaction.PremiumBotTopUp.Subtitle" = "via Premium Bot"; +"Stars.Intro.Transaction.FragmentTopUp.Title" = "Stars Top-Up"; +"Stars.Intro.Transaction.FragmentTopUp.Subtitle" = "via Fragment"; +"Stars.Intro.Transaction.Unsupported.Title" = "Unsupported"; +"Stars.Intro.Transaction.Refund" = "Refund"; + +"Stars.Intro.PurchasedTitle" = "Stars Acquired"; +"Stars.Intro.PurchasedText" = "**%@** added to your balance."; +"Stars.Intro.PurchasedText.Stars_1" = "%@ Star"; +"Stars.Intro.PurchasedText.Stars_any" = "%@ Stars"; + +"Stars.Purchase.GetStars" = "Get Stars"; +"Stars.Purchase.GetStarsInfo" = "Choose how many Stars you would like to buy."; + +"Stars.Purchase.Balance" = "Balance"; + +"Stars.Purchase.StarsNeeded_1" = "%@ Star Needed"; +"Stars.Purchase.StarsNeeded_any" = "%@ Stars Needed"; +"Stars.Purchase.StarsNeededInfo" = "Buy Stars to use them on **%@** and other miniapps."; + +"Stars.Purchase.Stars_1" = "%@ Star"; +"Stars.Purchase.Stars_any" = "%@ Stars"; +"Stars.Purchase.ShowMore" = "Show More Options"; +"Stars.Purchase.Info" = "By proceeding and purchasing Stars, you agree with [Terms and Conditions]()."; +"Stars.Purchase.Terms_URL" = "https://telegram.org/tos"; + +"Stars.Transaction.Via" = "Via"; +"Stars.Transaction.To" = "To"; +"Stars.Transaction.From" = "From"; +"Stars.Transaction.Id" = "Transaction ID"; +"Stars.Transaction.Date" = "Date"; +"Stars.Transaction.Terms" = "Review the [Terms of Service]() for Stars."; +"Stars.Transaction.Terms_URL" = "https://telegram.org/tos"; +"Stars.Transaction.CopiedId" = "Transaction ID copied to clipboard."; + +"Stars.Transaction.AppleTopUp.Title" = "Stars Top-Up"; +"Stars.Transaction.AppleTopUp.Subtitle" = "App Store"; +"Stars.Transaction.GoogleTopUp.Title" = "Stars Top-Up"; +"Stars.Transaction.GoogleTopUp.Subtitle" = "Play Market"; +"Stars.Transaction.PremiumBotTopUp.Title" = "Stars Top-Up"; +"Stars.Transaction.PremiumBotTopUp.Subtitle" = "Premium Bot"; +"Stars.Transaction.FragmentTopUp.Title" = "Stars Top-Up"; +"Stars.Transaction.FragmentTopUp.Subtitle" = "Fragment"; +"Stars.Transaction.Unsupported.Title" = "Unsupported"; +"Stars.Transaction.Refund" = "Refund"; + +"Stars.Transfer.Title" = "Confirm Your Purchase"; +"Stars.Transfer.Info" = "Do you want to buy **%1$@** in **%2$@** for **%3$@**?"; +"Stars.Transfer.Info.Stars_1" = "%@ Star"; +"Stars.Transfer.Info.Stars_any" = "%@ Stars"; +"Stars.Transfer.Pay" = "Confirm and Pay"; +"Stars.Transfer.PurchasedTitle" = "Purchase Completed"; +"Stars.Transfer.PurchasedText" = "You acquired **%1$@** in **%2$@** for **%3$@**."; +"Stars.Transfer.Purchased.Stars_1" = "%@ Star"; +"Stars.Transfer.Purchased.Stars_any" = "%@ Stars"; + +"Stars.Transfer.Balance" = "Balance"; + +"Stars.Transfer.Unavailable" = "Sorry, no star purchases are available from your country."; + +"Settings.Stars" = "Your Stars"; + +"Chat.MessageEffectMenu.TitleAddEffect" = "Add an animated effect"; +"Chat.MessageEffectMenu.SectionMessageEffects" = "Message Effects"; +"Chat.SendMessageMenu.MoveCaptionUp" = "Move Caption Up"; +"Chat.SendMessageMenu.MoveCaptionDown" = "Move Caption Down"; +"Chat.SendMessageMenu.ToastPremiumRequired.Text" = "Subscribe to [Telegram Premium]() to add this animated effect."; + +"BusinessLink.AlertTextLimitText" = "The message text limit is 4096 characters"; + +"Chat.SendMessageMenu.EditMessage" = "Edit Message"; diff --git a/Telegram/Telegram-iOS/en.lproj/NiceLocalizable.strings b/Telegram/Telegram-iOS/en.lproj/NiceLocalizable.strings index e74c084ce00..af5d37af770 100644 --- a/Telegram/Telegram-iOS/en.lproj/NiceLocalizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/NiceLocalizable.strings @@ -147,6 +147,7 @@ "Gmod.Disable" = "Disable Preview Mode?"; "Gmod.Notice" = "Your online status will be hidden from everyone by Telegram privacy settings.\nApp will show a warning if you're entering a private chat.\nYour online status may be revealed if you send or type ANY message."; +"ShowNicegramButtonInChat" = "Show Nicegram Button in Chat"; "SendWithKb" = "Send with «Enter» button"; "NiceFeatures.ShowGmodIcon" = "Show Preview Mode icon"; "Gmod.OpenChatQ" = "Open chat?"; @@ -188,21 +189,6 @@ "DoubleBottom.Enabled.OK" = "OK"; "DoubleBottom.Passcode.Error" = "Please set another passcode different from that you use for Passcode Lock"; -/*Onboarding*/ -"NicegramOnboarding.Continue" = "Continue"; -"NicegramOnboarding.1.Title" = "№1 Client for Telegram Messenger"; -"NicegramOnboarding.1.Desc" = "Join over 2 million Nicegram users and get access to the most powerful and secure Telegram client for business."; -"NicegramOnboarding.2.Title" = "Advanced Messaging Experience"; -"NicegramOnboarding.2.Desc" = "Speed up your communication with unique features like a Built-in Translator, Speech 2 Text, Forwarding messages without specifying the author and Quick saving to favorites."; -"NicegramOnboarding.3.Title" = "Extended User Profile"; -"NicegramOnboarding.3.Desc" = "Create as many Telegram accounts as you need completely free of charge, as well as view an advanced user profile with ID, registration date and clickable links."; -"NicegramOnboarding.4.Title" = "Unique Business Extensions"; -"NicegramOnboarding.4.Desc" = "Take advantage of the innovations from the Nicegram team and get an eSIM with internet access in 133 countries. You can also register new Telegram accounts to the phone number of the eSIM data plan."; -"NicegramOnboarding.5.Title" = "The Most Secure Messenger"; -"NicegramOnboarding.5.Desc" = "Secure Messenger with Strong Encryption, Group Audio and Video Calls, Public Channels, Groups and Bots, Unlimited Cloud Storage for Chats, Media and Documents sharing."; -"NicegramOnboarding.6.Title" = "Meet Lily"; -"NicegramOnboarding.6.Desc" = "Your Personal AI Chatbot Assistant for Simplifying Your Life!"; - /*Rounded Videos*/ "RoundedVideos.ButtonTitle" = "Send as Rounded Video"; "RoundedVideos.MoreButtonTooltip" = "Convert square videos to send as circles with one tap."; diff --git a/Tests/LottieMesh/Resources/Cat.json b/Tests/LottieMesh/Resources/Cat.json deleted file mode 100644 index 9ed36bd9be3..00000000000 --- a/Tests/LottieMesh/Resources/Cat.json +++ /dev/null @@ -1 +0,0 @@ -{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":120,"w":512,"h":512,"nm":"Cat Kissing heart","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 1","parent":28,"sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[144.75,81.342,0]},"a":{"a":0,"k":[50,50,0]}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"f1","parent":4,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[16.902,56.403,0],"to":[-10.812,-9.286,0],"ti":[-4.15,9.939,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[7.578,18.211,0],"to":[-3.682,8.922,0],"ti":[-11.474,-9.33,0]},{"t":100,"s":[16.902,56.403,0]}]},"a":{"a":0,"k":[41.268,20.785,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0.651,-0.673],[8.733,-1.767],[-19.861,3.406],[-2.625,-8.662],[-1,0.306],[12.5,-2.186],[-32.67,3.532],[-9.545,2.662]],"o":[[-7.894,8.108],[-18.845,2.227],[9.22,-1.644],[0.979,3.228],[-9.903,3.028],[-27.269,4.551],[11.584,-0.098],[1.044,-0.292]],"v":[[22.717,-18.677],[-4.571,-15.821],[6.137,12.114],[33.53,0.445],[31.529,4.186],[7.089,18.038],[-1.84,-22.491],[21.727,-20.236]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[0.793,-0.267],[8.733,-1.767],[-21.574,0.524],[-8.505,-5.453],[1.254,0.321],[7.891,-1.169],[-32.67,3.532],[-9.454,0.339]],"o":[[-10.484,3.106],[-19.463,2.918],[10.711,-0.207],[2.701,1.732],[-6.741,1.29],[-27.325,3.017],[11.584,-0.098],[1.054,-0.034]],"v":[[26.863,-13.654],[-3.731,-17.014],[2.892,8.287],[24.973,7.108],[20.51,8.322],[3.334,13.632],[-1.853,-22.754],[27.297,-16.351]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0.929,0.119],[8.733,-1.767],[-23.207,-2.223],[-1.817,-2.005],[3.403,0.335],[3.497,-0.198],[-32.67,3.532],[-9.368,-1.877]],"o":[[-12.954,-1.662],[-20.051,3.577],[12.132,1.162],[2.265,2.499],[-3.727,-0.367],[-27.379,1.554],[11.584,-0.098],[1.063,0.213]],"v":[[30.815,-8.865],[-2.93,-18.152],[-0.201,4.64],[15.987,10.187],[9.335,9.402],[-0.247,9.432],[-1.866,-23.005],[32.608,-12.648]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":54,"s":[{"i":[[0.871,-0.045],[8.733,-1.767],[-22.513,-1.055],[-4.661,-3.471],[2.489,0.329],[5.366,-0.611],[-32.67,3.532],[-9.405,-0.935]],"o":[[-11.903,0.366],[-19.801,3.296],[11.528,0.58],[2.451,2.173],[-5.009,0.337],[-27.356,2.176],[11.584,-0.098],[1.059,0.108]],"v":[[29.134,-10.902],[-3.271,-17.668],[1.115,6.191],[19.808,8.877],[14.087,8.943],[1.276,11.218],[-1.861,-22.898],[36.162,-12.839]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[{"i":[[0.793,-0.267],[8.733,-1.767],[-21.574,0.524],[-8.505,-5.453],[1.254,0.321],[7.891,-1.169],[-32.67,3.532],[-9.454,0.339]],"o":[[-10.484,3.106],[-19.463,2.918],[10.711,-0.207],[2.701,1.732],[-6.741,1.29],[-27.325,3.017],[11.584,-0.098],[1.054,-0.034]],"v":[[26.863,-13.654],[-3.731,-17.014],[2.892,8.287],[24.973,7.108],[20.51,8.322],[3.334,13.632],[-1.853,-22.754],[27.297,-16.351]],"c":true}]},{"t":100,"s":[{"i":[[0.651,-0.673],[8.733,-1.767],[-19.861,3.406],[-2.625,-8.662],[-1,0.306],[12.5,-2.186],[-32.67,3.532],[-9.545,2.662]],"o":[[-7.894,8.108],[-18.845,2.227],[9.22,-1.644],[0.979,3.228],[-9.903,3.028],[-27.269,4.551],[11.584,-0.098],[1.044,-0.292]],"v":[[22.717,-18.677],[-4.571,-15.821],[6.137,12.114],[33.53,0.445],[31.529,4.186],[7.089,18.038],[-1.84,-22.491],[21.727,-20.236]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[34.759,22.839]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[-9.375,0.938],[6.562,4.312]],"o":[[-12.188,3.938],[9.375,-0.937],[-6.562,-4.313]],"v":[[-6.437,-5],[-0.687,8],[12.063,-4.625]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0,0],[-9.375,0.938],[6.562,4.312]],"o":[[-12.188,3.938],[9.375,-0.937],[-6.562,-4.313]],"v":[[-7.605,-7.323],[-3.029,-8.211],[10.895,-6.948]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[-9.375,0.938],[6.562,4.312]],"o":[[-12.188,3.938],[9.375,-0.937],[-6.562,-4.313]],"v":[[-6.437,-5],[-0.687,8],[12.063,-4.625]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.847000002394,0.39199999641,0.611999990426,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[35.293,11.008]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[5,-3],[-7,-9],[-8,2],[-7.143,4.495]],"o":[[-4,-5],[-5,3],[7,9],[8,-2],[7.417,-4.666]],"v":[[3.042,-13],[-18.958,-15],[-20.958,8],[2.042,16],[20.542,0.916]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[{"i":[[1.874,0.695],[6.475,-3.732],[-7,-9],[-9.105,4.499],[-7.143,4.495]],"o":[[-4.562,-4.055],[-5.035,2.939],[7,9],[8.308,-3.769],[2.475,-4.597]],"v":[[3.873,-14.069],[-19.066,-15.353],[-22.312,6.098],[3.572,11.589],[22.755,-0.962]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[5.712,2.12],[9.496,-5.232],[-7,-9],[-9.131,-6.24],[-7.143,4.495]],"o":[[-5.712,-2.12],[-5.107,2.814],[7,9],[8,-2],[-7.646,-4.456]],"v":[[5.575,-16.258],[-19.287,-16.076],[-25.084,2.203],[8.536,12.446],[27.288,-4.807]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[1.874,0.695],[6.475,-3.732],[-7,-9],[-9.105,4.499],[-7.143,4.495]],"o":[[-4.562,-4.055],[-5.035,2.939],[7,9],[8.308,-3.769],[2.475,-4.597]],"v":[[3.873,-14.069],[-19.066,-15.353],[-22.312,6.098],[3.572,11.589],[22.755,-0.962]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[5,-3],[-7,-9],[-8,2],[-7.143,4.495]],"o":[[-4,-5],[-5,3],[7,9],[8,-2],[7.417,-4.666]],"v":[[3.042,-13],[-18.958,-15],[-20.958,8],[2.042,16],[20.542,0.916]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.317999985639,0.250999989229,0.340999977261,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[41.94,21.383]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"f2","parent":4,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[-2.767,39.504,0],"to":[-1.188,-2.175,0],"ti":[-1.923,2.047,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[-2.018,31.694,0],"to":[-1.855,1.914,0],"ti":[-1.109,-2.178,0]},{"t":100,"s":[-2.767,39.504,0]}]},"a":{"a":0,"k":[26.122,23.263,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0.756,-0.2],[10.97,-6.857],[-1.658,-5.764],[-8.699,0.471],[3.793,-0.425],[4.467,6.13],[-13.35,2.744],[-1.28,-0.875]],"o":[[-7.171,1.887],[-4.457,2.666],[2.673,8.425],[4.046,-0.22],[-12.093,1.336],[-8.359,-11.275],[12.733,-2.744],[1.179,0.806]],"v":[[22.538,-7.906],[-5.818,-11.696],[-11.643,3.163],[11.24,15.109],[11.836,21.079],[-15.357,10.918],[-3.292,-19.671],[22.538,-9.906]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0.756,-0.2],[9.396,-1.276],[-0.159,-8.296],[-6.871,-2.834],[3.795,-0.409],[4.224,10.934],[-13.35,2.744],[-1.28,-0.875]],"o":[[-7.171,1.887],[-5.267,0.715],[0.25,13.056],[3.746,1.545],[-13.734,1.479],[-3.273,-8.473],[12.733,-2.744],[1.179,0.806]],"v":[[22.538,-7.906],[-2.349,-14.456],[-15.161,-3.212],[14.168,11.861],[8.431,13.362],[-20.118,0.478],[-3.292,-19.671],[22.538,-9.906]],"c":true}]},{"t":100,"s":[{"i":[[0.756,-0.2],[10.97,-6.857],[-1.658,-5.764],[-8.699,0.471],[3.793,-0.425],[4.467,6.13],[-13.35,2.744],[-1.28,-0.875]],"o":[[-7.171,1.887],[-4.457,2.666],[2.673,8.425],[4.046,-0.22],[-12.093,1.336],[-8.359,-11.275],[12.733,-2.744],[1.179,0.806]],"v":[[22.538,-7.906],[-5.818,-11.696],[-11.643,3.163],[11.24,15.109],[11.836,21.079],[-15.357,10.918],[-3.292,-19.671],[22.538,-9.906]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[23.966,22.665]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[-8.188,-1.187],[4.5,6.375]],"o":[[-7.125,0.75],[8.187,1.188],[-4.5,-6.375]],"v":[[-5.969,-7.063],[-7.469,5.188],[11.156,0.688]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0,0],[-8.231,-0.842],[4.5,6.375]],"o":[[-7.125,0.75],[14.536,1.487],[-4.5,-6.375]],"v":[[-5.969,-7.063],[-7.165,-5.362],[11.157,0.688]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[-8.188,-1.187],[4.5,6.375]],"o":[[-7.125,0.75],[8.187,1.188],[-4.5,-6.375]],"v":[[-5.969,-7.063],[-7.469,5.188],[11.156,0.688]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.847000002394,0.39199999641,0.611999990426,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[30.347,13.072]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[4.666,-6.333],[-3,-6],[-10,1]],"o":[[-7,-8],[-4.667,6.334],[3,6],[10,-1]],"v":[[15,-8.667],[-12.333,-13.001],[-16,10.333],[9,18.333]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0,0],[6.92,-3.741],[-1.692,-6.098],[-17.478,-1.491]],"o":[[-7,-8],[-4.634,2.506],[3.335,12.017],[10.014,0.854]],"v":[[15,-8.667],[-12.625,-13.957],[-19.628,-1.106],[11.685,12.153]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[4.666,-6.333],[-3,-6],[-10,1]],"o":[[-7,-8],[-4.667,6.334],[3,6],[10,-1]],"v":[[15,-8.667],[-12.333,-13.001],[-16,10.333],[9,18.333]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.317999985639,0.250999989229,0.340999977261,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[26.504,22.426]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"paw","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":45,"s":[107]},{"t":100,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[111.441,291.275,0],"to":[15.458,-25.875,0],"ti":[-18.418,-3.424,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[183.941,262.775,0],"to":[-17.832,-3.201,0],"ti":[15.224,-27.332,0]},{"t":100,"s":[111.441,291.275,0]}]},"a":{"a":0,"k":[58.556,54.608,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[6.209,-5.835],[-7.703,-6.298],[-12.093,-4.587],[0.908,-0.236],[0,0],[13.005,13.809],[-5.857,11.364],[-0.037,-3.497]],"o":[[-11.16,10.306],[7.74,6.78],[0.881,0.334],[0,0],[-14.998,3.791],[-8.334,-8.919],[6.873,-13.117],[0.031,2.937]],"v":[[-12.669,-16.371],[-7.044,17.83],[26.777,14.898],[26.678,16.739],[26.635,16.75],[-13.581,19.769],[-21.801,-14.878],[10.484,-30.081]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[{"i":[[17.681,-13.462],[-5.195,-3.752],[-4.316,-1.138],[0.908,-0.236],[0,0],[7.616,6.279],[-5.857,11.364],[-1.898,-5.319]],"o":[[-11.16,10.306],[5.195,3.752],[4.245,-0.016],[0,0],[-4.356,0.822],[-8.14,-7.079],[12.606,-17.343],[0.987,2.766]],"v":[[-13.1,-16.985],[-9.293,9.312],[11.728,15.879],[14.154,19.355],[11.429,19.77],[-13.505,12.501],[-21.801,-14.878],[21.891,-30.155]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[6.209,-5.835],[-8.027,-3.588],[-2.125,-0.389],[-12.491,-10.035],[1.162,1.384],[8.488,4.463],[-5.857,11.364],[-0.037,-3.497]],"o":[[-11.16,10.306],[1.656,0.74],[13.201,2.417],[1.388,1.115],[-2.312,-2.754],[-7.453,-3.919],[6.873,-13.117],[0.031,2.937]],"v":[[-12.669,-16.371],[-7.853,13.045],[1.242,15.385],[32.684,23.987],[27.093,25.302],[-11.742,16.592],[-21.801,-14.878],[10.484,-30.081]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[17.681,-13.462],[-5.195,-3.752],[-4.316,-1.138],[0.908,-0.236],[0,0],[7.616,6.279],[-5.857,11.364],[-1.898,-5.319]],"o":[[-11.16,10.306],[5.195,3.752],[4.245,-0.016],[0,0],[-4.356,0.822],[-8.14,-7.079],[12.606,-17.343],[0.987,2.766]],"v":[[-13.1,-16.985],[-9.293,9.312],[11.728,15.879],[14.154,19.355],[11.429,19.77],[-13.505,12.501],[-21.801,-14.878],[21.891,-30.155]],"c":true}]},{"t":100,"s":[{"i":[[6.209,-5.835],[-7.703,-6.298],[-12.093,-4.587],[0.908,-0.236],[0,0],[13.005,13.809],[-5.857,11.364],[-0.037,-3.497]],"o":[[-11.16,10.306],[7.74,6.78],[0.881,0.334],[0,0],[-14.998,3.791],[-8.334,-8.919],[6.873,-13.117],[0.031,2.937]],"v":[[-12.669,-16.371],[-7.044,17.83],[26.777,14.898],[26.678,16.739],[26.635,16.75],[-13.581,19.769],[-21.801,-14.878],[10.484,-30.081]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[27.909,41.177]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[7.705,8.101],[-4.791,-0.109],[1.39,2.233],[6.232,1.487]],"o":[[7.036,12.193],[10.912,0.249],[-1.07,-1.718],[-8.815,-2.103]],"v":[[7.312,41.849],[27.461,58.027],[36.406,57.558],[28.732,54.731]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":25,"s":[{"i":[[7.705,8.101],[-4.791,-0.109],[1.39,2.233],[6.232,1.487]],"o":[[7.036,12.193],[10.912,0.249],[-1.07,-1.718],[-8.815,-2.103]],"v":[[7.312,41.849],[30.461,57.652],[39.05,58.965],[27.521,56.399]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[7.705,8.101],[-10.915,0.064],[4.829,11.063],[8.986,1.174]],"o":[[7.909,17.319],[19.18,-0.113],[-1.309,-2.998],[-8.986,-1.174]],"v":[[7.312,41.849],[37.533,60.321],[68.597,59.319],[49.423,51.326]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":75,"s":[{"i":[[7.705,8.101],[-4.791,-0.109],[1.39,2.233],[6.232,1.487]],"o":[[7.036,12.193],[10.912,0.249],[-1.07,-1.718],[-8.815,-2.103]],"v":[[7.312,41.849],[30.461,57.652],[39.05,58.965],[27.521,56.399]],"c":true}]},{"t":100,"s":[{"i":[[7.705,8.101],[-4.791,-0.109],[1.39,2.233],[6.232,1.487]],"o":[[7.036,12.193],[10.912,0.249],[-1.07,-1.718],[-8.815,-2.103]],"v":[[7.312,41.849],[27.461,58.027],[36.406,57.558],[28.732,54.731]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.439215686275,0.380392156863,0.462745098039,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[5.988,3.7]],"o":[[-0.133,-8.013],[0,0]],"v":[[4.471,10.997],[-5.334,-11.612]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[{"i":[[0,0],[16.114,0.14]],"o":[[-0.133,-8.013],[0,0]],"v":[[4.471,10.996],[-23.015,-14.798]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0,0],[29.161,-11.697]],"o":[[-0.133,-8.013],[0,0]],"v":[[4.471,10.997],[-38.222,-13.226]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[0,0],[16.114,0.14]],"o":[[-0.133,-8.013],[0,0]],"v":[[4.471,10.996],[-23.015,-14.798]],"c":false}]},{"t":100,"s":[{"i":[[0,0],[5.988,3.7]],"o":[[-0.133,-8.013],[0,0]],"v":[[4.471,10.997],[-5.334,-11.612]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[68.085,25.996]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[-7.778,-1.46],[-3.058,0.611],[-8,9],[3.43,2.535],[-0.883,1.684],[1.073,1.314],[0,0]],"o":[[2.301,0.432],[0,0],[5.054,-5.686],[-1.529,-1.13],[0.528,-1.008],[-7.042,-8.625],[-7.215,3.25]],"v":[[-12.089,9.48],[-4.046,9.312],[15.954,10.313],[13.852,-2.398],[12.546,-7.28],[11.954,-10.688],[-13.794,-5.954]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[-7.778,-1.46],[-3.058,0.611],[-6.645,-4.976],[3.43,2.535],[1.831,0.512],[1.303,0.154],[0,0]],"o":[[2.301,0.432],[0,0],[6.089,4.56],[-1.529,-1.13],[-2.469,-0.69],[-3.42,-0.405],[-6.52,-2.658]],"v":[[-8.944,-11.872],[-0.715,-13.142],[19.452,-7.749],[15.245,-10.666],[10.002,-13.037],[5.412,-13.916],[-9.252,-12.049]],"c":true}]},{"t":100,"s":[{"i":[[-7.778,-1.46],[-3.058,0.611],[-8,9],[3.43,2.535],[-0.883,1.684],[1.073,1.314],[0,0]],"o":[[2.301,0.432],[0,0],[5.054,-5.686],[-1.529,-1.13],[0.528,-1.008],[-7.042,-8.625],[-7.215,3.25]],"v":[[-12.089,9.48],[-4.046,9.312],[15.954,10.313],[13.852,-2.398],[12.546,-7.28],[11.954,-10.688],[-13.794,-5.954]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.847000002394,0.39199999641,0.611999990426,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[41.661,23.688]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[-1,3],[6.99,7.542],[13.008,-12.252],[-10,-9],[-13,-1]],"o":[[2.846,-8.538],[-6.99,-7.542],[-13.008,12.252],[10,9],[13,1]],"v":[[34.077,3.5],[25.077,-23.5],[-23.923,-18.5],[-18.923,20.5],[17.077,16.5]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[{"i":[[-1,3],[6.99,7.542],[13.008,-12.252],[-10.087,-8.9],[-13,-1]],"o":[[2.846,-8.538],[-6.99,-7.542],[-13.008,12.252],[9.4,8.317],[13,1]],"v":[[34.077,3.5],[25.077,-23.5],[-23.923,-18.5],[-19.657,12.64],[17.1,17.136]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[-1,3],[6.99,7.542],[13.008,-12.252],[-10.266,-8.695],[-11.542,-6.348]],"o":[[2.846,-8.538],[-6.99,-7.542],[-13.008,12.252],[8.17,6.919],[11.425,6.283]],"v":[[34.077,3.5],[25.077,-23.5],[-23.923,-18.5],[-22.185,14.962],[21.634,22.427]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[-1,3],[6.99,7.542],[13.008,-12.252],[-10.087,-8.9],[-13,-1]],"o":[[2.846,-8.538],[-6.99,-7.542],[-13.008,12.252],[9.4,8.317],[13,1]],"v":[[34.077,3.5],[25.077,-23.5],[-23.923,-18.5],[-19.657,12.64],[17.1,17.136]],"c":true}]},{"t":100,"s":[{"i":[[-1,3],[6.99,7.542],[13.008,-12.252],[-10,-9],[-13,-1]],"o":[[2.846,-8.538],[-6.99,-7.542],[-13.008,12.252],[10,9],[13,1]],"v":[[34.077,3.5],[25.077,-23.5],[-23.923,-18.5],[-18.923,20.5],[17.077,16.5]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.317999985639,0.250999989229,0.340999977261,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[37.538,40.501]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"f3","parent":4,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[4.619,25.343,0],"to":[-3.608,6.724,0],"ti":[-3.478,-6.387,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[5.5,47.034,0],"to":[-3.684,-6.408,0],"ti":[-3.401,6.708,0]},{"t":100,"s":[4.619,25.343,0]}]},"a":{"a":0,"k":[37.004,30.342,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[8,-2],[-3,-8],[-2,6]],"o":[[-12,-9],[-8,2],[3,8],[2,-6]],"v":[[19.5,-3.5],[-8.5,-13.5],[-18.5,7.5],[19.5,9.5]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0,0],[8,-2],[-7.921,-9.107],[-13.277,-5.057]],"o":[[-12,-9],[-8,2],[8.122,9.339],[5.91,2.251]],"v":[[19.5,-3.5],[-8.5,-13.5],[-20.347,2.313],[19.5,9.5]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[8,-2],[-3,-8],[-2,6]],"o":[[-12,-9],[-8,2],[3,8],[2,-6]],"v":[[19.5,-3.5],[-8.5,-13.5],[-18.5,7.5],[19.5,9.5]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[36.5,30.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[-6.438,-2.687],[-1.687,3.188],[4.437,3]],"o":[[-9.688,2.5],[6.438,2.688],[1.688,-3.187],[-4.438,-3]],"v":[[-0.874,-6.469],[-8.749,4.156],[10.876,5.031],[10.751,-5.219]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0,0],[-6.572,2.341],[-4.068,-1.048],[4.437,3]],"o":[[-9.688,2.5],[7.594,-2.706],[3.492,0.9],[-4.438,-3]],"v":[[-0.874,-6.469],[-8.495,-4.418],[9.323,-5.605],[10.751,-5.219]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[-6.438,-2.687],[-1.687,3.188],[4.437,3]],"o":[[-9.688,2.5],[6.438,2.688],[1.688,-3.187],[-4.438,-3]],"v":[[-0.874,-6.469],[-8.749,4.156],[10.876,5.031],[10.751,-5.219]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.847000002394,0.39199999641,0.611999990426,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[30.624,24.343]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[8,-2],[-3,-8],[-2,6]],"o":[[-12,-9],[-8,2],[3,8],[2,-6]],"v":[[19.5,-3.5],[-8.5,-13.5],[-18.5,7.5],[19.5,9.5]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0,0],[8,-2],[-7.921,-9.107],[-13.277,-5.057]],"o":[[-12,-9],[-8,2],[8.122,9.339],[5.91,2.251]],"v":[[19.5,-3.5],[-8.5,-13.5],[-20.347,2.313],[19.5,9.5]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[8,-2],[-3,-8],[-2,6]],"o":[[-12,-9],[-8,2],[3,8],[2,-6]],"v":[[19.5,-3.5],[-8.5,-13.5],[-18.5,7.5],[19.5,9.5]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.439215689898,0.380392163992,0.46274510026,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[36.5,30.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":3,"nm":"Null heart","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":-10},"p":{"a":0,"k":[245.089,223.749,0]},"a":{"a":0,"k":[80.714,97.833,0]}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"heart","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":102,"s":[100]},{"t":103,"s":[0]}]},"r":{"a":1,"k":[{"i":{"x":[0.189],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":80,"s":[10]},{"t":105,"s":[30]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[80.714,97.833,0],"to":[-4.609,-6.736,0],"ti":[-3.364,4.442,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":60,"s":[81.349,78.398,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":80,"s":[81.349,78.398,0],"to":[-14.819,-10.736,0],"ti":[-14.105,157.019,0]},{"t":105,"s":[-52.15,-44.145,0]}]},"a":{"a":0,"k":[80.714,97.833,0]},"s":{"a":1,"k":[{"i":{"x":[0.189,0.189,0.189],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":45,"s":[0,0,100]},{"t":105,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1.558,-4.781],[2.924,-0.08],[0.219,0.653]],"o":[[-2.251,6.795],[-0.723,0.02],[-6.051,-18.128]],"v":[[4.379,-0.723],[1.682,9.603],[0.114,8.506]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.620000023935,0.008000000785,0.340999977261,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[52.977,37.118]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-4.472,1.491],[-3.206,18.21],[14.833,3.5],[3.417,-14.708],[9.453,-8.569],[-20.125,-12.875]],"o":[[4.5,-1.5],[3.42,-19.427],[-14.483,-3.417],[0,0],[-13.375,12.125],[16.471,10.537]],"v":[[19.978,40.609],[41.103,-0.516],[24.603,-38.683],[-5.648,-19.266],[-31.148,-20.266],[-19.398,27.859]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.620000023935,0.008000000785,0.340999977261,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[59.522,57.1]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0.125,2.25],[-2.437,-0.375],[1.875,-2.813]],"o":[[-0.33,-5.932],[2.438,0.375],[-1.875,2.812]],"v":[[-3.867,3.625],[1.759,-5.5],[0.509,2.625]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.501999978458,0.776000019148,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[64.033,37.542]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[4.597,-0.418],[0.438,-5.75],[-5.187,-4.187],[-0.687,6.875]],"o":[[-5.166,0.469],[-0.437,5.75],[3.581,2.89],[1.03,-10.293]],"v":[[2.878,-15.096],[-7.038,-4.063],[-1.038,12.624],[0.837,-3.063]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.501999978458,0.776000019148,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[34.225,55.648]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1.5,3.313],[-7.843,6.138],[-0.171,14.548],[6.125,6.625],[-0.713,1.695],[-6.653,-12.938],[5.232,-3.113]],"o":[[0,0],[7.666,-6],[0.136,-11.629],[-4.735,-5.122],[0.713,-1.695],[10.914,21.229],[-5.232,3.113]],"v":[[-26.625,31.541],[-8.458,30.333],[3.261,-8.902],[-4.25,-34.834],[-10.125,-39.084],[15.711,-27.646],[-1,37.666]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823999980852,0.016000001571,0.501999978458,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[82,59.168]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.475,18.432],[9.07,3.416],[4.25,-15.875],[12.237,-7.231],[-13.886,-10.392],[-9.621,-0.165]],"o":[[1.916,-23.942],[-9.071,-3.415],[0,0],[-21.676,12.81],[14.476,10.835],[5.881,0.1]],"v":[[44.099,-1.657],[28.092,-35.997],[-1.265,-18.217],[-24.339,-21.799],[-14.867,27.484],[27.909,39.312]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.905999995213,0.156999999402,0.528999956916,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[55.39,56.301]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 6","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"heart 9","parent":7,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.189],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":77,"s":[-19.681]},{"t":102,"s":[0.319]}]},"p":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.167,"y":0},"t":97,"s":[63.394,65.09,0],"to":[51.309,11.913,0],"ti":[0,0,0]},{"t":117,"s":[133.266,127.688,0]}]},"a":{"a":0,"k":[66.22,63.693,0]},"s":{"a":1,"k":[{"i":{"x":[0.189,0.189,0.189],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":42,"s":[0,0,100]},{"i":{"x":[0.576,0.576,0.576],"y":[0.599,0.599,23.85]},"o":{"x":[0.17,0.17,0.17],"y":[0,0,0]},"t":97,"s":[100.318,100.318,100]},{"i":{"x":[0.836,0.836,0.836],"y":[1,1,1]},"o":{"x":[0.408,0.408,0.408],"y":[0.623,0.623,-26.829]},"t":106,"s":[43.173,43.173,100]},{"t":117,"s":[0,0,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.788,14.788]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.01568627451,0.501960784314,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[66.22,63.693]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":-3,"op":177,"st":-3,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"heart 8","parent":7,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.167,"y":0},"t":99,"s":[63.394,65.09,0],"to":[-12.835,49.669,0],"ti":[0,0,0]},{"t":119,"s":[85.951,183.108,0]}]},"a":{"a":0,"k":[66.22,63.693,0]},"s":{"a":1,"k":[{"i":{"x":[0.189,0.189,0.189],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44,"s":[0,0,100]},{"i":{"x":[0.565,0.565,0.565],"y":[0.605,0.605,18.812]},"o":{"x":[0.18,0.18,0.18],"y":[0,0,0]},"t":99,"s":[100.318,100.318,100]},{"i":{"x":[0.841,0.841,0.841],"y":[1,1,1]},"o":{"x":[0.384,0.384,0.384],"y":[0.533,0.533,-29.239]},"t":106,"s":[55.072,55.072,100]},{"t":119,"s":[0,0,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.788,14.788]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.01568627451,0.501960784314,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[66.22,63.693]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":-1,"op":179,"st":-1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"heart 7","parent":7,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.167,"y":0},"t":98,"s":[63.394,65.09,0],"to":[37.549,-61.843,0],"ti":[0,0,0]},{"t":118,"s":[187.317,21.394,0]}]},"a":{"a":0,"k":[66.22,63.693,0]},"s":{"a":1,"k":[{"i":{"x":[0.189,0.189,0.189],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":43,"s":[0,0,100]},{"i":{"x":[0.57,0.57,0.57],"y":[0.599,0.599,21.437]},"o":{"x":[0.175,0.175,0.175],"y":[0,0,0]},"t":98,"s":[100.318,100.318,100]},{"i":{"x":[0.839,0.839,0.839],"y":[1,1,1]},"o":{"x":[0.397,0.397,0.397],"y":[0.577,0.577,-28.27]},"t":106,"s":[49.155,49.155,100]},{"t":118,"s":[0,0,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.788,14.788]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.01568627451,0.501960784314,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[66.22,63.693]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":-2,"op":178,"st":-2,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"heart 6","parent":7,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.189],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":76,"s":[-19.681]},{"t":101,"s":[0.319]}]},"p":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.167,"y":0},"t":96,"s":[63.394,65.09,0],"to":[-62.756,-13.289,0],"ti":[0,0,0]},{"t":116,"s":[-12.34,101.557,0]}]},"a":{"a":0,"k":[66.22,63.693,0]},"s":{"a":1,"k":[{"i":{"x":[0.189,0.189,0.189],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":41,"s":[0,0,100]},{"i":{"x":[0.583,0.583,0.583],"y":[0.603,0.603,26]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":96,"s":[100.318,100.318,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.417,0.417,0.417],"y":[0.675,0.675,-25]},"t":106,"s":[37.159,37.159,100]},{"t":116,"s":[0,0,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.788,14.788]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.01568627451,0.501960784314,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[66.22,63.693]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":-4,"op":176,"st":-4,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"heart 5","parent":7,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.189],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":78,"s":[-19.681]},{"t":103,"s":[0.319]}]},"p":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.167,"y":0},"t":98,"s":[63.394,65.09,0],"to":[67.385,-10.433,0],"ti":[0,0,0]},{"t":118,"s":[174.241,84.573,0]}]},"a":{"a":0,"k":[66.22,63.693,0]},"s":{"a":1,"k":[{"i":{"x":[0.189,0.189,0.189],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":43,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":98,"s":[100.318,100.318,100]},{"t":118,"s":[0,0,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.788,14.788]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.01568627451,0.501960784314,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[66.22,63.693]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":-2,"op":178,"st":-2,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"heart 4","parent":7,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.167,"y":0},"t":100,"s":[63.394,65.09,0],"to":[-53.484,38.126,0],"ti":[0,0,0]},{"t":120,"s":[24.859,164.373,0]}]},"a":{"a":0,"k":[66.22,63.693,0]},"s":{"a":1,"k":[{"i":{"x":[0.189,0.189,0.189],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":45,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":100,"s":[100.318,100.318,100]},{"t":120,"s":[0,0,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.788,14.788]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.01568627451,0.501960784314,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[66.22,63.693]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"heart 3","parent":7,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.167,"y":0},"t":99,"s":[63.394,65.09,0],"to":[-3.869,-69.648,0],"ti":[0,0,0]},{"t":119,"s":[130.36,-25.451,0]}]},"a":{"a":0,"k":[66.22,63.693,0]},"s":{"a":1,"k":[{"i":{"x":[0.189,0.189,0.189],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":44,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":99,"s":[100.318,100.318,100]},{"t":119,"s":[0,0,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.788,14.788]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.01568627451,0.501960784314,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[66.22,63.693]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":-1,"op":179,"st":-1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"heart 2","parent":7,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.189],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":77,"s":[-19.681]},{"t":102,"s":[0.319]}]},"p":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.167,"y":0},"t":97,"s":[63.394,65.09,0],"to":[-34.617,-57.318,0],"ti":[0,0,0]},{"t":117,"s":[-31.348,31.057,0]}]},"a":{"a":0,"k":[66.22,63.693,0]},"s":{"a":1,"k":[{"i":{"x":[0.189,0.189,0.189],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":42,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":97,"s":[100.318,100.318,100]},{"t":117,"s":[0,0,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.788,14.788]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.01568627451,0.501960784314,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[66.22,63.693]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":-3,"op":177,"st":-3,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"Shape Layer 2","parent":25,"sr":1,"ks":{"p":{"a":0,"k":[2.759,85.47,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[36.962,-2.672],[18.863,70.063]],"o":[[-55.916,4.042],[-19.074,-70.847]],"v":[[-0.674,-58.611],[-133.389,-154.947]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[0]},{"t":109,"s":[100]}]},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":85,"s":[0]},{"t":103,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"Shape Layer 1","parent":25,"sr":1,"ks":{"p":{"a":0,"k":[2.759,85.47,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[35.36,-11.092],[18.863,70.063]],"o":[[-55.87,17.525],[-19.074,-70.847]],"v":[[-0.674,-58.611],[-141.474,-121.937]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":94,"s":[0]},{"t":113,"s":[100]}]},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":89,"s":[0]},{"t":107,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"f4","parent":4,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[37.738,13.01,0],"to":[-0.376,4.472,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[35.485,39.841,0],"to":[0,0,0],"ti":[-0.376,4.472,0]},{"t":100,"s":[37.738,13.01,0]}]},"a":{"a":0,"k":[44.123,32.01,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1.894,4.735],[8,0],[10,-6],[-4,-5],[0,0]],"o":[[-2,-5],[-8,0],[-10,6],[4,5],[0,0]],"v":[[27.053,4.5],[10.053,-6.5],[-18.947,-9.5],[-23.947,10.5],[5.053,14.5]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[43.947,30.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1,3],[6,-1],[-1.125,-1.625],[-6,2]],"o":[[-1.14,-3.42],[-6,1],[1.125,1.625],[6,-2]],"v":[[10.125,-2.5],[-1.875,-7.5],[-10,1.875],[0.125,6.5]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.847000002394,0.39199999641,0.611999990426,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[29.875,32.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0.838,5.03],[8,0],[10,-6],[-4,-5],[0,0]],"o":[[-1,-6],[-8,0],[-10,6],[4,5],[0,0]],"v":[[27.581,4.5],[10.581,-6.5],[-18.419,-9.5],[-23.419,10.5],[5.581,14.5]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.317999985639,0.250999989229,0.340999977261,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[43.419,30.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"arm","parent":4,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":10,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":45,"s":[-46]},{"t":100,"s":[0]}]},"p":{"a":0,"k":[49.516,39.146,0]},"a":{"a":0,"k":[35.9,36.357,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[17.013,22.683],[-7,-13],[-4,-33],[-22.75,-8.167]],"o":[[-8.159,-24.976],[-9.159,-12.212],[7,13],[4,33],[0,0]],"v":[[45.768,-1.733],[-5.768,-85.769],[-38.768,-51.769],[-23.768,16.231],[26.44,97.981]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,0],[13.927,25.823],[-7,-13],[-4,-33],[-24.518,-5.842]],"o":[[-8.159,-24.976],[-7.254,-13.262],[7,13],[4,33],[0,0]],"v":[[45.768,-1.733],[-6.04,-84.749],[-43.223,-65.617],[-23.768,16.231],[30.074,89.313]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":45,"s":[{"i":[[0,0],[10.985,28.818],[-7,-13],[-4,-33],[-26.203,-3.625]],"o":[[-8.159,-24.976],[-5.437,-14.264],[7,13],[4,33],[0,0]],"v":[[45.768,-1.733],[-6.3,-83.777],[-40.255,-58.882],[-23.768,16.231],[33.539,81.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[0,0],[13.927,25.823],[-7,-13],[-4,-33],[-24.518,-5.842]],"o":[[-8.159,-24.976],[-7.254,-13.262],[7,13],[4,33],[0,0]],"v":[[45.768,-1.733],[-6.04,-84.749],[-43.223,-65.617],[-23.768,16.231],[30.074,89.313]],"c":false}]},{"t":100,"s":[{"i":[[0,0],[17.013,22.683],[-7,-13],[-4,-33],[-22.75,-8.167]],"o":[[-8.159,-24.976],[-9.159,-12.212],[7,13],[4,33],[0,0]],"v":[[45.768,-1.733],[-5.768,-85.769],[-38.768,-51.769],[-23.768,16.231],[26.44,97.981]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[60.768,112.981]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[-1.359,-0.09],[-5.833,-15.399],[11.666,7.333],[6.833,32.667]],"o":[[0,0],[3.171,0.21],[8.334,22],[-11.667,-7.334],[-6.834,-32.666]],"v":[[-32.667,-70],[-30.497,-70.27],[-15.333,-52],[21.001,66.667],[-20.999,-0.667]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,0],[-2.003,-2.449],[-5.423,-14.196],[-6.727,-9.744],[6.555,30.332]],"o":[[0,0],[6.396,5.722],[8.409,21.971],[-17.397,-6.976],[-7.09,-32.609]],"v":[[-40.093,-90.028],[-34.95,-89.325],[-15.72,-51.98],[24.971,61.365],[-20.999,-0.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":45,"s":[{"i":[[0,0],[-3.32,-7.281],[-4.585,-11.731],[-44.394,-44.716],[5.986,25.549]],"o":[[0,0],[3.297,7.231],[8.563,21.912],[-29.133,-6.243],[-7.613,-32.493]],"v":[[-38.219,-86.798],[-29.558,-88.913],[-16.511,-51.938],[33.102,50.508],[-20.999,-0.667]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[0,0],[-2.003,-2.449],[-5.423,-14.196],[-6.727,-9.744],[6.555,30.332]],"o":[[0,0],[6.396,5.722],[8.409,21.971],[-17.397,-6.976],[-7.09,-32.609]],"v":[[-40.093,-90.028],[-34.95,-89.325],[-15.72,-51.98],[24.971,61.365],[-20.999,-0.667]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[-1.359,-0.09],[-5.833,-15.399],[11.666,7.333],[6.833,32.667]],"o":[[0,0],[3.171,0.21],[8.334,22],[-11.667,-7.334],[-6.834,-32.666]],"v":[[-32.667,-70],[-30.497,-70.27],[-15.333,-52],[21.001,66.667],[-20.999,-0.667]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.438999998803,0.380000005984,0.463000009574,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[61,143.545]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[1,22],[17,19],[-7,-13],[-4,-33],[-21.248,-14.628]],"o":[[-1.079,-23.745],[-10.178,-11.376],[7,13],[4,33],[11.5,7.916]],"v":[[47,7.563],[-8,-89.77],[-41,-55.77],[-26,12.23],[19,93.23]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":45,"s":[{"i":[[19.898,36.371],[7.089,17.797],[-7,-13],[-4,-33],[-28.358,-8.672]],"o":[[-14.292,-26.124],[-5.649,-14.181],[7,13],[4,33],[13.351,4.083]],"v":[[50.035,7.244],[-9.309,-87.923],[-41,-55.77],[-26,12.23],[30.422,77.266]],"c":true}]},{"t":100,"s":[{"i":[[1,22],[17,19],[-7,-13],[-4,-33],[-21.248,-14.628]],"o":[[-1.079,-23.745],[-10.178,-11.376],[7,13],[4,33],[11.5,7.916]],"v":[[47,7.563],[-8,-89.77],[-41,-55.77],[-26,12.23],[19,93.23]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.317999985639,0.250999989229,0.340999977261,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[63,116.982]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"browe","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[91.297,12.085,0]},"a":{"a":0,"k":[63.694,23.139,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":19,"s":[{"i":[[3.904,0.985],[-4.273,-0.119],[-3.051,-3.041],[-0.671,-3.881],[1.679,-3.651],[1.806,2.552],[1.843,1.804],[2.47,1.309]],"o":[[2.677,-3.02],[4.285,0.094],[3.092,3.036],[0.656,3.898],[-2.497,-3.193],[-1.778,-2.548],[-1.848,-1.812],[-2.48,-1.325]],"v":[[-15.956,-2.474],[-4.937,-6.893],[6.735,-1.755],[12.368,9.059],[11.044,20.526],[5.037,11.902],[-0.292,5.357],[-6.545,0.776]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":30,"s":[{"i":[[3.904,0.985],[-4.273,-0.119],[-3.051,-3.041],[-0.671,-3.881],[1.679,-3.651],[1.806,2.552],[1.843,1.804],[2.47,1.309]],"o":[[2.677,-3.02],[4.285,0.094],[3.092,3.036],[0.656,3.898],[-2.497,-3.193],[-1.778,-2.548],[-1.848,-1.812],[-2.48,-1.325]],"v":[[-11.733,-16.371],[-0.714,-20.79],[10.958,-15.652],[16.591,-4.838],[15.267,6.629],[9.26,-1.995],[3.931,-8.54],[-2.322,-13.121]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":75,"s":[{"i":[[3.904,0.985],[-4.273,-0.119],[-3.051,-3.041],[-0.671,-3.881],[1.679,-3.651],[1.806,2.552],[1.843,1.804],[2.47,1.309]],"o":[[2.677,-3.02],[4.285,0.094],[3.092,3.036],[0.656,3.898],[-2.497,-3.193],[-1.778,-2.548],[-1.848,-1.812],[-2.48,-1.325]],"v":[[-11.733,-16.371],[-0.714,-20.79],[10.958,-15.652],[16.591,-4.838],[15.267,6.629],[9.26,-1.995],[3.931,-8.54],[-2.322,-13.121]],"c":true}]},{"t":80,"s":[{"i":[[3.904,0.985],[-4.273,-0.119],[-3.051,-3.041],[-0.671,-3.881],[1.679,-3.651],[1.806,2.552],[1.843,1.804],[2.47,1.309]],"o":[[2.677,-3.02],[4.285,0.094],[3.092,3.036],[0.656,3.898],[-2.497,-3.193],[-1.778,-2.548],[-1.848,-1.812],[-2.48,-1.325]],"v":[[-15.956,-2.474],[-4.937,-6.893],[6.735,-1.755],[12.368,9.059],[11.044,20.526],[5.037,11.902],[-0.292,5.357],[-6.545,0.776]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.067000003889,0.027000000898,0.086000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[112.887,31.951]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0.806,-0.356],[-0.272,0.808],[-0.429,0.72],[-1.226,1.085],[-3.332,0.582],[-3.14,-1.565],[-1.131,-1.275],[-0.389,-1.719],[1.365,-0.006],[1.152,-0.115],[2.18,-0.355],[2.471,-0.449],[0,0],[0.729,-0.147]],"o":[[-0.147,-0.878],[0.27,-0.826],[0.863,-1.45],[2.472,-2.141],[3.311,-0.574],[1.557,0.786],[1.109,1.306],[-1.782,-0.038],[-1.364,0.019],[-2.287,0.193],[-2.175,0.362],[0,0],[-0.69,0.116],[-0.719,0.171]],"v":[[-14.926,6.735],[-14.62,4.111],[-13.545,1.785],[-10.353,-1.986],[-1.491,-6.161],[8.609,-4.855],[12.714,-1.765],[15.074,2.735],[10.469,2.607],[6.739,2.793],[0.214,3.691],[-6.698,4.955],[-10.57,5.665],[-12.662,6.074]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.067000003889,0.027000000898,0.086000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[15.324,6.985]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"eye R closed","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":25,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":77,"s":[0]},{"t":78,"s":[100]}]},"p":{"a":0,"k":[114.362,76.994,0]},"a":{"a":0,"k":[50.774,38.871,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[-20.555,-27.688],[0.654,0.277],[36.721,-5.441],[0.259,1.49]],"o":[[0.426,0.573],[-8.064,-3.405],[-1.731,0.286],[-1.047,-6.006]],"v":[[36.722,13.173],[35.885,14.238],[-32.455,-1.642],[-36.101,-2.091]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-21.797,1.882],[0.486,-0.167],[10.773,18.711],[-0.274,-0.172]],"o":[[0.839,0.403],[-7.115,4.407],[-2.216,-3.849],[10.576,22.542]],"v":[[33.082,15.738],[32.009,18.317],[-35.783,4.352],[-34.917,-0.614]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":39,"s":[{"i":[[-1.986,-0.344],[0.306,-0.641],[14.716,47.168],[-0.844,-1.948]],"o":[[1.28,0.222],[-6.1,12.756],[-0.694,-2.225],[22.999,53.054]],"v":[[29.193,18.479],[27.865,22.677],[-36.796,1.496],[-34.86,-2.052]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":70,"s":[{"i":[[-1.986,-0.344],[0.306,-0.641],[14.716,47.168],[-0.844,-1.948]],"o":[[1.28,0.222],[-6.1,12.756],[-0.694,-2.225],[22.999,53.054]],"v":[[29.193,18.479],[27.865,22.677],[-36.796,1.496],[-34.86,-2.052]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[-21.797,1.882],[0.486,-0.167],[10.773,18.711],[-0.274,-0.172]],"o":[[0.839,0.403],[-7.115,4.407],[-2.216,-3.849],[10.576,22.542]],"v":[[33.082,15.738],[32.009,18.317],[-35.783,4.352],[-34.917,-0.614]],"c":true}]},{"t":80,"s":[{"i":[[-20.555,-27.688],[0.654,0.277],[36.721,-5.441],[0.259,1.49]],"o":[[0.426,0.573],[-8.064,-3.405],[-1.731,0.286],[-1.047,-6.006]],"v":[[36.722,13.173],[35.885,14.238],[-32.455,-1.642],[-36.101,-2.091]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[52.147,27.854]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[25,0],[-17.787,5.711],[-8.761,-9.663]],"o":[[-30.266,0],[25.543,-8.201],[9.605,10.594]],"v":[[7.029,22.538],[-27.346,-14.337],[35.529,-3.42]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[25,0],[-12.093,-5.6],[-17.585,11.825]],"o":[[-30.266,0],[19.383,13.979],[5.767,4.477]],"v":[[7.029,22.538],[-28.403,-8.57],[36.594,0.145]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":39,"s":[{"i":[[25,0],[-6.008,-17.689],[-20.726,25.677]],"o":[[-30.266,0],[12.799,37.684],[1.664,-2.062]],"v":[[7.029,22.538],[-28.396,-12.858],[37.732,3.956]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":70,"s":[{"i":[[25,0],[-6.008,-17.689],[-20.726,25.677]],"o":[[-30.266,0],[12.799,37.684],[1.664,-2.062]],"v":[[7.029,22.538],[-28.396,-12.858],[37.732,3.956]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[25,0],[-12.093,-5.6],[-17.585,11.825]],"o":[[-30.266,0],[19.383,13.979],[5.767,4.477]],"v":[[7.029,22.538],[-28.403,-8.57],[36.594,0.145]],"c":true}]},{"t":80,"s":[{"i":[[25,0],[-17.787,5.711],[-8.761,-9.663]],"o":[[-30.266,0],[25.543,-8.201],[9.605,10.594]],"v":[[7.029,22.538],[-27.346,-14.337],[35.529,-3.42]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.913999968884,0.925,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[45.383,39.464]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[50.329,41.267]},"a":{"a":0,"k":[50.329,41.267]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"eye R open","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[115.656,65.054,0]},"a":{"a":0,"k":[44.404,41.45,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.762},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[1.25,-1.938],[4.625,-0.063],[-11.5,3.438]],"o":[[-1.25,1.938],[-4.625,0.063],[11.5,-3.438]],"v":[[81.561,40.083],[69.748,42.896],[70.436,52.396]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.238},"t":23.199,"s":[{"i":[[0.692,-0.789],[2.948,0.007],[-5.577,0.064]],"o":[[-0.708,0.877],[-2.829,0.045],[7.701,-2.862]],"v":[[77.631,41.586],[70.57,42.943],[71.053,48.706]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":26.699,"s":[{"i":[[0.082,0.468],[1.112,0.084],[0.908,-3.629]],"o":[[-0.115,-0.284],[-0.862,0.025],[3.541,-2.232]],"v":[[73.329,43.23],[71.47,42.995],[69.374,52.487]],"c":true}]},{"i":{"x":0.833,"y":0.633},"o":{"x":0.167,"y":0},"t":79,"s":[{"i":[[0.082,0.468],[1.112,0.084],[0.908,-3.629]],"o":[[-0.115,-0.284],[-0.862,0.025],[3.541,-2.232]],"v":[[73.329,43.23],[71.47,42.995],[69.374,52.487]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.367},"t":81,"s":[{"i":[[0.692,-0.789],[2.948,0.007],[-5.577,0.064]],"o":[[-0.708,0.877],[-2.829,0.045],[7.701,-2.862]],"v":[[77.631,41.586],[70.57,42.943],[71.053,48.706]],"c":true}]},{"t":84,"s":[{"i":[[1.25,-1.938],[4.625,-0.063],[-11.5,3.438]],"o":[[-1.25,1.938],[-4.625,0.063],[11.5,-3.438]],"v":[[81.561,40.083],[69.748,42.896],[70.436,52.396]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.090196078431,0.176470588235,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":19,"s":[{"i":[[-6.094,5.6],[-17.909,-0.943],[-3.25,-5.875],[16.623,0.761]],"o":[[4.625,-4.25],[13.745,0.724],[2.961,5.352],[-22.114,-1.013]],"v":[[-28.276,0.524],[5.599,-10.079],[28.849,3.649],[3.812,-5.34]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":33,"s":[{"i":[[-1.248,8.182],[-21.537,-3.172],[0.675,-6.079],[16.623,0.761]],"o":[[1.125,-7.375],[19.921,2.934],[-0.675,6.079],[-22.114,-1.013]],"v":[[-29.026,-2.351],[7.729,-24.95],[29.599,3.649],[4.389,27.361]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":74,"s":[{"i":[[-1.248,8.182],[-21.537,-3.172],[0.675,-6.079],[16.623,0.761]],"o":[[1.125,-7.375],[19.921,2.934],[-0.675,6.079],[-22.114,-1.013]],"v":[[-29.026,-2.351],[7.729,-24.95],[29.599,3.649],[4.389,27.361]],"c":true}]},{"t":84,"s":[{"i":[[-6.094,5.6],[-17.909,-0.943],[-3.25,-5.875],[16.623,0.761]],"o":[[4.625,-4.25],[13.745,0.724],[2.961,5.352],[-22.114,-1.013]],"v":[[-28.276,0.524],[5.599,-10.079],[28.849,3.649],[3.812,-5.34]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[42.774,40.622]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.762},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[41.875,4.125],[-5.625,5.25],[-13.75,-0.5],[-5.25,-8.375],[8,0.125],[-0.5,-6.5],[-31.5,6.875]],"o":[[-41.875,-4.125],[5.625,-5.25],[13.75,0.5],[-6.875,-13],[-8,-0.125],[0.5,6.5],[31.5,-6.875]],"v":[[51.373,11.896],[10.248,44.646],[45.491,30.825],[69.123,47.896],[41.784,34.116],[8.248,46.646],[57.873,73.646]],"c":true}]},{"i":{"x":0.833,"y":0.762},"o":{"x":0.167,"y":0.238},"t":23.199,"s":[{"i":[[41.875,4.125],[-5.625,5.25],[-24.89,-0.067],[-3.768,-8.65],[12.124,0.214],[0.475,-3.189],[-31.5,6.875]],"o":[[-41.875,-4.125],[5.625,-5.25],[21.279,1.376],[-5.609,-1.715],[-14.124,-1.368],[-0.961,6.448],[31.5,-6.875]],"v":[[51.373,11.896],[10.248,44.646],[46.529,25.979],[73.579,48.269],[42.759,45.329],[8.248,46.646],[57.873,73.646]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.238},"t":26.699,"s":[{"i":[[41.875,4.125],[-4.175,6.463],[-22.366,-1.384],[2.66,-9.833],[17.895,0.338],[-0.5,-6.5],[-31.5,6.875]],"o":[[-41.875,-4.125],[5.701,-8.825],[19.013,1.136],[-2.056,3.375],[-22.692,-3.108],[0.5,6.5],[31.5,-6.875]],"v":[[51.373,11.896],[10.248,44.646],[47.459,21.34],[70.49,48.244],[44.191,57.316],[8.248,46.646],[57.873,73.646]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":33,"s":[{"i":[[41.875,4.125],[-5.625,5.25],[-31.574,-2.328],[2.108,-9.741],[15.633,-1.377],[-0.5,-6.5],[-31.5,6.875]],"o":[[-41.875,-4.125],[5.625,-5.25],[24.637,1.817],[-2.406,7.753],[-20.224,1.781],[0.5,6.5],[31.5,-6.875]],"v":[[51.373,11.896],[10.248,44.646],[48.499,15.613],[71.952,48.615],[46.789,68.732],[8.248,46.646],[57.873,73.646]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":74,"s":[{"i":[[41.875,4.125],[-5.625,5.25],[-31.574,-2.328],[2.108,-9.741],[15.633,-1.377],[-0.5,-6.5],[-31.5,6.875]],"o":[[-41.875,-4.125],[5.625,-5.25],[24.637,1.817],[-2.406,7.753],[-20.224,1.781],[0.5,6.5],[31.5,-6.875]],"v":[[51.373,11.896],[10.248,44.646],[48.499,15.613],[71.952,48.615],[46.789,68.732],[8.248,46.646],[57.873,73.646]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79,"s":[{"i":[[41.875,4.125],[-4.425,6.295],[-22.366,-1.384],[3.223,-5.158],[17.895,0.338],[-0.5,-6.5],[-31.5,6.875]],"o":[[-41.875,-4.125],[4.794,-6.82],[22.57,1.897],[-2.056,3.375],[-22.692,-3.108],[0.5,6.5],[31.5,-6.875]],"v":[[51.373,11.896],[10.248,44.646],[47.238,21.797],[70.49,48.244],[44.156,54.518],[8.248,46.646],[57.873,73.646]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":82,"s":[{"i":[[41.875,4.125],[-5.625,5.25],[-21.281,-1.086],[-4.352,-8.542],[10.498,0.179],[-0.5,-6.5],[-31.5,6.875]],"o":[[-41.875,-4.125],[5.625,-5.25],[21.05,1.074],[-3.375,-4.354],[-20.928,1.053],[0.5,6.5],[31.5,-6.875]],"v":[[51.373,11.896],[10.248,44.646],[45.756,27.766],[74.451,49.491],[45.494,40.579],[9.077,48.743],[57.873,73.646]],"c":true}]},{"t":84,"s":[{"i":[[41.875,4.125],[-5.625,5.25],[-13.75,-0.5],[-5.25,-8.375],[8,0.125],[-0.5,-6.5],[-31.5,6.875]],"o":[[-41.875,-4.125],[5.625,-5.25],[13.75,0.5],[-6.875,-13],[-8,-0.125],[0.5,6.5],[31.5,-6.875]],"v":[[51.373,11.896],[10.248,44.646],[45.491,30.825],[69.123,47.896],[41.784,34.116],[8.248,46.646],[57.873,73.646]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.913725490196,0.925490196078,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1.604,-3.044],[2.992,1.578],[-1.605,3.044],[-2.992,-1.578]],"o":[[-1.604,3.044],[-2.993,-1.577],[1.605,-3.044],[2.993,1.577]],"v":[[5.419,2.856],[-2.905,5.512],[-5.418,-2.856],[2.906,-5.511]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.987999949736,0.987999949736,0.987999949736,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[55.961,41.093]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[3.15,-5.978],[5.875,3.098],[-3.151,5.978],[-5.875,-3.097]],"o":[[-3.151,5.977],[-5.876,-3.098],[3.151,-5.977],[5.876,3.098]],"v":[[10.64,5.608],[-5.705,10.823],[-10.639,-5.609],[5.705,-10.823]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[42.998,45.646]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[4.638,-8.796],[8.648,4.559],[-4.638,8.797],[-8.647,-4.559]],"o":[[-4.638,8.796],[-8.647,-4.558],[4.637,-8.796],[8.648,4.558]],"v":[[15.657,8.254],[-8.397,15.927],[-15.657,-8.254],[8.396,-15.927]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.224000010771,0.741000007181,0.969000004787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[42.998,45.646]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.736,8.093],[-21.434,-3.774],[0.675,-6.078],[19.841,1.319]],"o":[[1.066,-4.972],[21.993,3.874],[-0.675,6.08],[-19.556,-1.301]],"v":[[-30.552,-1.841],[8.287,-25.121],[31.614,2.62],[3.278,27.576]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.987999949736,0.987999949736,0.987999949736,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[43.885,40.407]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":"eye L","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[12.887,32.795,0]},"a":{"a":0,"k":[20.137,19.359,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[10.468,4.469],[-0.565,3.893],[-2.429,-23.587],[0.491,0.77]],"o":[[-8.042,-3.442],[1.037,-7.151],[0.094,0.908],[-5.974,-9.366]],"v":[[-5.84,-5.944],[-17.423,-10.526],[17.894,16.352],[16.298,16.908]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[21.507,17.966]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[3.417,7.333],[10,-0.334],[0,-3]],"o":[[8,3],[-4.568,-9.805],[-10,0.333],[0,3]],"v":[[10.292,12.333],[16.292,3.333],[-7.708,-14.999],[-19.708,-9.667]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.913999968884,0.925,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[19.958,24.897]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[16.031,0],[1.005,2.494],[0.257,-1.046],[-4.474,-1.491]],"o":[[3,-10],[-5.985,0],[-0.402,-0.999],[-1.169,4.769],[12,4]],"v":[[12.383,15.782],[-0.909,-11.801],[-11.402,-14.783],[-13.531,-14.606],[-10.909,-1.801]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[26.159,16.032]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":"nose","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[44.266,62.819,0]},"a":{"a":0,"k":[31.895,25.428,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.98,-5.569],[-3.148,-1.474],[-4.21,4.321],[5.95,1.986]],"o":[[-5.326,-10.492],[-2.699,7.588],[3.15,1.475],[4.124,-4.234],[-6.688,-2.232]],"v":[[-0.87,-3.81],[-16.367,-6.586],[-6.793,12.827],[14.942,9.459],[12.32,-3.394]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.517999985639,0.097999999102,0.317999985639,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.847000002394,0.39199999641,0.611999990426,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[31.566,26.802]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-2.305,1.963],[-13.254,-8.275],[0.668,0.234],[7.118,-4.211]],"o":[[6.574,-5.61],[0.603,0.376],[-14.536,-5.112],[-2.707,1.602]],"v":[[-14.745,0.825],[16.448,1.811],[15.935,2.881],[-11.797,4.862]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[30.726,14.376]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"mouth","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[35.082,113.25,0]},"a":{"a":0,"k":[38.091,44.721,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":35,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":45,"s":[94,106,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":80,"s":[94,106,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":90,"s":[110,90,100]},{"t":105,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[10.203,6.005],[-1.149,-0.299],[-3.818,2.432],[-2.016,-2.478]],"o":[[-1.032,-0.607],[4.568,1.192],[2.753,-1.665],[3.343,4.11]],"v":[[-11.311,1.249],[-10.527,-0.643],[2.089,-2.645],[9,-4.776]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[3.837,-13.554],[-2.5,7.537],[-3.548,3.386],[-0.991,-2.915]],"o":[[-0.484,0.526],[1.291,-3.892],[2.753,-1.665],[1.401,2.886]],"v":[[-6.592,18.515],[-8.145,11.669],[1.698,-3.279],[9,-4.776]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[3.484,-13.441],[-0.69,4.518],[-3.499,3.559],[-0.805,-2.995]],"o":[[-0.385,0.731],[1.561,-4.328],[2.753,-1.665],[1.05,2.665]],"v":[[-4.552,17.288],[-6.529,10.799],[1.627,-3.394],[9,-4.776]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[2.416,-17.375],[-0.534,6.157],[-3.39,3.943],[-0.393,-3.17]],"o":[[-0.165,1.186],[0.538,-6.206],[2.753,-1.665],[0.269,2.173]],"v":[[-1.405,18.556],[-4.664,13.52],[1.47,-3.649],[9,-4.776]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[2.416,-17.375],[-0.534,6.157],[-3.39,3.943],[-0.393,-3.17]],"o":[[-0.165,1.186],[0.538,-6.206],[2.753,-1.665],[0.269,2.173]],"v":[[-1.405,18.556],[-4.664,13.52],[1.47,-3.649],[9,-4.776]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[3.484,-13.441],[-0.69,4.518],[-3.499,3.559],[-0.805,-2.995]],"o":[[-0.385,0.731],[1.561,-4.328],[2.753,-1.665],[1.05,2.665]],"v":[[-4.552,17.288],[-6.529,10.799],[1.627,-3.394],[9,-4.776]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[3.837,-13.554],[-2.5,7.537],[-3.548,3.386],[-0.991,-2.915]],"o":[[-0.484,0.526],[1.291,-3.892],[2.753,-1.665],[1.401,2.886]],"v":[[-6.592,18.515],[-8.145,11.669],[1.698,-3.279],[9,-4.776]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[10.203,6.005],[-1.149,-0.299],[-3.818,2.432],[-2.016,-2.478]],"o":[[-1.032,-0.607],[4.568,1.192],[2.753,-1.665],[3.343,4.11]],"v":[[-11.311,1.249],[-10.527,-0.643],[2.089,-2.645],[9,-4.776]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[10.203,6.005],[-1.149,-0.299],[-3.818,2.432],[-2.016,-2.478]],"o":[[-1.032,-0.607],[4.568,1.192],[2.753,-1.665],[3.343,4.11]],"v":[[-7.877,1.746],[-7.092,-0.146],[2.089,-2.645],[9,-4.776]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[{"i":[[10.203,6.005],[-1.149,-0.299],[-3.818,2.432],[-2.016,-2.478]],"o":[[-1.032,-0.607],[4.568,1.192],[2.753,-1.665],[3.343,4.11]],"v":[[-11.311,1.249],[-10.527,-0.643],[2.089,-2.645],[9,-4.776]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":90,"s":[{"i":[[10.203,6.005],[-1.149,-0.299],[-3.818,2.432],[-2.016,-2.478]],"o":[[-1.032,-0.607],[4.568,1.192],[2.753,-1.665],[3.343,4.11]],"v":[[-11.311,1.249],[-10.527,-0.643],[2.089,-2.645],[9,-4.776]],"c":true}]},{"t":105,"s":[{"i":[[10.203,6.005],[-1.149,-0.299],[-3.818,2.432],[-2.016,-2.478]],"o":[[-1.032,-0.607],[4.568,1.192],[2.753,-1.665],[3.343,4.11]],"v":[[-11.311,1.249],[-10.527,-0.643],[2.089,-2.645],[9,-4.776]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[35.864,8.423]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-15.394,-15.632],[2.347,0.88],[5.217,0.4],[-0.036,0],[-7.125,-0.125],[1.765,3.851],[-0.748,-1]],"o":[[1.838,1.866],[-2.957,-0.263],[-1.83,-0.101],[-3.682,0.04],[-4.514,-2.527],[-2.247,-4.915],[1.624,2.171]],"v":[[9.884,7.541],[7.338,11.536],[-4.069,10.146],[-8.04,10.086],[3.402,6.903],[-8.83,-7.224],[-8.257,-11.416]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[-16.24,-13.855],[1.025,-0.204],[2.047,0.45],[1.151,0.735],[1.171,1.235],[1.765,3.851],[-0.748,-1]],"o":[[5.159,2.307],[-1.025,0.204],[-1.85,-0.406],[-0.778,-0.497],[-3.141,-1.765],[-2.247,-4.915],[1.624,2.171]],"v":[[14.294,8.435],[18.033,11.47],[12.523,11.037],[6.857,8.314],[2.836,5.002],[-6.558,-6.601],[-5.665,-10.675]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[-18.86,-8.351],[-1.325,-0.349],[5.852,-0.709],[4.242,1.826],[1.256,1.441],[1.829,3.821],[0.032,-0.225]],"o":[[15.448,3.672],[1.695,0.404],[-5.839,0.741],[-3.983,-1.684],[-2.935,-2.249],[-1.645,-3.436],[1.624,2.171]],"v":[[27.955,11.204],[51.167,11.266],[37.552,15.631],[19.983,12.914],[10.717,7.368],[0.481,-4.673],[2.803,-7.567]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[-20.859,-4.056],[-3.088,-0.458],[8.57,-2.196],[8.307,3.026],[1.32,1.597],[1.765,3.851],[-0.748,-1]],"o":[[21.544,4.19],[3.735,0.554],[-8.209,2.104],[-6.47,-2.357],[-2.78,-2.611],[-2.247,-4.915],[1.624,2.171]],"v":[[38.201,13.282],[74.898,10.233],[56.324,19.076],[30.196,17.492],[15.629,9.015],[4.642,-4.356],[8.275,-8.787]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[-20.859,-4.056],[-3.088,-0.458],[8.57,-2.196],[8.307,3.026],[1.32,1.597],[1.765,3.851],[-0.748,-1]],"o":[[21.544,4.19],[3.735,0.554],[-8.209,2.104],[-6.47,-2.357],[-2.78,-2.611],[-2.247,-4.915],[1.624,2.171]],"v":[[38.201,13.282],[74.898,10.233],[56.324,19.076],[30.196,17.492],[15.629,9.015],[4.642,-4.356],[8.275,-8.787]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-18.86,-8.351],[-1.325,-0.349],[5.852,-0.709],[4.242,1.826],[1.256,1.441],[1.829,3.821],[0.032,-0.225]],"o":[[15.448,3.672],[1.695,0.404],[-5.839,0.741],[-3.983,-1.684],[-2.935,-2.249],[-1.645,-3.436],[1.624,2.171]],"v":[[27.955,11.204],[51.167,11.266],[37.552,15.631],[19.983,12.914],[10.717,7.368],[0.481,-4.673],[2.803,-7.567]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[-16.24,-13.855],[1.025,-0.204],[2.047,0.45],[1.151,0.735],[1.171,1.235],[1.765,3.851],[-0.748,-1]],"o":[[5.159,2.307],[-1.025,0.204],[-1.85,-0.406],[-0.778,-0.497],[-3.141,-1.765],[-2.247,-4.915],[1.624,2.171]],"v":[[14.294,8.435],[18.033,11.47],[12.523,11.037],[6.857,8.314],[2.836,5.002],[-6.558,-6.601],[-5.665,-10.675]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[-15.394,-15.632],[2.347,0.88],[5.217,0.4],[-0.036,0],[-7.125,-0.125],[1.765,3.851],[-0.748,-1]],"o":[[1.838,1.866],[-2.957,-0.263],[-1.83,-0.101],[-3.682,0.04],[-4.514,-2.527],[-2.247,-4.915],[1.624,2.171]],"v":[[9.884,7.541],[7.338,11.536],[-4.069,10.146],[-8.04,10.086],[3.402,6.903],[-8.83,-7.224],[-8.257,-11.416]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":45,"s":[{"i":[[-5.096,-8.374],[2.347,0.88],[1.609,-0.119],[-0.036,0],[-7.125,-0.125],[0.611,4.46],[-0.419,-1.176]],"o":[[1.362,2.237],[-2.957,-0.263],[-1.83,-0.101],[-3.682,0.04],[-2.743,-2.686],[-0.734,-5.354],[1.376,3.858]],"v":[[-0.002,4.819],[-2.548,8.814],[-8.021,9.11],[-11.992,9.05],[-7.704,3.855],[-12.193,-7.774],[-8.257,-11.416]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[{"i":[[-15.394,-15.632],[2.347,0.88],[5.217,0.4],[-0.036,0],[-7.125,-0.125],[1.765,3.851],[-0.748,-1]],"o":[[1.838,1.866],[-2.957,-0.263],[-1.83,-0.101],[-3.682,0.04],[-4.514,-2.527],[-2.247,-4.915],[1.624,2.171]],"v":[[9.884,7.541],[7.338,11.536],[-4.069,10.146],[-8.04,10.086],[3.402,6.903],[-8.83,-7.224],[-8.257,-11.416]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":80,"s":[{"i":[[2.831,-14.008],[4.18,1.205],[2.903,3.906],[-1.337,-1.27],[6.062,14.467],[6.231,4.347],[-1.228,-0.224]],"o":[[-1.105,5.467],[-4.18,-1.205],[-1.125,-1.513],[12.63,12.003],[-1.746,-4.167],[-4.432,-3.092],[10.781,1.969]],"v":[[16.69,17.355],[4.53,23.304],[-5.988,15.963],[-8.04,10.086],[10.483,7.764],[-6.757,-5.861],[-9.015,-10.391]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[-0.909,-6.876],[2.955,-0.693],[2.518,2.319],[-0.686,-0.635],[2.939,7.703],[3.998,4.099],[-0.988,-0.612]],"o":[[0.357,2.702],[-2.955,0.693],[-2.518,-2.319],[5.253,4.075],[-3.749,-9.825],[-3.34,-4.004],[10.418,3.126]],"v":[[11.518,11.956],[5.934,17.42],[-5.311,14.46],[-8.04,10.086],[7.161,9.489],[-7.516,-4.502],[-5.538,-6.981]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":90,"s":[{"i":[[-15.394,-15.632],[2.347,0.88],[5.217,0.4],[-0.036,0],[-7.125,-0.125],[1.765,3.851],[-0.748,-1]],"o":[[1.838,1.866],[-2.957,-0.263],[-1.83,-0.101],[-3.682,0.04],[-4.514,-2.527],[-2.247,-4.915],[1.624,2.171]],"v":[[9.884,7.541],[7.338,11.536],[-4.069,10.146],[-8.04,10.086],[3.402,6.903],[-8.83,-7.224],[-8.257,-11.416]],"c":true}]},{"t":105,"s":[{"i":[[-15.394,-15.632],[2.347,0.88],[5.217,0.4],[-0.036,0],[-7.125,-0.125],[1.765,3.851],[-0.748,-1]],"o":[[1.838,1.866],[-2.957,-0.263],[-1.83,-0.101],[-3.682,0.04],[-4.514,-2.527],[-2.247,-4.915],[1.624,2.171]],"v":[[9.884,7.541],[7.338,11.536],[-4.069,10.146],[-8.04,10.086],[3.402,6.903],[-8.83,-7.224],[-8.257,-11.416]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[25.326,31.486]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":0,"s":[{"i":[[10.258,-3.375],[-9.607,-1.68],[2.975,0.536],[-6.896,2.144],[0.601,-1.029]],"o":[[0.521,-0.026],[1.134,0.198],[-14.084,-2.525],[5.564,-1.249],[2.187,-3.745]],"v":[[-2.798,0.26],[8.057,10.161],[6.826,14.91],[-4.012,-4.59],[4.286,-11.701]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[-0.116,-6.751],[-10.685,-1.372],[2.996,0.317],[-2.222,4.723],[0.22,-2.367]],"o":[[1.846,2.009],[1.14,0.152],[-14.178,-1.494],[0.832,-5.579],[1.4,0.476]],"v":[[-1.429,-0.328],[13.527,8.814],[12.322,13.535],[-5.494,-3.009],[0.533,-15]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[-4.282,-5.58],[-12.071,-0.976],[3.023,0.036],[3.787,8.039],[-0.269,-4.088]],"o":[[3.55,4.626],[1.147,0.093],[-14.299,-0.169],[-5.251,-11.146],[0.388,5.903]],"v":[[-1.669,-0.799],[20.559,7.081],[19.39,11.767],[-7.4,-0.975],[-4.293,-19.242]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[-4.282,-5.58],[-12.071,-0.976],[3.023,0.036],[3.787,8.039],[-0.269,-4.088]],"o":[[3.55,4.626],[1.147,0.093],[-14.299,-0.169],[-5.251,-11.146],[0.388,5.903]],"v":[[-1.669,-0.799],[20.559,7.081],[19.39,11.767],[-7.4,-0.975],[-4.293,-19.242]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.116,-6.751],[-10.685,-1.372],[2.996,0.317],[-2.222,4.723],[0.22,-2.367]],"o":[[1.846,2.009],[1.14,0.152],[-14.178,-1.494],[0.832,-5.579],[1.4,0.476]],"v":[[-1.429,-0.328],[13.527,8.814],[12.322,13.535],[-5.494,-3.009],[0.533,-15]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":35,"s":[{"i":[[10.258,-3.375],[-9.607,-1.68],[2.975,0.536],[-6.896,2.144],[0.601,-1.029]],"o":[[0.521,-0.026],[1.134,0.198],[-14.084,-2.525],[5.564,-1.249],[2.187,-3.745]],"v":[[-2.798,0.26],[8.057,10.161],[6.826,14.91],[-4.012,-4.59],[4.286,-11.701]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":45,"s":[{"i":[[10.258,-3.375],[-9.607,-1.68],[2.975,0.536],[-6.896,2.144],[0.601,-1.029]],"o":[[0.521,-0.026],[1.134,0.198],[-14.084,-2.525],[5.564,-1.249],[2.187,-3.745]],"v":[[-6.155,3.293],[8.057,10.161],[6.826,14.91],[-7.369,-1.558],[3.837,-7.347]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":60,"s":[{"i":[[10.258,-3.375],[-9.607,-1.68],[2.975,0.536],[-6.896,2.144],[0.601,-1.029]],"o":[[0.521,-0.026],[1.134,0.198],[-14.084,-2.525],[5.564,-1.249],[2.187,-3.745]],"v":[[-2.798,0.26],[8.057,10.161],[6.826,14.91],[-4.012,-4.59],[4.286,-11.701]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":80,"s":[{"i":[[1.393,-7.271],[-4.657,-2.941],[3.574,2.066],[-2.086,2.974],[-0.436,-1.356]],"o":[[-0.312,1.629],[0.973,0.615],[-12.387,-7.162],[2.086,-2.974],[0.436,1.356]],"v":[[1.159,-1.943],[12.854,8.333],[10.121,11.238],[-4.012,-4.59],[4.286,-11.701]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":90,"s":[{"i":[[10.258,-3.375],[-9.607,-1.68],[2.956,0.631],[-5.926,-1.672],[0.601,-1.029]],"o":[[0.521,-0.026],[1.134,0.198],[-18.736,-4.002],[5.532,1.561],[2.187,-3.745]],"v":[[-5.256,-0.876],[8.057,10.161],[6.826,14.91],[-7.343,-6.875],[4.286,-11.701]],"c":true}]},{"t":105,"s":[{"i":[[10.258,-3.375],[-9.607,-1.68],[2.975,0.536],[-6.896,2.144],[0.601,-1.029]],"o":[[0.521,-0.026],[1.134,0.198],[-14.084,-2.525],[5.564,-1.249],[2.187,-3.745]],"v":[[-2.798,0.26],[8.057,10.161],[6.826,14.91],[-4.012,-4.59],[4.286,-11.701]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[11.157,15.696]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 6","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-2.036,-20.327],[-8.366,-0.032],[-18.363,1.556],[10.924,13.307]],"o":[[2.383,23.785],[6.825,8.911],[18.363,-1.556],[-10.924,-13.307]],"v":[[3.259,1.966],[29.743,25.343],[73.71,48.538],[83.32,23.548]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.913725490196,0.925490196078,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":0,"s":[{"i":[[0,0],[-8.194,0.491],[0,0]],"o":[[0,0],[3.86,-0.232],[0,0]],"v":[[-6.773,4.761],[0.981,-2.393],[6.728,3.902]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[2.201,1.759],[-4.768,0.77],[0,0]],"o":[[-2.201,-1.759],[3.818,-0.616],[0,0]],"v":[[-4.836,5.261],[-5.394,-1.83],[2.29,4.84]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[2.054,1.642],[-4.997,0.751],[0,0]],"o":[[-2.054,-1.642],[3.821,-0.591],[0,0]],"v":[[-3.153,2.04],[-4.967,-1.868],[2.587,4.777]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.054,1.642],[-4.997,0.751],[0,0]],"o":[[-2.054,-1.642],[3.821,-0.591],[0,0]],"v":[[-1.748,-0.634],[-4.967,-1.868],[2.587,4.777]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[2.201,1.759],[-4.768,0.77],[0,0]],"o":[[-2.201,-1.759],[3.818,-0.616],[0,0]],"v":[[-4.836,5.261],[-5.394,-1.83],[2.29,4.84]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":35,"s":[{"i":[[0,0],[-8.194,0.491],[0,0]],"o":[[0,0],[3.86,-0.232],[0,0]],"v":[[-6.773,4.761],[0.981,-2.393],[6.728,3.902]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":45,"s":[{"i":[[0,0],[-2.704,-0.654],[0,0]],"o":[[0,0],[3.759,0.909],[0,0]],"v":[[-4.962,3.552],[-6.343,-2.288],[-1.444,2.223]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":60,"s":[{"i":[[0,0],[-8.194,0.491],[0,0]],"o":[[0,0],[3.86,-0.232],[0,0]],"v":[[-6.773,4.761],[0.981,-2.393],[6.728,3.902]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":80,"s":[{"i":[[10.384,13.247],[3.772,12.379],[4.779,-13.11]],"o":[[-4.157,-5.303],[-1.127,-3.699],[-0.564,1.548]],"v":[[-3.763,9.055],[0.656,-7.872],[14.766,13.782]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":90,"s":[{"i":[[0,0],[-8.194,0.491],[0,0]],"o":[[0,0],[3.86,-0.232],[0,0]],"v":[[-6.773,4.761],[0.981,-2.393],[6.728,3.902]],"c":true}]},{"t":105,"s":[{"i":[[0,0],[-8.194,0.491],[0,0]],"o":[[0,0],[3.86,-0.232],[0,0]],"v":[[-6.773,4.761],[0.981,-2.393],[6.728,3.902]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.447000002394,0.102000000898,0.102000000898,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[23.782,36.03]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[5.417,-5.639],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[4.656,-4.71],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-6.153,11.467],[-5.353,-8.396],[8.798,-11.109],[-1.774,-4.906],[-0.14,8.869],[7.67,10.739],[9.92,13.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[1.752,-1.585],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[2.143,-1.157],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-6.153,11.467],[-5.353,-8.396],[2.611,-8.796],[-1.774,-4.906],[-0.14,8.869],[7.67,10.739],[9.92,13.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[2.003,-1.863],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[2.316,-1.401],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-5.402,11.469],[-5.353,-8.396],[3.035,-8.955],[-1.774,-4.906],[-0.14,8.869],[7.67,10.739],[9.92,13.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[2.129,-2.002],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[2.402,-1.522],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-4.026,9.844],[-5.353,-8.396],[3.247,-9.034],[-1.774,-4.906],[0.861,6.12],[7.67,10.739],[9.92,13.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[2.255,-2.141],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[2.488,-1.644],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-2.65,8.22],[-5.353,-8.396],[3.459,-9.113],[-1.774,-4.906],[-0.14,8.869],[7.67,10.739],[9.92,13.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[2.255,-2.141],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[2.488,-1.644],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-0.896,6.642],[-5.353,-8.396],[3.459,-9.113],[-1.774,-4.906],[1.582,6.971],[7.67,10.739],[9.92,13.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":29,"s":[{"i":[[2.9,2.721],[-6.346,6.521],[-0.083,-4.777],[2.129,-2.002],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[2.402,-1.522],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-4.018,9.22],[-5.353,-8.396],[3.247,-9.034],[-1.774,-4.906],[1.151,7.446],[7.67,10.739],[9.92,13.592]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[1.752,-1.585],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[2.143,-1.157],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-6.153,11.467],[-5.353,-8.396],[2.611,-8.796],[-1.774,-4.906],[-0.14,8.869],[7.67,10.739],[9.92,13.592]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[5.417,-5.639],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[4.656,-4.71],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-6.153,11.467],[-5.353,-8.396],[8.798,-11.109],[-1.774,-4.906],[-0.14,8.869],[7.67,10.739],[9.92,13.592]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":45,"s":[{"i":[[7.665,6.41],[-6.346,6.521],[-0.083,-4.777],[5.417,-5.639],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[4.656,-4.71],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-10.108,16.681],[-5.181,-5.699],[8.798,-11.109],[-1.602,-2.208],[-4.095,14.083],[3.369,9.254],[5.619,12.107]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[5.417,-5.639],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[4.656,-4.71],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-6.153,11.467],[-5.353,-8.396],[8.798,-11.109],[-1.774,-4.906],[-0.14,8.869],[7.67,10.739],[9.92,13.592]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":80,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-3.128,-3.612],[3.79,-4.411],[-7.455,-5.647],[-2.375,-0.833],[1,0.057]],"o":[[-5.508,-4.522],[1.627,-2.692],[2.224,2.568],[-3.982,4.06],[2.009,1.522],[4.029,1.254],[-4.481,-0.255]],"v":[[-3.872,19.16],[-7.333,-2.337],[13.194,-2.767],[-3.754,1.154],[2.141,16.562],[9.951,18.431],[12.201,21.284]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":90,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[5.417,-5.639],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[4.656,-4.71],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-8.475,11.923],[-7.675,-7.941],[8.798,-11.109],[-3.985,-4.431],[-2.351,9.344],[7.67,10.739],[9.92,13.592]],"c":true}]},{"t":105,"s":[{"i":[[4.772,4.418],[-6.346,6.521],[-0.083,-4.777],[5.417,-5.639],[-6.26,-2.111],[-2.375,-0.833],[1,0.057]],"o":[[-4.351,-4.234],[4.656,-4.71],[0.074,4.288],[-3.982,4.06],[2.39,0.799],[4.029,1.254],[-4.481,-0.255]],"v":[[-6.153,11.467],[-5.353,-8.396],[8.798,-11.109],[-1.774,-4.906],[-0.14,8.869],[7.67,10.739],[9.92,13.592]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[14.643,40.835]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[17.694,45.337],"to":[3.346,-4.578],"ti":[-3.346,4.578]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.167,"y":0.167},"t":10,"s":[37.77,17.871],"to":[0,0],"ti":[0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":25,"s":[37.77,17.871],"to":[-3.346,4.578],"ti":[3.346,-4.578]},{"t":35,"s":[17.694,45.337]}]},"a":{"a":0,"k":[17.694,45.337]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 7","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-4.518,-5.656],[-1.641,-2.504],[0.211,0.215],[3.144,3.906]],"o":[[0,0],[0.673,1.12],[1.554,1.575],[-4.909,-5.93]],"v":[[0.919,-2.758],[2.987,4.551],[4.897,6.838],[-1.543,4.12]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[-4.518,-5.656],[-2.12,-1.954],[0.211,0.215],[3.144,3.906]],"o":[[0,0],[2.12,1.954],[1.554,1.575],[-4.909,-5.93]],"v":[[-1.566,-5.52],[3.366,3.802],[10.009,8.61],[-1.543,4.12]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[-4.518,-5.656],[-2.12,-1.954],[0.211,0.215],[3.144,3.906]],"o":[[0,0],[2.12,1.954],[1.554,1.575],[-4.909,-5.93]],"v":[[-1.566,-5.52],[3.366,3.802],[10.009,8.61],[-1.543,4.12]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[-4.518,-5.656],[-1.641,-2.504],[0.211,0.215],[3.144,3.906]],"o":[[0,0],[0.673,1.12],[1.554,1.575],[-4.909,-5.93]],"v":[[0.919,-2.758],[2.987,4.551],[4.897,6.838],[-1.543,4.12]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":45,"s":[{"i":[[-2.268,-2.926],[-1.579,-4.511],[0.211,0.215],[5.102,1.75]],"o":[[0,0],[0.673,1.12],[1.554,1.575],[-5.102,-1.75]],"v":[[-2.921,-1.106],[2.987,4.551],[2.519,5.994],[-4.294,2.955]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":60,"s":[{"i":[[-4.518,-5.656],[-1.641,-2.504],[0.211,0.215],[3.144,3.906]],"o":[[0,0],[0.673,1.12],[1.554,1.575],[-4.909,-5.93]],"v":[[0.919,-2.758],[2.987,4.551],[4.897,6.838],[-1.543,4.12]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":80,"s":[{"i":[[-4.518,-5.656],[-1.641,-2.504],[0.211,0.215],[3.144,3.906]],"o":[[0,0],[0.673,1.12],[1.554,1.575],[-4.909,-5.93]],"v":[[-0.536,4.221],[1.533,11.53],[3.443,13.817],[-2.997,11.099]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":90,"s":[{"i":[[-4.518,-5.656],[-1.641,-2.504],[0.211,0.215],[3.144,3.906]],"o":[[0,0],[0.673,1.12],[1.554,1.575],[-4.909,-5.93]],"v":[[0.919,-2.758],[2.987,4.551],[4.897,6.838],[-1.543,4.12]],"c":true}]},{"t":105,"s":[{"i":[[-4.518,-5.656],[-1.641,-2.504],[0.211,0.215],[3.144,3.906]],"o":[[0,0],[0.673,1.12],[1.554,1.575],[-4.909,-5.93]],"v":[[0.919,-2.758],[2.987,4.551],[4.897,6.838],[-1.543,4.12]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[17.84,55.727]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":"ear R","parent":28,"sr":1,"ks":{"p":{"a":0,"k":[326.561,103.226,0]},"a":{"a":0,"k":[73.794,102.718,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-8.384,-0.988],[-2.33,-63.067],[4,9]],"o":[[8.514,-14.316],[8.384,0.987],[0.612,16.55],[0,0]],"v":[[-29.691,-13.157],[3.189,-46.225],[29.079,30.663],[7.292,28.288]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[44.691,62.213]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-14,-17]],"o":[[15,3],[0,0]],"v":[[-2.5,-38.5],[1.5,38.5]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[52.483,55.001]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.144,-20.125],[-8.852,-5.401],[-1.856,11.197],[3.614,17.643]],"o":[[4.544,4.964],[-4.143,20.124],[8.85,5.4],[1.857,-11.197],[-3.613,-17.643]],"v":[[-10.756,-46.593],[-7.601,-10.445],[-5.412,33.083],[12.407,35.396],[6.71,-17.301]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.317999985639,0.250999989229,0.340999977261,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[60.825,61.569]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[5.431,8.375],[-8.383,-0.987],[-2.33,-63.067]],"o":[[8.528,-10.901],[8.383,0.988],[1.308,35.402]],"v":[[-30.039,-22.078],[5.029,-56.158],[28.732,21.742]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541000007181,0.455000005984,0.573000021542,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[45.039,71.133]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":27,"ty":3,"nm":"Null 2","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":12},"p":{"a":0,"k":[287.811,339.011,0]},"a":{"a":0,"k":[50,50,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":5,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":45,"s":[103,97,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":75,"s":[97,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":95,"s":[103,97,100]},{"t":120,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"head","parent":27,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.569],"y":[0.038]},"t":5,"s":[-12]},{"i":{"x":[0.415],"y":[1.029]},"o":{"x":[0.333],"y":[0]},"t":45,"s":[-22]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":75,"s":[-12]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":95,"s":[-22]},{"t":120,"s":[-12]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.569,"y":0},"t":2,"s":[73.83,-76.055,0],"to":[-8.534,0.03,0],"ti":[0.291,1.77,0]},{"i":{"x":0.415,"y":1},"o":{"x":0.333,"y":0},"t":42,"s":[59.761,-67.09,0],"to":[-0.291,-1.77,0],"ti":[-7.931,4.822,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":72,"s":[72.084,-86.674,0],"to":[1.59,-0.967,0],"ti":[-0.291,-1.77,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":92,"s":[59.761,-67.09,0],"to":[0.291,1.77,0],"ti":[1.861,-0.007,0]},{"t":117,"s":[73.83,-76.055,0]}]},"a":{"a":0,"k":[175.828,150.007,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":0,"s":[{"i":[[-7.757,37.124],[8.483,12.743],[33.421,-43.443],[21.766,-1.513],[7.1,-28.053],[-34.941,-37.761],[-11.174,88.362],[-37.879,-6.729],[-0.095,0.176],[-31.014,-58.505],[0,0],[14.241,-72.739],[-6.274,8.083]],"o":[[15.132,-80.993],[-34.437,-50.783],[-3.133,3.566],[-21.38,1.487],[-9.893,39.09],[10.577,12.163],[9.103,-71.845],[0.657,0.117],[26.393,-49.26],[0,0],[18.343,13.531],[-13.369,61.603],[16.807,-21.652]],"v":[[151.669,45.461],[120.64,-58.1],[-65.864,-77.467],[-100.43,-76.305],[-144.99,-26.303],[-112.398,104.376],[-154.982,-10.318],[-70.914,-81.08],[-70.335,-81.317],[127.425,-58.144],[126.56,-59.162],[158.173,46.77],[112.167,122.494]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":40,"s":[{"i":[[-7.757,37.124],[8.483,12.743],[33.421,-43.443],[21.766,-1.513],[4.256,-36.267],[-30.211,-32.649],[-10.213,88.478],[-27.783,-18.484],[-0.095,0.176],[-31.014,-58.505],[0,0],[14.241,-72.739],[-6.274,8.083]],"o":[[15.132,-80.993],[-34.437,-50.783],[-5.773,7.066],[-25.413,1.767],[-4.314,36.76],[10.577,12.163],[8.852,-76.684],[0.556,0.37],[26.393,-49.26],[0,0],[18.343,13.531],[-13.369,61.603],[16.807,-21.652]],"v":[[151.669,45.461],[120.64,-58.1],[-65.864,-77.467],[-100.728,-82.604],[-147.748,-17.722],[-112.398,104.376],[-154.126,-16.168],[-70.914,-81.08],[-70.335,-81.317],[127.425,-58.144],[126.56,-59.162],[158.173,46.77],[112.167,122.494]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":70,"s":[{"i":[[-7.757,37.124],[8.483,12.743],[33.421,-43.443],[22.815,-4.317],[7.097,-28.054],[-29.159,-28.84],[-4.11,84.085],[-38.304,1.543],[-0.095,0.176],[-31.014,-58.505],[0,0],[14.241,-72.739],[-6.274,8.083]],"o":[[15.132,-80.993],[-34.437,-50.783],[-3.133,3.566],[-21.058,3.984],[-11.568,45.726],[10.577,12.163],[3.536,-72.333],[0.667,-0.027],[26.393,-49.26],[0,0],[18.343,13.531],[-13.369,61.603],[16.807,-21.652]],"v":[[151.669,45.461],[120.64,-58.1],[-65.864,-77.467],[-100.626,-71.441],[-148.288,-23.678],[-113.806,103.623],[-158.728,-1.603],[-70.914,-81.08],[-70.335,-81.317],[127.425,-58.144],[126.56,-59.162],[158.173,46.77],[112.167,122.494]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":90,"s":[{"i":[[-7.757,37.124],[8.483,12.743],[33.421,-43.443],[21.766,-1.513],[4.256,-36.267],[-30.211,-32.649],[-10.213,88.478],[-27.783,-18.484],[-0.095,0.176],[-31.014,-58.505],[0,0],[14.241,-72.739],[-6.274,8.083]],"o":[[15.132,-80.993],[-34.437,-50.783],[-5.773,7.066],[-25.413,1.767],[-4.314,36.76],[10.577,12.163],[8.852,-76.684],[0.556,0.37],[26.393,-49.26],[0,0],[18.343,13.531],[-13.369,61.603],[16.807,-21.652]],"v":[[151.669,45.461],[120.64,-58.1],[-65.864,-77.467],[-100.728,-82.604],[-147.748,-17.722],[-112.398,104.376],[-154.126,-16.168],[-70.914,-81.08],[-70.335,-81.317],[127.425,-58.144],[126.56,-59.162],[158.173,46.77],[112.167,122.494]],"c":true}]},{"t":115,"s":[{"i":[[-7.757,37.124],[8.483,12.743],[33.421,-43.443],[21.766,-1.513],[7.1,-28.053],[-34.941,-37.761],[-11.174,88.362],[-37.879,-6.729],[-0.095,0.176],[-31.014,-58.505],[0,0],[14.241,-72.739],[-6.274,8.083]],"o":[[15.132,-80.993],[-34.437,-50.783],[-3.133,3.566],[-21.38,1.487],[-9.893,39.09],[10.577,12.163],[9.103,-71.845],[0.657,0.117],[26.393,-49.26],[0,0],[18.343,13.531],[-13.369,61.603],[16.807,-21.652]],"v":[[151.669,45.461],[120.64,-58.1],[-65.864,-77.467],[-100.43,-76.305],[-144.99,-26.303],[-112.398,104.376],[-154.982,-10.318],[-70.914,-81.08],[-70.335,-81.317],[127.425,-58.144],[126.56,-59.162],[158.173,46.77],[112.167,122.494]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[172.914,130.827]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[14,54],[4.444,4.955],[34.368,-32.605],[60,57],[5.808,10.261],[-20.215,-19.205],[-39,37]],"o":[[-1.285,-4.957],[6.329,47.928],[-39,37],[-9.629,-9.147],[5.076,22.27],[60,57],[39,-37]],"v":[[144.152,-85.831],[134.574,-101.169],[90.818,27.502],[-135.182,9.502],[-158.152,-19.802],[-121.848,44.169],[104.152,62.169]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.819999964097,0.74900004069,0.769000004787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[181.598,191.84]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.833,-2.167],[-19.334,0.834],[17,1.667]],"o":[[-22.5,0.333],[-4.834,2.166],[19.333,-0.833],[-17,-1.666]],"v":[[-5.458,-5.521],[-39.916,0.021],[7.084,4.687],[27.75,-3.813]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.987999949736,0.987999949736,0.987999949736,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[183.333,35.655]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[17,-23],[-60,-57],[-39,37],[14,54],[0,0],[73,8]],"o":[[-26,-2],[-17,23],[60,57],[39,-37],[-3.654,-14.094],[0,0],[-78.17,-8.566]],"v":[[-66.25,-98.217],[-132.25,-81.217],[-109.75,77.783],[111.75,97.283],[155.75,-45.217],[127.75,-77.217],[33.75,-126.217]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":40,"s":[{"i":[[0,0],[17,-23],[-60,-57],[-39,37],[14,54],[0,0],[73,8]],"o":[[-21.525,-11.852],[-17,23],[60,57],[39,-37],[-3.654,-14.094],[0,0],[-78.17,-8.566]],"v":[[-66.25,-98.217],[-130.836,-87.743],[-109.75,77.783],[111.75,97.283],[155.75,-45.217],[127.75,-77.217],[33.75,-126.217]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":70,"s":[{"i":[[0,0],[17,-23],[-57.731,-43.104],[-39,37],[14,54],[0,0],[73,8]],"o":[[-24.615,-2.505],[-17,23],[66.314,49.512],[39,-37],[-3.654,-14.094],[0,0],[-78.17,-8.566]],"v":[[-66.25,-98.217],[-140.677,-66.566],[-109.75,77.783],[111.75,97.283],[155.75,-45.217],[127.75,-77.217],[33.75,-126.217]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":90,"s":[{"i":[[0,0],[17,-23],[-60,-57],[-39,37],[14,54],[0,0],[73,8]],"o":[[-21.525,-11.852],[-17,23],[60,57],[39,-37],[-3.654,-14.094],[0,0],[-78.17,-8.566]],"v":[[-66.25,-98.217],[-130.836,-87.743],[-109.75,77.783],[111.75,97.283],[155.75,-45.217],[127.75,-77.217],[33.75,-126.217]],"c":true}]},{"t":115,"s":[{"i":[[0,0],[17,-23],[-60,-57],[-39,37],[14,54],[0,0],[73,8]],"o":[[-26,-2],[-17,23],[60,57],[39,-37],[-3.654,-14.094],[0,0],[-78.17,-8.566]],"v":[[-66.25,-98.217],[-132.25,-81.217],[-109.75,77.783],[111.75,97.283],[155.75,-45.217],[127.75,-77.217],[33.75,-126.217]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.913999968884,0.925,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[170,151.226]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":"ear L","parent":28,"sr":1,"ks":{"p":{"a":0,"k":[192.291,30.766,0]},"a":{"a":0,"k":[86.998,57.532,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-9.095,-14.043]],"o":[[9.581,30.312],[0,0]],"v":[[-3.436,-30.542],[2.95,30.542]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[61.699,46.215]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[5.857,0.673],[-23.668,-30.971]],"o":[[-8.132,-22.162],[-5.857,-0.672],[23.669,30.97]],"v":[[35.349,-10.926],[7.914,-52.801],[-11.681,22.503]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[50.348,68.473]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.383,9.068],[-0.078,-3.404]],"o":[[1.704,-11.734],[10.745,4.364],[0.078,3.404]],"v":[[-14.265,31.038],[-14.276,-31.038],[14.198,12.608]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.317999985639,0.250999989229,0.340999977261,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[72.8,49.412]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[5.857,0.673],[-23.668,-30.971]],"o":[[-8.132,-22.162],[-5.857,-0.672],[23.669,30.97]],"v":[[35.349,-10.926],[7.914,-52.801],[-11.681,22.503]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541000007181,0.455000005984,0.573000021542,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[50.348,68.473]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"neck","sr":1,"ks":{"p":{"a":0,"k":[338.345,409.653,0]},"a":{"a":0,"k":[161.669,147.644,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":2,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":42,"s":[103,97,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":72,"s":[97,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":92,"s":[103,97,100]},{"t":120,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-1.381,-0.905],[10.284,-27.252],[34.027,-15.556],[-0.905,0.653],[-10.166,27.433],[24.062,15.756],[-0.891,1.362]],"o":[[26.373,17.266],[-10.304,28.396],[-1.034,0.474],[27.897,-20.172],[9.347,-24.506],[-1.414,-0.925],[0.906,-1.382]],"v":[[3.203,-58.085],[26.866,13.038],[-35.302,58.516],[-36.245,56.915],[21.253,10.978],[-0.063,-53.077],[-0.936,-57.222]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[291.254,59.24]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0.682,0.395],[15.347,32.96],[-0.949,-1.273],[-11.486,-18.784],[0.895,1]],"o":[[-32.358,-18.752],[-0.689,-1.481],[15.381,20.602],[4.645,7.557],[0.523,0.584]],"v":[[18.695,39.303],[-19.765,-37.046],[-17.495,-38.425],[6.315,21.575],[19.559,38.242]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[20.704,82.895]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[23,15],[0,0],[0,0],[0,0],[24,-17.375],[-9.775,9.164],[-14,36]],"o":[[0,0],[0,0],[0,0],[44.333,7.666],[16.5,5.25],[48,-45],[11.581,-29.781]],"v":[[43.71,-73.582],[-52.547,-60.97],[-68.29,-52.582],[-77.29,0.418],[-62.665,65.543],[-26.29,64.418],[65.71,-4.582]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.819999964097,0.74900004069,0.769000004787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[249.114,77.241]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-1.024,-17.807],[-36,-3.834],[6.167,6.5],[41.167,12.167]],"o":[[2.167,37.667],[36,3.833],[-6.166,-6.5],[-34.276,-10.13]],"v":[[-69.988,-22.18],[3.179,36.154],[64.845,10.82],[-16.488,-3.68]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":40,"s":[{"i":[[-2.605,-17.645],[-36,-3.834],[6.167,6.5],[38.152,9.528]],"o":[[3.491,23.649],[36,3.833],[-6.166,-6.5],[-34.677,-8.66]],"v":[[-64.888,-8.014],[-2.893,35.124],[55.859,12.623],[-16.245,4.305]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":70,"s":[{"i":[[-1.024,-17.807],[-36,-3.834],[6.167,6.5],[41.167,12.167]],"o":[[2.167,37.667],[36,3.833],[-6.166,-6.5],[-34.276,-10.13]],"v":[[-69.988,-22.18],[3.179,36.154],[64.845,10.82],[-16.488,-3.68]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":90,"s":[{"i":[[-2.605,-17.645],[-36,-3.834],[6.167,6.5],[38.152,9.528]],"o":[[3.491,23.649],[36,3.833],[-6.166,-6.5],[-34.677,-8.66]],"v":[[-64.888,-8.014],[-2.893,35.124],[55.859,12.623],[-16.245,4.305]],"c":true}]},{"t":115,"s":[{"i":[[-1.024,-17.807],[-36,-3.834],[6.167,6.5],[41.167,12.167]],"o":[[2.167,37.667],[36,3.833],[-6.166,-6.5],[-34.276,-10.13]],"v":[[-69.988,-22.18],[3.179,36.154],[64.845,10.82],[-16.488,-3.68]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.987999949736,0.987999949736,0.987999949736,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[109.312,94.505]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[11.581,-29.78],[48,-45],[33,26],[15,25]],"o":[[23,15],[-14,36],[-33.726,31.616],[-32.52,-25.622],[0,0]],"v":[[128.96,-73.308],[150.96,-4.308],[55.626,41.692],[-124.04,44.692],[-162.54,-34.975]],"c":false}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.913999968884,0.925,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[163.864,76.967]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":"body","sr":1,"ks":{"p":{"a":0,"k":[336.91,498.263,0]},"a":{"a":0,"k":[160.66,196.596,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":40,"s":[103,97,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":70,"s":[97,103,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":90,"s":[103,97,100]},{"t":117,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0],[5,-9]],"o":[[25.13,-47.91],[0,0],[0,0]],"v":[[125.436,48.632],[133.435,-48.632],[-150.565,-28.632]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[176.815,63.632]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0.165,-17.083],[6.74,-1.296],[0.425,1.477],[-6.682,21.544],[-0.518,-0.16]],"o":[[-0.455,46.656],[-1.538,0.296],[-6.116,-21.534],[0.161,-0.518],[1.376,0.427]],"v":[[-0.565,-18.478],[1.853,36.903],[-1.602,34.775],[-1.911,-36.391],[-0.682,-37.038]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[11.935,116.928]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[1.432,3.581],[-32.508,23.877],[0.782,-0.856],[-16.566,-47.916]],"o":[[-16.339,-41.075],[0.934,-0.687],[-20.147,22.104],[1.299,3.754]],"v":[[-6.777,42.277],[21.05,-45.171],[22.335,-43.744],[-1.17,40.141]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[212.224,141.791]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.5,16],[0,0],[8,-22.5],[0,0]],"o":[[-8.167,-16],[-0.5,-16],[0,0],[-8,22.5],[0,0]],"v":[[20.084,33.083],[11.916,-5.917],[18.251,-43.25],[-12.084,-5.584],[-7.249,43.25]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.913999968884,0.925,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[215.666,140.25]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-23.723,7.742],[0,0],[54.204,-2.708],[0,0],[29.5,-25],[5.5,-17],[1.542,-18.312]],"o":[[144,-47],[0,0],[0,0],[21,5.5],[-29.5,25],[-4.868,15.048],[19.215,-1.335]],"v":[[-31.569,77.47],[70.431,-90.531],[-28.808,-86.048],[-48.569,-66.531],[-46.694,-8.906],[-88.569,35.47],[-95.931,90.531]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.819999964097,0.74900004069,0.769000004787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[239.819,105.531]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[5,-9],[-13,-54],[-133.963,43.724],[0,0]],"o":[[-9.713,17.482],[0,0],[144,-47],[0,0]],"v":[[-141.75,-77.667],[-154.75,38.334],[40.25,70.334],[142.25,-97.667]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.913999968884,0.925,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[168,112.667]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 6","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"hand","sr":1,"ks":{"p":{"a":0,"k":[180.099,310.667,0]},"a":{"a":0,"k":[63.599,15,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[0,0],[3,-15],[-25,2],[-7,3]],"o":[[-18,4],[-3,15],[25,-2],[7,-3]],"v":[[14,-71],[-27,-35],[-21,69],[39,2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[5.862,-12.632],[-28.202,-0.477],[-11.611,10.258]],"o":[[-18,4],[-15.681,16.622],[40.721,0.854],[6.424,-3.757]],"v":[[14,-71],[-34.301,-39.355],[-24.221,56.979],[37.975,-0.818]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,0],[9.542,-9.589],[-32.32,-3.661],[-17.54,19.591]],"o":[[-18,4],[-31.985,18.708],[28.853,2.952],[5.682,-4.729]],"v":[[14,-71],[-43.689,-44.955],[-33.506,44.951],[36.658,-4.441]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":45,"s":[{"i":[[0,0],[14.172,-5.759],[-37.5,-7.667],[-25,31.333]],"o":[[-18,4],[-52.5,21.333],[31.579,6.456],[4.75,-5.953]],"v":[[14,-71],[-55.5,-52],[-51.75,37.75],[35,-9]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[11.574,-7.907],[-34.594,-5.419],[-20.815,24.745]],"o":[[-18,4],[-40.991,19.861],[30.05,4.49],[5.273,-5.266]],"v":[[14,-71],[-48.874,-48.047],[-38.35,43.516],[35.93,-6.442]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,0],[6.751,-11.897],[-29.197,-1.246],[-13.044,12.514]],"o":[[-18,4],[-19.621,17.126],[35.591,2.732],[6.244,-3.992]],"v":[[14,-71],[-36.57,-40.708],[-22.841,52.851],[37.657,-1.694]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[3,-15],[-25,2],[-7,3]],"o":[[-18,4],[-3,15],[25,-2],[7,-3]],"v":[[14,-71],[-27,-35],[-21,69],[39,2]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[61,86]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.833,12.083],[5.334,-3.167],[0,0]],"o":[[11.333,-2.584],[-1.834,-12.084],[-5.333,3.167],[0,0]],"v":[[-5.833,19.292],[16.833,-7.208],[2.999,-13.875],[-18.667,4.792]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.913999968884,0.925,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[53.5,35.708]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[0,0],[3,-15],[-25,2],[-7,3]],"o":[[-18,4],[-3,15],[25,-2],[7,-3]],"v":[[14,-71],[-27,-35],[-21,69],[39,2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[5.862,-12.632],[-28.202,-0.477],[-11.611,10.258]],"o":[[-18,4],[-15.681,16.622],[40.721,0.854],[6.424,-3.757]],"v":[[14,-71],[-34.301,-39.355],[-24.221,56.979],[37.975,-0.818]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,0],[9.542,-9.589],[-32.32,-3.661],[-17.54,19.591]],"o":[[-18,4],[-31.985,18.708],[28.853,2.952],[5.682,-4.729]],"v":[[14,-71],[-43.689,-44.955],[-33.506,44.951],[36.658,-4.441]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":45,"s":[{"i":[[0,0],[14.172,-5.759],[-37.5,-7.667],[-25,31.333]],"o":[[-18,4],[-52.5,21.333],[31.579,6.456],[4.75,-5.953]],"v":[[14,-71],[-55.5,-52],[-51.75,37.75],[35,-9]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[11.574,-7.907],[-34.594,-5.419],[-20.815,24.745]],"o":[[-18,4],[-40.991,19.861],[30.05,4.49],[5.273,-5.266]],"v":[[14,-71],[-48.874,-48.047],[-38.35,43.516],[35.93,-6.442]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,0],[6.751,-11.897],[-29.197,-1.246],[-13.044,12.514]],"o":[[-18,4],[-19.621,17.126],[35.591,2.732],[6.244,-3.992]],"v":[[14,-71],[-36.57,-40.708],[-22.841,52.851],[37.657,-1.694]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[3,-15],[-25,2],[-7,3]],"o":[[-18,4],[-3,15],[25,-2],[7,-3]],"v":[[14,-71],[-27,-35],[-21,69],[39,2]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.819999964097,0.74900004069,0.769000004787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[61,86]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":"neck 2","parent":30,"sr":1,"ks":{"p":{"a":0,"k":[31.165,93.769,0]},"a":{"a":0,"k":[55.37,109.11,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[16.958,6.358],[-1.029,-37.04],[-22,20]],"o":[[-32,-12],[1,36],[22,-20]],"v":[[12.514,-41],[-44.486,-2],[23.515,33]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.458999992819,0.322000002394,0.361000001197,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.913999968884,0.925,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[60.514,68]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0}]} \ No newline at end of file diff --git a/Tests/LottieMesh/Resources/Fireworks.json b/Tests/LottieMesh/Resources/Fireworks.json deleted file mode 100644 index 998fbd775c5..00000000000 --- a/Tests/LottieMesh/Resources/Fireworks.json +++ /dev/null @@ -1 +0,0 @@ -{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":180,"w":512,"h":512,"nm":"512_fireworks dop","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":0,"nm":"fireworks !!yellow 4 vspish","refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":76,"s":[100]},{"t":86,"s":[0]}]},"r":{"a":0,"k":-10.694},"p":{"a":0,"k":[977.5,379,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[80,80,100]}},"ao":0,"w":486,"h":486,"ip":36,"op":86,"st":6,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"fireworks pink 5","refId":"comp_3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":83,"s":[100]},{"t":94,"s":[0]}]},"r":{"a":0,"k":-0.087},"p":{"a":0,"k":[1279.5,464,0]},"a":{"a":0,"k":[243,243,0]}},"ao":0,"w":486,"h":486,"ip":48,"op":94,"st":18,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"fireworks !!yellow 4 vspish","refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":105,"s":[100]},{"t":115,"s":[0]}]},"r":{"a":0,"k":23.994},"p":{"a":0,"k":[305.5,923,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[78.011,78.011,100]}},"ao":0,"w":486,"h":486,"ip":65,"op":115,"st":35,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"fireworks pink 5","refId":"comp_3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":71,"s":[100]},{"t":81,"s":[0]}]},"p":{"a":0,"k":[441.5,398,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[157.86,157.86,100]}},"ao":0,"w":486,"h":486,"ip":31,"op":81,"st":1,"bm":0},{"ddd":0,"ind":5,"ty":3,"nm":"Trace Shape Layer 1: Path 6 [1.1]","cl":"1","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[-120.256]},{"t":29,"s":[-176.947]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[1286,792,0],"to":[-172.306,-276.404,0],"ti":[305.23,23.654,0]},{"t":29,"s":[468.343,405.178,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":10,"s":[40,40,100]},{"t":30,"s":[150,150,100]}]}},"ao":0,"ip":0,"op":30,"st":-60,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"!!!rocket pink 2","parent":5,"refId":"comp_4","sr":1,"ks":{"r":{"a":0,"k":90},"p":{"a":0,"k":[-167.855,0.94,0]},"a":{"a":0,"k":[256.94,395.855,0]}},"ao":0,"w":512,"h":512,"ip":1,"op":36,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"fireworks !!yellow 4 vspish","refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":89,"s":[100]},{"t":99,"s":[0]}]},"r":{"a":0,"k":-10.694},"p":{"a":0,"k":[633.5,1163,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[128.178,128.178,100]}},"ao":0,"w":486,"h":486,"ip":49,"op":99,"st":19,"bm":0},{"ddd":0,"ind":8,"ty":3,"nm":"Trace Shape Layer 1: Path 5 [1.2]","cl":"2","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[98.009]},{"t":46,"s":[185.502]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":16,"s":[1236,866,0],"to":[-48.547,286.872,0],"ti":[243.966,25.033,0]},{"t":46,"s":[667.754,1167.601,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":16,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,3.222]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":26,"s":[40,40,100]},{"t":46,"s":[130,130,100]}]}},"ao":0,"ip":16,"op":47,"st":-44,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"!!!rocket yellow 2","parent":8,"refId":"comp_5","sr":1,"ks":{"r":{"a":0,"k":90},"p":{"a":0,"k":[-167.855,0.94,0]},"a":{"a":0,"k":[256.94,395.855,0]}},"ao":0,"w":512,"h":512,"ip":16,"op":52,"st":16,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"fireworks !!blue 4 vspish","refId":"comp_6","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":89,"s":[100]},{"t":100,"s":[0]}]},"r":{"a":0,"k":-0.087},"p":{"a":0,"k":[695.5,832,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[201.159,201.159,100]}},"ao":0,"w":486,"h":486,"ip":54,"op":100,"st":24,"bm":0},{"ddd":0,"ind":11,"ty":3,"nm":"Trace Shape Layer 1: Path 8 [1.8]","cl":"8","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[112.759]},{"t":51,"s":[219.154]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.82,"y":0},"t":22,"s":[1352,810,0],"to":[-138.369,305.192,0],"ti":[207.549,143.516,0]},{"t":51,"s":[705.012,835.922,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":22,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,-2.333]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":32,"s":[40,40,100]},{"t":52,"s":[180,180,100]}]}},"ao":0,"ip":22,"op":52,"st":-38,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"!!!rocket blue 2","parent":11,"refId":"comp_7","sr":1,"ks":{"r":{"a":0,"k":90},"p":{"a":0,"k":[-167.855,0.94,0]},"a":{"a":0,"k":[256.94,395.855,0]}},"ao":0,"w":512,"h":512,"ip":22,"op":58,"st":22,"bm":0},{"ddd":0,"ind":13,"ty":0,"nm":"fireworks pink 5","refId":"comp_3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":115,"s":[100]},{"t":125,"s":[0]}]},"p":{"a":0,"k":[1025.5,1230,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[-119.753,119.753,100]}},"ao":0,"w":486,"h":486,"ip":76,"op":125,"st":45,"bm":0},{"ddd":0,"ind":14,"ty":3,"nm":"Trace Shape Layer 1: Path 2 [1.4]","cl":"4","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[-0.702]},"o":{"x":[0.333],"y":[0]},"t":43,"s":[56.31]},{"i":{"x":[0.667],"y":[0.756]},"o":{"x":[0.333],"y":[0.183]},"t":53,"s":[67.425]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[1.177]},"t":62,"s":[160.472]},{"t":73,"s":[184.041]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":43,"s":[1266,806,0],"to":[170.946,262.206,0],"ti":[330.742,0.419,0]},{"t":73,"s":[1052.274,1229.28,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":43,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,4.333]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":53,"s":[40,40,100]},{"t":73,"s":[120,120,100]}]}},"ao":0,"ip":43,"op":74,"st":-17,"bm":0},{"ddd":0,"ind":15,"ty":0,"nm":"!!!rocket pink 2","parent":14,"refId":"comp_4","sr":1,"ks":{"r":{"a":0,"k":90},"p":{"a":0,"k":[-167.855,0.94,0]},"a":{"a":0,"k":[256.94,395.855,0]}},"ao":0,"w":512,"h":512,"ip":43,"op":79,"st":43,"bm":0},{"ddd":0,"ind":16,"ty":0,"nm":"fireworks !!blue 4 vspish","refId":"comp_6","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":127,"s":[100]},{"t":137,"s":[0]}]},"r":{"a":0,"k":12.326},"p":{"a":0,"k":[560.5,539,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[-177.976,177.976,100]}},"ao":0,"w":486,"h":486,"ip":87,"op":137,"st":57,"bm":0},{"ddd":0,"ind":17,"ty":3,"nm":"Trace Shape Layer 1: Path 7 [1.5]","cl":"5","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":55,"s":[155.577]},{"t":84,"s":[228.764]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":55,"s":[1194,772,0],"to":[-257.364,110.984,0],"ti":[153.116,221.504,0]},{"t":84,"s":[574.62,567.336,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":55,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,-1.222]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":65,"s":[40,40,100]},{"t":85,"s":[170,170,100]}]}},"ao":0,"ip":55,"op":85,"st":-5,"bm":0},{"ddd":0,"ind":18,"ty":0,"nm":"!!!rocket blue 2","parent":17,"refId":"comp_7","sr":1,"ks":{"r":{"a":0,"k":90},"p":{"a":0,"k":[-167.855,0.94,0]},"a":{"a":0,"k":[256.94,395.855,0]}},"ao":0,"w":512,"h":512,"ip":55,"op":91,"st":55,"bm":0},{"ddd":0,"ind":19,"ty":0,"nm":"fireworks !!yellow 4 vspish","refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":165,"s":[100]},{"t":175,"s":[0]}]},"r":{"a":0,"k":-0.478},"p":{"a":0,"k":[1038.964,328.288,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[132.042,132.042,100]}},"ao":0,"w":486,"h":486,"ip":125,"op":175,"st":95,"bm":0},{"ddd":0,"ind":20,"ty":3,"nm":"Trace Shape Layer 1: Path 3 [1.6]","cl":"6","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":93,"s":[-102.219]},{"t":122,"s":[-148.865]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":93,"s":[1362,740,0],"to":[-42.181,-182.712,0],"ti":[156.249,102.291,0]},{"t":122,"s":[1054.382,305.32,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":93,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":103,"s":[40,40,100]},{"t":123,"s":[130,130,100]}]}},"ao":0,"ip":93,"op":150,"st":33,"bm":0},{"ddd":0,"ind":21,"ty":0,"nm":"!!!rocket yellow 2","parent":20,"refId":"comp_5","sr":1,"ks":{"r":{"a":0,"k":90},"p":{"a":0,"k":[-167.855,0.94,0]},"a":{"a":0,"k":[256.94,395.855,0]}},"ao":0,"w":512,"h":512,"ip":93,"op":129,"st":93,"bm":0},{"ddd":0,"ind":22,"ty":0,"nm":"fireworks !!blue 4 vspish","refId":"comp_6","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":170,"s":[100]},{"t":180,"s":[0]}]},"r":{"a":0,"k":11.633},"p":{"a":0,"k":[655.5,1077,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":486,"h":486,"ip":134,"op":180,"st":105,"bm":0},{"ddd":0,"ind":23,"ty":3,"nm":"Trace Shape Layer 1: Path 1 [1.3]","cl":"3","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[0.703]},"o":{"x":[0.333],"y":[0]},"t":102,"s":[59.135]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0.623]},"t":112,"s":[132.595]},{"t":131,"s":[199.135]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":102,"s":[1156,836,0],"to":[208.414,207.152,0],"ti":[260.651,130.415,0]},{"t":131,"s":[655.186,1070.59,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":102,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,6.556]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":112,"s":[40,40,100]},{"t":132,"s":[100,100,100]}]}},"ao":0,"ip":102,"op":132,"st":42,"bm":0},{"ddd":0,"ind":24,"ty":0,"nm":"!!!rocket blue 2","parent":23,"refId":"comp_7","sr":1,"ks":{"r":{"a":0,"k":90},"p":{"a":0,"k":[-167.855,0.94,0]},"a":{"a":0,"k":[256.94,395.855,0]}},"ao":0,"w":512,"h":512,"ip":102,"op":138,"st":102,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"pink","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[100]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[100]},{"t":100,"s":[0]}]},"p":{"a":0,"k":[245.75,225.75,0]},"a":{"a":0,"k":[2.75,-17.25,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.629,0.629,0.629],"y":[0,0,0]},"t":30,"s":[43.235,43.235,100]},{"t":40,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.312,8.525],[3.244,0.766],[0.278,-4.348]],"o":[[-1.312,-8.525],[-5.358,-1.265],[-0.278,4.348]],"v":[[6.13,-24.571],[8.951,-66.768],[5.014,-23.638]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.55,23.061],[3.244,0.766],[0.753,-11.762]],"o":[[-3.55,-23.061],[-5.358,-1.265],[-0.753,11.762]],"v":[[11.788,-37.733],[14.47,-92.154],[8.769,-35.211]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.113,0.736],[3.244,0.766],[0.024,-0.376]],"o":[[-0.113,-0.736],[-5.358,-1.265],[-0.024,0.376]],"v":[[1.251,-14.165],[19.643,-137.56],[1.155,-14.085]],"c":true}]},{"t":100,"s":[{"i":[[0.002,0.014],[3.244,0.766],[0,-0.007]],"o":[[-0.002,-0.014],[-5.358,-1.265],[0,0.007]],"v":[[29.568,-215.114],[32.47,-250.154],[29.566,-215.113]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.775,14.209],[6.351,4.603],[2.652,-16.185]],"o":[[0.775,-14.209],[-4.056,-2.94],[-2.652,16.185]],"v":[[7.231,-23.969],[18.873,-61.186],[6.13,-24.571]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-2.096,38.439],[6.351,4.603],[7.173,-43.783]],"o":[[2.096,-38.439],[-4.056,-2.94],[-7.173,43.783]],"v":[[14.767,-36.105],[37.637,-108.647],[11.788,-37.733]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.067,1.227],[6.351,4.603],[0.229,-1.398]],"o":[[0.067,-1.227],[-4.056,-2.94],[-0.229,1.398]],"v":[[1.932,-14.242],[49.85,-146.868],[1.837,-14.294]],"c":true}]},{"t":100,"s":[{"i":[[-0.001,0.023],[6.351,4.603],[0.004,-0.026]],"o":[[0.001,-0.023],[-4.056,-2.94],[-0.004,0.026]],"v":[[75.069,-225.113],[80.137,-241.647],[75.068,-225.114]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.749,6.829],[6.31,5.609],[1.359,-6.48]],"o":[[-0.749,-6.829],[-3.554,-3.159],[-1.359,6.48]],"v":[[8.596,-23.217],[31.24,-73.607],[7.231,-23.969]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[2.026,18.473],[6.31,5.609],[3.677,-17.531]],"o":[[-2.026,-18.473],[-3.554,-3.159],[-3.677,17.531]],"v":[[18.46,-34.07],[47.481,-104.355],[14.767,-36.105]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.065,0.59],[6.31,5.609],[0.117,-0.56]],"o":[[-0.065,-0.59],[-3.554,-3.159],[-0.117,0.56]],"v":[[2.166,-13.836],[63.43,-142.576],[2.048,-13.901]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0.011],[6.31,5.609],[0.002,-0.011]],"o":[[-0.001,-0.011],[-3.554,-3.159],[-0.002,0.011]],"v":[[84.072,-198.612],[102.981,-237.355],[84.069,-198.613]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.199,15.089],[5.581,6.595],[12.758,-17.563]],"o":[[6.199,-15.089],[-2.72,-3.214],[-12.758,17.563]],"v":[[9.486,-22.203],[48.882,-61.987],[8.281,-23.532]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-16.769,40.819],[5.581,6.595],[34.513,-47.512]],"o":[[16.769,-40.819],[-2.72,-3.214],[-34.513,47.512]],"v":[[21.719,-30.475],[103.282,-109.764],[18.46,-34.07]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.535,1.303],[5.581,6.595],[1.102,-1.517]],"o":[[0.535,-1.303],[-2.72,-3.214],[-1.102,1.517]],"v":[[3.204,-13.341],[122.823,-131.604],[3.099,-13.456]],"c":true}]},{"t":100,"s":[{"i":[[-0.01,0.025],[5.581,6.595],[0.021,-0.029]],"o":[[0.01,-0.025],[-2.72,-3.214],[-0.021,0.029]],"v":[[156.574,-169.11],[171.282,-185.764],[156.572,-169.112]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.223,9.945],[2.258,6.104],[6.726,-10.205]],"o":[[4.223,-9.945],[-1.817,-4.912],[-6.726,10.205]],"v":[[11.177,-20.424],[48.778,-58.677],[9.801,-21.888]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-11.423,26.904],[2.258,6.104],[18.195,-27.606]],"o":[[11.423,-26.904],[-1.817,-4.912],[-18.195,27.606]],"v":[[25.439,-26.516],[74.48,-79.649],[21.719,-30.475]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.365,0.859],[2.258,6.104],[0.581,-0.882]],"o":[[0.365,-0.859],[-1.817,-4.912],[-0.581,0.882]],"v":[[3.264,-12.932],[105.66,-107.524],[3.146,-13.058]],"c":true}]},{"t":100,"s":[{"i":[[-0.007,0.016],[2.258,6.104],[0.011,-0.017]],"o":[[0.007,-0.016],[-1.817,-4.912],[-0.011,0.017]],"v":[[152.076,-147.108],[182.98,-176.649],[152.074,-147.11]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-3.334,6.648],[-1.799,5.518],[9.673,-9.931]],"o":[[3.334,-6.648],[1.878,-5.758],[-9.673,9.931]],"v":[[12.897,-19.193],[48.972,-44.204],[11.177,-20.424]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-9.018,17.983],[-1.799,5.518],[26.167,-26.865]],"o":[[9.018,-17.983],[1.878,-5.758],[-26.167,26.865]],"v":[[30.094,-23.186],[81.927,-62.022],[25.439,-26.516]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.288,0.574],[-1.799,5.518],[0.836,-0.858]],"o":[[0.288,-0.574],[1.878,-5.758],[-0.836,0.858]],"v":[[3.883,-12.491],[120.292,-83.862],[3.734,-12.597]],"c":true}]},{"t":100,"s":[{"i":[[-0.005,0.011],[-1.799,5.518],[0.016,-0.016]],"o":[[0.005,-0.011],[1.878,-5.758],[-0.016,0.016]],"v":[[188.579,-121.106],[215.427,-138.022],[188.576,-121.108]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-9.869,8.704],[-3.432,7.058],[12.009,-7.852]],"o":[[9.869,-8.704],[2.981,-6.13],[-12.009,7.852]],"v":[[12.832,-16.628],[74.374,-42.527],[12.897,-19.193]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-26.696,23.547],[-3.432,7.058],[32.485,-21.24]],"o":[[26.696,-23.547],[2.981,-6.13],[-32.485,21.24]],"v":[[29.916,-16.247],[117.42,-56.876],[30.094,-23.186]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.852,0.752],[-3.432,7.058],[1.037,-0.678]],"o":[[0.852,-0.752],[2.981,-6.13],[-1.037,0.678]],"v":[[3.897,-11.786],[151.618,-69.521],[3.902,-12.008]],"c":true}]},{"t":100,"s":[{"i":[[-0.016,0.014],[-3.432,7.058],[0.02,-0.013]],"o":[[0.016,-0.014],[2.981,-6.13],[-0.02,0.013]],"v":[[190.079,-83.601],[236.42,-100.876],[190.079,-83.606]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-14.199,3.553],[-2.249,4.962],[22.38,-4.873]],"o":[[22.287,-5.577],[4.049,-8.935],[-22.38,4.873]],"v":[[10.861,-15.141],[88.919,-15.854],[12.516,-16.944]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-38.412,9.611],[-2.249,4.962],[60.542,-13.183]],"o":[[60.291,-15.085],[4.049,-8.935],[-60.542,13.183]],"v":[[25.439,-11.372],[154.986,-15.381],[29.916,-16.247]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-1.227,0.307],[-2.249,4.962],[1.933,-0.421]],"o":[[1.925,-0.482],[4.049,-8.935],[-1.933,0.421]],"v":[[3.934,-10.704],[182.862,-13.944],[4.077,-10.859]],"c":true}]},{"t":100,"s":[{"i":[[-0.023,0.006],[-2.249,4.962],[0.036,-0.008]],"o":[[0.036,-0.009],[4.049,-8.935],[-0.036,0.008]],"v":[[204.076,-11.599],[251.986,-10.381],[204.079,-11.601]],"c":true}]}]},"nm":"Path 8","hd":false},{"ind":8,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.711,2.16],[-0.839,0.958],[12.251,-0.88]],"o":[[4.711,-2.16],[1.348,-1.539],[-12.251,0.88]],"v":[[9.079,-12.869],[39.383,-12.388],[10.861,-14.826]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-12.744,5.843],[-0.839,0.958],[33.14,-2.38]],"o":[[12.744,-5.843],[1.348,-1.539],[-33.14,2.38]],"v":[[20.619,-6.076],[78.015,-8.131],[25.439,-11.372]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.407,0.187],[-0.839,0.958],[1.058,-0.076]],"o":[[0.407,-0.187],[1.348,-1.539],[-1.058,0.076]],"v":[[4.224,-10.251],[128.305,-2.239],[4.378,-10.421]],"c":true}]},{"t":100,"s":[{"i":[[-0.008,0.004],[-0.839,0.958],[0.02,-0.001]],"o":[[0.008,-0.004],[1.348,-1.539],[-0.02,0.001]],"v":[[238.573,10.405],[253.015,12.369],[238.576,10.401]],"c":true}]}]},"nm":"Path 9","hd":false},{"ind":9,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-19.702,-2.401],[-2.482,3.154],[14.29,0.398]],"o":[[19.702,2.401],[2.595,-3.297],[-14.29,-0.398]],"v":[[9.698,-12.307],[64.597,6.088],[9.395,-13.184]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-53.298,-6.494],[-2.482,3.154],[38.657,1.077]],"o":[[53.298,6.494],[2.595,-3.297],[-38.657,-1.077]],"v":[[21.439,-3.705],[93.137,16.968],[20.619,-6.076]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-1.702,-0.207],[-2.482,3.154],[1.234,0.034]],"o":[[1.702,0.207],[2.595,-3.297],[-1.234,-0.034]],"v":[[3.71,-9.493],[135.812,35.216],[3.683,-9.569]],"c":true}]},{"t":100,"s":[{"i":[[-0.032,-0.004],[-2.482,3.154],[0.023,0.001]],"o":[[0.032,0.004],[2.595,-3.297],[-0.023,-0.001]],"v":[[196.573,63.406],[241.637,80.468],[196.573,63.405]],"c":true}]}]},"nm":"Path 10","hd":false},{"ind":10,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-9.253,-3.386],[-5.064,4.293],[16.792,8.51]],"o":[[9.253,3.386],[4.126,-3.498],[-16.792,-8.51]],"v":[[11.313,-9.614],[54.159,17.809],[9.698,-11.992]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-25.032,-9.16],[-5.064,4.293],[45.426,23.022]],"o":[[25.032,9.16],[4.126,-3.498],[-45.426,-23.022]],"v":[[25.808,2.729],[103.197,50.764],[21.439,-3.705]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.799,-0.292],[-5.064,4.293],[1.451,0.735]],"o":[[0.799,0.292],[4.126,-3.498],[-1.451,-0.735]],"v":[[3.881,-8.702],[137.251,71.886],[3.742,-8.908]],"c":true}]},{"t":100,"s":[{"i":[[-0.015,-0.006],[-5.064,4.293],[0.027,0.014]],"o":[[0.015,0.006],[4.126,-3.498],[-0.027,-0.014]],"v":[[199.076,108.91],[221.697,124.264],[199.073,108.906]],"c":true}]}]},"nm":"Path 11","hd":false},{"ind":11,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.526,-3.215],[-3.502,3.162],[9.671,6.481]],"o":[[6.526,3.215],[3.502,-3.162],[-9.671,-6.481]],"v":[[10.491,-8.994],[73.057,40.569],[11.313,-9.929]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-17.653,-8.697],[-3.502,3.162],[26.163,17.532]],"o":[[17.653,8.697],[3.502,-3.162],[-26.163,-17.532]],"v":[[23.585,5.258],[96.236,63.117],[25.808,2.729]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.564,-0.278],[-3.502,3.162],[0.835,0.56]],"o":[[0.564,0.278],[3.502,-3.162],[-0.835,-0.56]],"v":[[3.102,-8.57],[126.267,90.274],[3.173,-8.651]],"c":true}]},{"t":100,"s":[{"i":[[-0.011,-0.005],[-3.502,3.162],[0.016,0.011]],"o":[[0.011,0.005],[3.502,-3.162],[-0.016,-0.011]],"v":[[144.075,112.911],[200.736,157.617],[144.076,112.91]],"c":true}]}]},"nm":"Path 12","hd":false},{"ind":12,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-13.475,-10.482],[-4.458,3.463],[10.073,7.849]],"o":[[13.475,10.482],[4.458,-3.463],[-10.073,-7.849]],"v":[[9.502,-7.951],[52.87,45.892],[10.491,-8.678]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-36.453,-28.355],[-4.458,3.463],[27.249,21.233]],"o":[[36.453,28.355],[4.458,-3.463],[-27.249,-21.233]],"v":[[20.909,7.226],[94.655,93.669],[23.585,5.258]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-1.164,-0.905],[-4.458,3.463],[0.87,0.678]],"o":[[1.164,0.905],[4.458,-3.463],[-0.87,-0.678]],"v":[[3.049,-7.986],[116.926,120.825],[3.134,-8.049]],"c":true}]},{"t":100,"s":[{"i":[[-0.022,-0.017],[-4.458,3.463],[0.016,0.013]],"o":[[0.022,0.017],[4.458,-3.463],[-0.016,-0.013]],"v":[[146.573,153.413],[172.155,188.169],[146.575,153.411]],"c":true}]}]},"nm":"Path 13","hd":false},{"ind":13,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.775,-3.087],[-3.488,2.449],[10.229,14.193]],"o":[[4.775,3.087],[3.488,-2.449],[-10.229,-14.193]],"v":[[7.167,-8.135],[48.091,56.332],[9.187,-7.951]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-12.917,-8.352],[-3.488,2.449],[27.671,38.395]],"o":[[12.917,8.352],[3.488,-2.449],[-27.671,-38.395]],"v":[[15.445,6.729],[62.124,74.78],[20.909,7.226]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.412,-0.267],[-3.488,2.449],[0.884,1.226]],"o":[[0.412,0.267],[3.488,-2.449],[-0.884,-1.226]],"v":[[2.321,-8.079],[84.827,113.576],[2.495,-8.063]],"c":true}]},{"t":100,"s":[{"i":[[-0.008,-0.005],[-3.488,2.449],[0.017,0.023]],"o":[[0.008,0.005],[3.488,-2.449],[-0.017,-0.023]],"v":[[103.57,147.412],[141.124,209.78],[103.573,147.413]],"c":true}]}]},"nm":"Path 14","hd":false},{"ind":14,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-5.362,-6.334],[-2.659,0.857],[3.694,6.735]],"o":[[5.361,6.334],[6.495,-2.093],[-3.694,-6.735]],"v":[[5.102,-9.295],[26.477,35.494],[7.167,-8.45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-14.504,-17.136],[-2.659,0.857],[9.994,18.219]],"o":[[14.504,17.136],[6.495,-2.093],[-9.994,-18.219]],"v":[[9.859,4.445],[41.772,64.349],[15.445,6.729]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.463,-0.547],[-2.659,0.857],[0.319,0.582]],"o":[[0.463,0.547],[6.495,-2.093],[-0.319,-0.582]],"v":[[2.027,-7.553],[61.313,111.91],[2.205,-7.48]],"c":true}]},{"t":100,"s":[{"i":[[-0.009,-0.01],[-2.659,0.857],[0.006,0.011]],"o":[[0.009,0.01],[6.495,-2.093],[-0.006,-0.011]],"v":[[94.566,193.911],[109.772,229.849],[94.57,193.912]],"c":true}]}]},"nm":"Path 15","hd":false},{"ind":15,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.43,-4.038],[-5.792,0.94],[0.557,8.126]],"o":[[0.43,4.038],[3.71,-0.602],[-0.557,-8.126]],"v":[[3.69,-10.876],[17.077,55.965],[5.102,-9.295]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-1.164,-10.924],[-5.792,0.94],[1.507,21.981]],"o":[[1.164,10.924],[3.71,-0.602],[-1.507,-21.981]],"v":[[6.039,0.167],[22.28,71.891],[9.859,4.445]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.037,-0.349],[-5.792,0.94],[0.048,0.702]],"o":[[0.037,0.349],[3.71,-0.602],[-0.048,-0.702]],"v":[[1.242,-7.799],[31.62,122.9],[1.364,-7.663]],"c":true}]},{"t":100,"s":[{"i":[[-0.001,-0.007],[-5.792,0.94],[0.001,0.013]],"o":[[0.001,0.007],[3.71,-0.602],[-0.001,-0.013]],"v":[[43.064,185.408],[54.78,249.391],[43.066,185.411]],"c":true}]}]},"nm":"Path 16","hd":false},{"ind":16,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.268,-1.072],[-8.766,2.77],[-1.453,20.282]],"o":[[-0.268,1.072],[8.956,-2.83],[1.453,-20.282]],"v":[[2.258,-8.649],[11.352,48.031],[4.005,-10.876]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[0.726,-2.901],[-8.766,2.77],[-3.93,54.866]],"o":[[-0.726,2.901],[8.956,-2.83],[3.93,-54.866]],"v":[[1.312,6.192],[20.655,115.675],[6.039,0.167]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.023,-0.093],[-8.766,2.77],[-0.125,1.752]],"o":[[-0.023,0.093],[8.956,-2.83],[0.125,-1.752]],"v":[[0.956,-7.06],[24.966,155.333],[1.106,-7.252]],"c":true}]},{"t":100,"s":[{"i":[[0,-0.002],[-8.766,2.77],[-0.002,0.033]],"o":[[0,0.002],[8.956,-2.83],[0.002,-0.033]],"v":[[32.561,227.912],[35.655,253.675],[32.564,227.908]],"c":true}]}]},"nm":"Path 17","hd":false},{"ind":17,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.674,-5.118],[-9.34,-10.304],[-5.656,20.469]],"o":[[-0.674,5.118],[5.335,5.886],[5.656,-20.469]],"v":[[0.319,-7.862],[-7.754,79.004],[2.258,-8.649]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[1.822,-13.845],[-9.34,-10.304],[-15.299,55.372]],"o":[[-1.822,13.845],[5.335,5.886],[15.299,-55.372]],"v":[[-3.933,8.322],[-10.119,142.549],[1.312,6.192]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.058,-0.442],[-9.34,-10.304],[-0.489,1.768]],"o":[[-0.058,0.442],[5.335,5.886],[0.489,-1.768]],"v":[[0.157,-7.436],[-14.573,174.16],[0.325,-7.504]],"c":true}]},{"t":100,"s":[{"i":[[0.001,-0.008],[-9.34,-10.304],[-0.009,0.033]],"o":[[-0.001,0.008],[5.335,5.886],[0.009,-0.033]],"v":[[-16.442,193.413],[-25.619,252.549],[-16.439,193.412]],"c":true}]}]},"nm":"Path 18","hd":false},{"ind":18,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.134,-4.491],[-8.529,-8.273],[-6.696,18.034]],"o":[[-1.134,4.491],[3.166,3.071],[6.696,-18.034]],"v":[[-1.761,-9.64],[-13.598,51.106],[0.003,-7.546]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.068,-12.148],[-8.529,-8.273],[-18.113,48.786]],"o":[[-3.068,12.148],[3.166,3.071],[18.113,-48.786]],"v":[[-8.707,2.658],[-24.162,110.866],[-3.933,8.322]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.098,-0.388],[-8.529,-8.273],[-0.578,1.558]],"o":[[-0.098,0.388],[3.166,3.071],[0.578,-1.558]],"v":[[-0.446,-7.378],[-34.795,149.374],[-0.293,-7.198]],"c":true}]},{"t":100,"s":[{"i":[[0.002,-0.007],[-8.529,-8.273],[-0.011,0.029]],"o":[[-0.002,0.007],[3.166,3.071],[0.011,-0.029]],"v":[[-51.445,211.91],[-61.162,244.866],[-51.442,211.913]],"c":true}]}]},"nm":"Path 19","hd":false},{"ind":19,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[9.217,-13.161],[-2.977,-4.918],[-5.725,7.174]],"o":[[-9.217,13.161],[1.721,2.843],[5.725,-7.174]],"v":[[-1.154,-12.499],[-42.53,56.34],[-1.761,-9.64]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[24.934,-35.602],[-2.977,-4.918],[-15.488,19.406]],"o":[[-24.934,35.602],[1.721,2.843],[15.488,-19.406]],"v":[[-7.064,-5.076],[-47.418,71.95],[-8.707,2.658]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.796,-1.137],[-2.977,-4.918],[-0.495,0.62]],"o":[[-0.796,1.137],[1.721,2.843],[0.495,-0.62]],"v":[[-0.876,-8.404],[-71.414,113.188],[-0.929,-8.157]],"c":true}]},{"t":100,"s":[{"i":[[0.015,-0.021],[-2.977,-4.918],[-0.009,0.012]],"o":[[-0.015,0.021],[1.721,2.843],[0.009,-0.012]],"v":[[-88.944,151.405],[-130.918,215.45],[-88.945,151.41]],"c":true}]}]},"nm":"Path 20","hd":false},{"ind":20,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[7.626,-7.899],[-8.084,-5.313],[-13.235,7.936]],"o":[[-2.855,2.957],[3.306,2.173],[10.979,-6.583]],"v":[[-4.169,-12.723],[-45.434,37.243],[-0.839,-12.499]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[20.63,-21.369],[-8.084,-5.313],[-35.802,21.467]],"o":[[-7.724,8],[3.306,2.173],[29.699,-17.808]],"v":[[-16.072,-5.684],[-88.796,85.335],[-7.064,-5.076]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.659,-0.682],[-8.084,-5.313],[-1.143,0.686]],"o":[[-0.247,0.255],[3.306,2.173],[0.948,-0.569]],"v":[[-1.962,-8.308],[-113.223,114.36],[-1.674,-8.288]],"c":true}]},{"t":100,"s":[{"i":[[0.012,-0.013],[-8.084,-5.313],[-0.022,0.013]],"o":[[-0.005,0.005],[3.306,2.173],[0.018,-0.011]],"v":[[-150.949,160.405],[-173.796,186.335],[-150.944,160.405]],"c":true}]}]},"nm":"Path 21","hd":false},{"ind":21,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[14.117,-10.21],[-5.628,-14.818],[-24.822,15.796]],"o":[[-14.086,10.188],[3.081,4.733],[21.924,-13.951]],"v":[[-3.765,-15.075],[-82.448,63.651],[-4.484,-12.723]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[38.189,-27.621],[0.797,-25.979],[-67.149,42.73]],"o":[[-38.106,27.561],[-0.163,5.311],[59.307,-37.74]],"v":[[-14.127,-12.046],[-117.611,102.283],[-16.072,-5.684]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[1.219,-0.882],[-2.13,-20.894],[-2.144,1.364]],"o":[[-1.217,0.88],[1.315,5.048],[1.894,-1.205]],"v":[[-1.552,-9.148],[-137.871,121.393],[-1.614,-8.945]],"c":true}]},{"t":100,"s":[{"i":[[0.023,-0.017],[-9.389,-8.283],[-0.04,0.026]],"o":[[-0.023,0.017],[4.981,4.394],[0.036,-0.023]],"v":[[-123.948,110.901],[-188.111,168.783],[-123.949,110.905]],"c":true}]}]},"nm":"Path 22","hd":false},{"ind":22,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.794,-1.349],[0.656,-8.742],[-9.144,3.299]],"o":[[-0.388,0.187],[-0.279,3.717],[9.144,-3.298]],"v":[[-4.357,-15.318],[-39.206,7.131],[-3.283,-15.062]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[7.56,-3.648],[1.507,-8.661],[-24.737,8.919]],"o":[[-1.051,0.507],[-0.641,3.682],[24.737,-8.919]],"v":[[-17.032,-12.737],[-65.957,17.111],[-14.127,-12.046]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.241,-0.117],[1.12,-8.698],[-0.79,0.285]],"o":[[-0.034,0.016],[-0.476,3.698],[0.79,-0.285]],"v":[[-2.581,-9.26],[-111.172,47.264],[-2.489,-9.238]],"c":true}]},{"t":100,"s":[{"i":[[0.004,-0.003],[0.158,-8.79],[-0.014,0.008]],"o":[[-0.001,0],[-0.067,3.737],[0.014,-0.008]],"v":[[-196.685,103.922],[-223.294,122.037],[-196.683,103.922]],"c":true}]}]},"nm":"Path 23","hd":false},{"ind":23,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.652,-0.002],[9.621,-23.195],[-30.797,11.034]],"o":[[-0.652,0.002],[1.201,4.343],[30.797,-11.034]],"v":[[-5.938,-16.534],[-91.485,36.162],[-4.524,-15.646]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[1.764,-0.005],[34.94,-49.93],[-83.311,29.848]],"o":[[-1.764,0.005],[-2.448,3.499],[83.311,-29.848]],"v":[[-20.857,-15.139],[-161.337,74.163],[-17.032,-12.737]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.056,0],[23.404,-37.749],[-2.66,0.953]],"o":[[-0.056,0],[-0.786,3.883],[2.66,-0.953]],"v":[[-2.437,-9.678],[-180.879,84.077],[-2.314,-9.602]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0],[-5.203,-7.542],[-0.05,0.018]],"o":[[-0.001,0],[3.337,4.837],[0.05,-0.018]],"v":[[-175.952,77.399],[-229.337,108.663],[-175.95,77.401]],"c":true}]}]},"nm":"Path 24","hd":false},{"ind":24,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[3.079,-0.51],[0.18,-8.491],[-14.645,0.239]],"o":[[-3.079,0.51],[-0.099,4.7],[14.645,-0.239]],"v":[[-8.996,-17.288],[-60.714,-4.682],[-6.253,-16.534]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[8.33,-1.38],[0.18,-8.491],[-39.617,0.647]],"o":[[-8.33,1.38],[-0.099,4.7],[39.617,-0.647]],"v":[[-28.279,-17.178],[-119.213,6.829],[-20.857,-15.139]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.266,-0.044],[0.18,-8.491],[-1.265,0.021]],"o":[[-0.266,0.044],[-0.099,4.7],[1.265,-0.021]],"v":[[-3.288,-10.423],[-157.506,13.654],[-3.051,-10.357]],"c":true}]},{"t":100,"s":[{"i":[[0.005,-0.001],[0.18,-8.491],[-0.024,0]],"o":[[-0.005,0.001],[-0.099,4.7],[0.024,0]],"v":[[-223.707,24.648],[-252.463,30.579],[-223.702,24.649]],"c":true}]}]},"nm":"Path 25","hd":false},{"ind":25,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[5.002,-0.018],[4.376,-11.742],[-21.034,-0.505]],"o":[[-5.002,0.018],[-1,2.682],[21.034,0.505]],"v":[[-8.482,-18.09],[-80.041,-5.651],[-8.996,-16.972]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[13.533,-0.05],[4.376,-11.742],[-56.9,-1.366]],"o":[[-13.533,0.05],[-1,2.682],[56.9,1.366]],"v":[[-26.888,-20.202],[-133.179,-1.393],[-28.279,-17.178]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.432,-0.002],[4.376,-11.742],[-1.817,-0.044]],"o":[[-0.432,0.002],[-1,2.682],[1.817,0.044]],"v":[[-2.957,-10.802],[-167.52,2.271],[-3.002,-10.706]],"c":true}]},{"t":100,"s":[{"i":[[0.008,0],[4.376,-11.742],[-0.034,-0.001]],"o":[[-0.008,0],[-1,2.682],[0.034,0.001]],"v":[[-201.456,2.646],[-252.679,11.357],[-201.457,2.648]],"c":true}]}]},"nm":"Path 26","hd":false},{"ind":26,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.015,0.648],[9.285,-5.938],[-12.687,-1.577]],"o":[[-1.837,-0.296],[-5.457,3.49],[12.687,1.577]],"v":[[-7.193,-19.109],[-55.539,-18.566],[-8.167,-18.09]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[10.862,1.752],[9.285,-5.938],[-34.32,-4.266]],"o":[[-4.969,-0.802],[-5.457,3.49],[34.32,4.266]],"v":[[-24.253,-22.959],[-119.714,-21.877],[-26.888,-20.202]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.347,0.056],[9.285,-5.938],[-1.096,-0.136]],"o":[[-0.159,-0.026],[-5.457,3.49],[1.096,0.136]],"v":[[-3.256,-11.254],[-157.719,-22.883],[-3.34,-11.166]],"c":true}]},{"t":100,"s":[{"i":[[0.007,0.001],[9.285,-5.938],[-0.021,-0.003]],"o":[[-0.003,0],[-5.457,3.49],[0.021,0.003]],"v":[[-231.204,-25.606],[-251.964,-25.377],[-231.206,-25.604]],"c":true}]}]},"nm":"Path 27","hd":false},{"ind":27,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.393,1.499],[5.601,-5.319],[-6.035,-3.074]],"o":[[-4.393,-1.499],[-2.043,1.94],[7.22,3.678]],"v":[[-5.608,-19.397],[-48.68,-27.903],[-7.508,-19.109]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[11.885,4.055],[5.601,-5.319],[-16.326,-8.316]],"o":[[-11.885,-4.055],[-2.043,1.94],[19.531,9.948]],"v":[[-19.113,-23.736],[-77.851,-34.052],[-24.253,-22.959]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.38,0.13],[5.601,-5.319],[-0.521,-0.266]],"o":[[-0.38,-0.129],[-2.043,1.94],[0.624,0.318]],"v":[[-2.828,-11.723],[-125.77,-43.679],[-2.992,-11.698]],"c":true}]},{"t":100,"s":[{"i":[[0.007,0.002],[5.601,-5.319],[-0.01,-0.005]],"o":[[-0.007,-0.002],[-2.043,1.94],[0.012,0.006]],"v":[[-210.701,-60.106],[-244.601,-67.552],[-210.704,-60.106]],"c":true}]}]},"nm":"Path 28","hd":false},{"ind":28,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[5.221,2.81],[2.362,-11.3],[-20.382,-17.294]],"o":[[-5.221,-2.81],[-1.358,6.576],[20.382,17.294]],"v":[[-7.193,-23.937],[-71.1,-48.972],[-5.293,-19.397]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[14.123,7.602],[4.156,-15.533],[-55.138,-46.784]],"o":[[-14.123,-7.602],[-2.317,8.659],[55.138,46.784]],"v":[[-24.253,-36.018],[-168.703,-94.857],[-19.113,-23.736]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.451,0.243],[3.339,-13.604],[-1.761,-1.494]],"o":[[-0.451,-0.243],[-1.88,7.71],[1.761,1.494]],"v":[[-2.925,-12.804],[-184.078,-101.754],[-2.761,-12.412]],"c":true}]},{"t":100,"s":[{"i":[[0.008,0.005],[1.312,-8.822],[-0.033,-0.028]],"o":[[-0.008,-0.005],[-0.797,5.357],[0.033,0.028]],"v":[[-205.454,-113.613],[-222.203,-118.857],[-205.451,-113.606]],"c":true}]}]},"nm":"Path 29","hd":false},{"ind":29,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[7.26,4.445],[7.011,-4.124],[-10.385,-10.766]],"o":[[-0.326,-0.199],[-7.011,4.124],[16.897,17.517]],"v":[[-5.801,-24.959],[-56.419,-56.973],[-7.193,-23.937]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[19.64,12.023],[7.011,-4.124],[-28.094,-29.123]],"o":[[-0.881,-0.54],[-7.011,4.124],[45.711,47.385]],"v":[[-20.488,-38.784],[-98.519,-86.459],[-24.253,-36.018]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.627,0.384],[7.011,-4.124],[-0.897,-0.93]],"o":[[-0.028,-0.017],[-7.011,4.124],[1.46,1.513]],"v":[[-2.277,-13.13],[-127.401,-106.145],[-2.397,-13.042]],"c":true}]},{"t":100,"s":[{"i":[[0.012,0.007],[7.011,-4.124],[-0.017,-0.018]],"o":[[-0.001,0],[-7.011,4.124],[0.028,0.029]],"v":[[-164.452,-132.115],[-199.019,-154.959],[-164.454,-132.113]],"c":true}]}]},"nm":"Path 30","hd":false},{"ind":30,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[23.866,20.95],[-0.775,-4.182],[-8.843,-10.116]],"o":[[-23.866,-20.95],[0.799,4.308],[8.843,10.116]],"v":[[-4.127,-25.389],[-47.359,-57.266],[-6.116,-25.275]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[64.562,56.675],[-0.775,-4.182],[-23.921,-27.366]],"o":[[-64.562,-56.675],[0.799,4.308],[23.921,27.366]],"v":[[-15.108,-39.093],[-105.227,-105.674],[-20.488,-38.784]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[2.062,1.81],[-0.775,-4.182],[-0.764,-0.874]],"o":[[-2.062,-1.81],[0.799,4.308],[0.764,0.874]],"v":[[-2.169,-13.514],[-128.217,-124.928],[-2.341,-13.504]],"c":true}]},{"t":100,"s":[{"i":[[0.039,0.034],[-0.775,-4.182],[-0.014,-0.016]],"o":[[-0.039,-0.034],[0.799,4.308],[0.014,0.016]],"v":[[-169.449,-161.115],[-185.227,-172.674],[-169.452,-161.115]],"c":true}]}]},"nm":"Path 31","hd":false},{"ind":31,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.861,0.835],[6.082,-5.676],[-10.296,-14.684]],"o":[[-0.861,-0.835],[-2.893,2.699],[10.296,14.684]],"v":[[-0.366,-23.104],[-55.133,-75.787],[-4.127,-25.389]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[2.329,2.258],[6.082,-5.676],[-27.852,-39.724]],"o":[[-2.329,-2.258],[-2.893,2.699],[27.852,39.724]],"v":[[-4.933,-32.911],[-79.889,-105.589],[-15.108,-39.093]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.074,0.072],[6.082,-5.676],[-0.889,-1.268]],"o":[[-0.074,-0.072],[-2.893,2.699],[0.889,1.268]],"v":[[-1.214,-13.207],[-103.885,-130.878],[-1.539,-13.404]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0.001],[6.082,-5.676],[-0.017,-0.024]],"o":[[-0.001,-0.001],[-2.893,2.699],[0.017,0.024]],"v":[[-120.443,-152.612],[-163.389,-193.589],[-120.449,-152.615]],"c":true}]}]},"nm":"Path 32","hd":false},{"ind":32,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[3.542,19.181],[3.073,-0.981],[-0.152,-9.722]],"o":[[-3.542,-19.181],[-2.453,0.783],[0.152,9.722]],"v":[[0.539,-22.899],[-14.028,-61.928],[-0.366,-22.788]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[9.581,51.887],[3.073,-0.981],[-0.412,-26.3]],"o":[[-9.581,-51.887],[-2.453,0.783],[0.412,26.3]],"v":[[-2.483,-33.211],[-31.215,-116.327],[-4.933,-32.911]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.306,1.657],[3.073,-0.981],[-0.013,-0.84]],"o":[[-0.306,-1.657],[-2.453,0.783],[0.013,0.84]],"v":[[-0.517,-14.182],[-44.865,-152.105],[-0.596,-14.172]],"c":true}]},{"t":100,"s":[{"i":[[0.006,0.031],[3.073,-0.981],[0,-0.016]],"o":[[-0.006,-0.031],[-2.453,0.783],[0,0.016]],"v":[[-72.441,-227.612],[-78.715,-240.827],[-72.443,-227.612]],"c":true}]}]},"nm":"Path 33","hd":false},{"ind":33,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.512,3.311],[7.408,-2.515],[0.879,-9.578]],"o":[[-0.512,-3.311],[-4.274,1.451],[-0.879,9.578]],"v":[[1.755,-24.526],[-15.758,-89.049],[0.539,-22.899]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[1.386,8.956],[7.408,-2.515],[2.377,-25.91]],"o":[[-1.386,-8.956],[-4.274,1.451],[-2.377,25.91]],"v":[[0.805,-37.612],[-23.169,-137.772],[-2.483,-33.211]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.044,0.286],[7.408,-2.515],[0.076,-0.827]],"o":[[-0.044,-0.286],[-4.274,1.451],[-0.076,0.827]],"v":[[0.051,-14.02],[-30.928,-169.383],[-0.054,-13.879]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0.005],[7.408,-2.515],[0.001,-0.016]],"o":[[-0.001,-0.005],[-4.274,1.451],[-0.001,0.016]],"v":[[-36.439,-204.114],[-50.169,-247.772],[-36.441,-204.112]],"c":true}]}]},"nm":"Path 34","hd":false},{"ind":34,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.275,0.477],[4.481,1.632],[2.961,-6.55],[-1.425,0.478]],"o":[[-2.55,-0.954],[-6.936,-2.526],[-1.48,3.275],[1.425,-0.478]],"v":[[5.014,-23.954],[2.623,-65.932],[2.07,-24.842],[3.754,-13.991]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.449,1.291],[4.481,1.632],[8.009,-17.719],[-3.855,1.292]],"o":[[-6.899,-2.581],[-6.936,-2.526],[-4.004,8.859],[3.855,-1.292]],"v":[[8.769,-35.211],[4.357,-117.808],[0.805,-37.612],[5.361,-8.261]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.11,0.041],[4.481,1.632],[0.256,-0.566],[-0.123,0.041]],"o":[[-0.22,-0.082],[-6.936,-2.526],[-0.128,0.283],[0.123,-0.041]],"v":[[0.788,-14.342],[3.064,-156.748],[0.534,-14.419],[0.679,-13.482]],"c":true}]},{"t":100,"s":[{"i":[[0.002,0.001],[4.481,1.632],[0.005,-0.011],[-0.002,0.001]],"o":[[-0.004,-0.002],[-6.936,-2.526],[-0.002,0.005],[0.002,-0.001]],"v":[[1.066,-235.113],[-0.143,-253.308],[1.061,-235.114],[1.064,-235.097]],"c":true}]}]},"nm":"Path 35","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":7,"k":{"a":0,"k":[0.166,1,1,1,0.291,1,0.986,0.894,0.379,1,0.973,0.788,0.522,0.961,0.876,0.394,0.708,0.922,0.78,0,0.865,0.961,0.888,0.206,0.999,1,0.996,0.412,0.166,1,0.291,1,0.379,1,0.522,0.9,0.708,0.8,0.865,0.8,0.999,0.8]}},"s":{"a":0,"k":[-12,-18]},"e":{"a":0,"k":[-123.596,-129.596]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-6.793,2.506]},"a":{"a":0,"k":[-6.793,2.506]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"white splashes precomp","refId":"comp_2","sr":1,"ks":{"p":{"a":0,"k":[243,243,0]},"a":{"a":0,"k":[243,243,0]}},"ao":0,"w":486,"h":486,"ip":-4,"op":99,"st":-5,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"ogonek5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[1],"y":[0]},"t":30,"s":[100]},{"t":45,"s":[0]}]},"p":{"a":0,"k":[241,232.25,0]},"a":{"a":0,"k":[-51,43,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,15.828]},"t":30,"s":[0,0,100]},{"t":45,"s":[151,151,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[164,164]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":7,"k":{"a":0,"k":[0.166,1,1,1,0.291,1,0.986,0.894,0.379,1,0.973,0.788,0.522,0.961,0.876,0.394,0.708,0.922,0.78,0,0.865,0.961,0.888,0.206,0.999,1,0.996,0.412,0.166,1,0.291,1,0.379,1,0.522,0.9,0.708,0.8,0.865,0.8,0.999,0.8]}},"s":{"a":0,"k":[1,0.5]},"e":{"a":0,"k":[-61.596,-53.096]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 7","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,43]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":30,"op":45,"st":-45,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"rose splashes 80%","sr":1,"ks":{"o":{"a":0,"k":80},"p":{"a":0,"k":[283.287,244.6,0]},"a":{"a":0,"k":[40.287,1.6,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[8.458,2.584],[-1.178,1.927]],"o":[[0,0],[1.178,-1.927]],"v":[[47.396,-2.495],[66.132,8.305]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[17.915,5.472],[-2.495,4.082]],"o":[[0,0],[2.494,-4.082]],"v":[[127.416,14.655],[167.1,37.528]],"c":true}]},{"t":100,"s":[{"i":[[2.922,0.892],[-0.407,0.666]],"o":[[0,0],[0.407,-0.666]],"v":[[231.544,79.714],[238.016,83.445]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-7.371,11.75],[-2.605,-2.954]],"o":[[0,0],[2.605,2.954]],"v":[[-19.417,21.61],[-33.613,50.565]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-16.511,26.322],[-5.835,-6.617]],"o":[[0,0],[5.835,6.617]],"v":[[-54.289,78.774],[-86.09,143.637]],"c":true}]},{"t":100,"s":[{"i":[[-2.019,3.219],[-0.714,-0.809]],"o":[[0,0],[0.714,0.809]],"v":[[-120,208.391],[-123.889,216.322]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.272,18.752],[3.489,0]],"o":[[0,0],[-2.919,0]],"v":[[13.337,36.564],[18.266,63.997]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[9.5,41.701],[7.758,0]],"o":[[0,0],[-6.491,0]],"v":[[34.44,120.578],[45.401,181.583]],"c":true}]},{"t":100,"s":[{"i":[[1.211,5.316],[0.989,0]],"o":[[0,0],[-0.827,0]],"v":[[49.981,239.625],[51.378,247.402]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[5.357,10.649],[2.065,-1.671]],"o":[[0,0],[-2.092,1.693]],"v":[[27.785,38.344],[37.752,60.836]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[12.682,25.21],[4.889,-3.955]],"o":[[0,0],[-4.953,4.007]],"v":[[73.965,124.33],[97.561,177.576]],"c":true}]},{"t":100,"s":[{"i":[[1.068,2.124],[0.412,-0.333]],"o":[[0,0],[-0.417,0.338]],"v":[[112.746,219.25],[114.734,223.735]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[7.125,6.764],[2.818,-3.288]],"o":[[0,0],[-1.952,2.278]],"v":[[39.186,19.394],[56.431,42.171]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[16.508,15.671],[6.529,-7.617]],"o":[[0,0],[-4.523,5.277]],"v":[[103.804,73.868],[143.76,126.64]],"c":true}]},{"t":100,"s":[{"i":[[1.631,1.548],[0.645,-0.753]],"o":[[0,0],[-0.447,0.521]],"v":[[178.352,168.75],[182.3,173.964]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[14.003,-18.483],[-2.995,-2.598]],"o":[[0,0],[2.346,2.035]],"v":[[19.896,-54.149],[37.88,-77.508]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[31.33,-41.355],[-6.701,-5.813]],"o":[[0,0],[5.249,4.553]],"v":[[52.828,-125.71],[93.066,-177.974]],"c":true}]},{"t":100,"s":[{"i":[[3.858,-5.092],[-0.825,-0.716]],"o":[[0,0],[0.646,0.561]],"v":[[119.615,-214],[124.57,-220.435]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.092,-13.223],[3.225,-0.461]],"o":[[0,0],[-3.225,0.461]],"v":[[-11.7,-53.501],[-23.447,-74.576]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-13.694,-29.721],[7.248,-1.036]],"o":[[0,0],[-7.248,1.036]],"v":[[-33.744,-123.528],[-60.149,-170.902]],"c":true}]},{"t":100,"s":[{"i":[[-1.641,-3.562],[0.869,-0.124]],"o":[[0,0],[-0.869,0.124]],"v":[[-92.793,-229.25],[-95.958,-234.928]],"c":true}]}]},"nm":"Path 7","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.996078431373,0.403921568627,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 7","hd":false},{"ty":"tr","p":{"a":0,"k":[40.287,1.6]},"a":{"a":0,"k":[40.287,1.6]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"rose splashes 40%","sr":1,"ks":{"o":{"a":0,"k":40},"p":{"a":0,"k":[255.462,250.532,0]},"a":{"a":0,"k":[12.462,7.532,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.498,-1.717],[-2.802,-3.967]],"o":[[-1.32,0.907],[0,0]],"v":[[-27.909,-69.864],[-23.485,-62.918]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[5.583,-3.836],[-6.261,-8.864]],"o":[[-2.949,2.026],[0,0]],"v":[[-77.365,-168.395],[-67.479,-152.872]],"c":true}]},{"t":100,"s":[{"i":[[0.692,-0.476],[-0.776,-1.099]],"o":[[-0.366,0.251],[0,0]],"v":[[-104.452,-204.425],[-103.226,-202.5]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.758,-1.202],[3.089,-1.172]],"o":[[0,0],[-3.089,1.172]],"v":[[-40.584,-41.866],[-51.165,-45.778]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-10.775,-2.722],[6.996,-2.654]],"o":[[0,0],[-6.996,2.654]],"v":[[-111.836,-92.023],[-135.799,-100.881]],"c":true}]},{"t":100,"s":[{"i":[[-1.235,-0.312],[0.802,-0.304]],"o":[[0,0],[-0.802,0.304]],"v":[[-194.867,-116.25],[-197.612,-117.265]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.418,17.711],[-4.713,-1.091]],"o":[[0,0],[2.758,0.638]],"v":[[-22.297,35.041],[-30.452,64.628]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-14.835,40.938],[-10.895,-2.522]],"o":[[0,0],[6.375,1.475]],"v":[[-62.088,115.52],[-80.938,183.907]],"c":true}]},{"t":100,"s":[{"i":[[-1.49,4.112],[-1.094,-0.253]],"o":[[0,0],[0.64,0.148]],"v":[[-89,203.172],[-90.893,210.041]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.125,2.009],[-1.205,-2.41]],"o":[[-0.644,-1.15],[1.205,2.41]],"v":[[60.208,34.488],[58.28,36.577]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[2.266,4.046],[-2.428,-4.856]],"o":[[-1.297,-2.316],[2.428,4.856]],"v":[[161.46,114.372],[157.576,118.58]],"c":true}]},{"t":100,"s":[{"i":[[0.457,0.815],[-0.489,-0.978]],"o":[[-0.261,-0.467],[0.489,0.978]],"v":[[178.425,143.467],[177.643,144.315]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.378,1.888],[-0.054,-1.294]],"o":[[-0.244,-1.218],[0.054,1.294]],"v":[[57.201,-37.513],[53.318,-36.704]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[0.721,3.602],[-0.103,-2.47]],"o":[[-0.465,-2.323],[0.103,2.47]],"v":[[152.987,-80.688],[145.577,-79.144]],"c":true}]},{"t":100,"s":[{"i":[[0.177,0.884],[-0.025,-0.606]],"o":[[-0.114,-0.57],[0.025,0.606]],"v":[[207.62,-96.485],[205.801,-96.106]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.033,1.484],[-0.082,-1.894]],"o":[[0.031,-1.392],[0.082,1.894]],"v":[[53.595,-30.294],[48.408,-29.669]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-0.074,3.328],[-0.185,-4.247]],"o":[[0.069,-3.121],[0.185,4.247]],"v":[[143.792,-61.221],[132.159,-59.821]],"c":true}]},{"t":100,"s":[{"i":[[-0.009,0.404],[-0.022,-0.516]],"o":[[0.008,-0.379],[0.022,0.516]],"v":[[216.285,-76.436],[214.873,-76.266]],"c":true}]}]},"nm":"Path 6","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.996078431373,0.403921568627,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 6","hd":false},{"ty":"tr","p":{"a":0,"k":[12.462,7.532]},"a":{"a":0,"k":[12.462,7.532]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"violet","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[33.333]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[100]},{"t":70,"s":[0]}]},"p":{"a":0,"k":[250.172,221.61,0]},"a":{"a":0,"k":[7.172,-21.39,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.629,0.629,0.629],"y":[0,0,0]},"t":30,"s":[43.235,43.235,100]},{"t":40,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[8.424,-5.071],[2.653,-10.693],[8.493,-0.25],[-0.281,-21.575],[8.206,2.479],[-9.071,-18.319],[10.163,-0.826],[-8.504,-13.367],[7.925,-6.142],[-9.44,-13.344],[2.854,-8.942],[-21.641,-24.17],[6.753,-1.287],[-6.374,-4.022],[10.533,-2.098],[-13.32,-3.511],[-1.839,-6.483],[-21.075,-3.827],[-5.954,-6.816],[-11.932,-2.571],[3.257,-17.575],[-38.942,9.134],[-0.325,-7.706],[-8.742,1.73],[-8.801,-7.785],[-30.134,14.717],[-9.382,-4.129],[-16.279,6.661],[1.088,-8.515],[-7.687,7.097],[-6.012,-8.868],[-10.651,19.166],[-10.35,0.696],[-9.579,21.376],[-8.09,4.605],[-5.131,22.602],[-3.808,-4.758],[-0.748,8.961],[-7.254,-2.788],[2.909,7.968],[-7.635,-1.047],[9.08,17.754],[-7.507,3.193],[9.997,10.579],[-6.351,4.692],[9.781,8.968],[0.178,6.817],[17.454,12.482],[0.03,6.973],[12.265,1.257],[-5.162,4.439],[17.018,1.365],[-5.218,7.12],[24.328,-1.527],[-3.121,9.666],[14.883,-6.778],[10.864,5.993],[11.338,-8.563],[5.168,9.097],[9.336,-10.339],[5.422,9.555],[17.401,-17.591],[8.409,7.467],[2.648,-7.068],[8.104,5.903],[5.776,-17.779],[7.39,-3.513],[1.572,-7.178],[9.221,3.529],[4.343,-6.683]],"o":[[-7.125,5.075],[-0.627,-20.281],[-8.492,0.25],[-7.605,-11.419],[-6.68,-3.509],[-2.4,-2.857],[-8.37,0.053],[-2.455,-2.437],[-7.318,5.151],[-25.313,-18.266],[-0.866,7.957],[-15.111,-8.219],[-6.731,1.147],[0,0],[-8.775,1.322],[-36.623,-3.316],[2.35,6.725],[-4.284,0.119],[5.62,5.119],[0,0],[-0.325,7.953],[-14.317,6.526],[0.333,6.979],[-5.382,3.713],[7.32,4.607],[-4.424,3.372],[7.613,2.532],[-10.42,10.905],[-1.668,7.104],[-2.075,4.92],[4.11,6.36],[-1.424,4.903],[7.699,-1.121],[0,0],[8.176,-4.621],[-1.052,23.086],[4.14,4.652],[5.615,9.885],[7.25,2.786],[5.203,5.546],[7.441,1.093],[15.761,17.732],[7.507,-3.193],[5.904,3.732],[6.351,-4.692],[9.84,5.408],[-0.622,-6.552],[19.337,4.71],[0.329,-7.351],[6.418,-1.574],[5.14,-4.409],[27.522,-2.876],[5.215,-7.117],[4.426,-6.277],[2.875,-9.309],[1.399,-5.562],[-7.079,-5.34],[4.34,-9.001],[-5.002,-8.493],[13.638,-18.255],[-5.312,-9.225],[0.333,-7.822],[-7.414,-6.268],[3.497,-13.498],[-8.104,-5.903],[-0.973,-5.144],[-8.335,3.293],[-2.994,-1.674],[-9.108,-3.513],[-0.006,-3.457]],"v":[[5.228,-95.512],[7.462,-28.929],[-2.195,-87.293],[6.416,-28.961],[-31.261,-81.602],[2.547,-32.204],[-43.849,-81.375],[0.273,-32.419],[-44.643,-71.2],[-1.483,-31.501],[-68.757,-71.437],[-0.116,-26.024],[-42.303,-41.201],[-2.32,-26.028],[-60.656,-35.365],[-6.28,-25.891],[-67.569,-24.781],[-6.096,-24.791],[-61.84,-18.285],[-3.524,-23.253],[-83.167,12.168],[-3.472,-21.52],[-43.616,2.576],[-3.348,-19.306],[-64.141,31.565],[0.002,-18.262],[-50.895,27.819],[3.739,-17.431],[-29.038,27.111],[2.554,-14.294],[-16.457,46.001],[4.192,-11.614],[-10.643,61.853],[6.528,-12.171],[7.719,52.216],[8.895,-14.393],[13.275,34.112],[10.222,-12.355],[26.825,31.666],[12.417,-11.04],[35.999,36.579],[14.675,-10.481],[49.25,44.887],[15.923,-11.135],[54.41,30.884],[17.017,-12.053],[59.976,24.143],[15.598,-15.029],[60.134,6.365],[15.406,-16.076],[56.972,-8.479],[17.766,-17.986],[90.136,-9.191],[19.955,-19.743],[79.759,-35.208],[20.474,-22.64],[63.92,-44.118],[18.736,-24.334],[63.006,-55.052],[17.431,-26.232],[77.149,-67.057],[16.295,-27.947],[47.537,-73.383],[14.878,-29.037],[41.274,-77.714],[13.733,-29.91],[25.971,-72.901],[12.306,-29.047],[19.524,-85.462],[9.122,-30.564]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[8.849,0.01],[7.184,-28.957],[3.052,-1.273],[-0.761,-58.423],[7.839,-5.353],[-24.563,-49.607],[9.192,-3.975],[-23.029,-36.198],[4.861,-7.946],[-25.563,-36.135],[12.25,-6.125],[-58.602,-65.452],[0.423,-3.502],[-17.26,-10.89],[11.497,-5.096],[-36.071,-9.509],[-2.201,-1.041],[-57.07,-10.364],[-1.998,-10.013],[-32.312,-6.962],[9.36,-30.715],[-105.454,24.735],[0.047,-4.178],[-23.672,4.686],[-7.461,-15.968],[-81.601,39.854],[-8.157,-7.347],[-44.083,18.039],[-3.026,-7.306],[-20.815,19.217],[-8.186,-10.792],[-28.842,51.901],[-12.022,-1.924],[-25.94,57.885],[-10.365,1.613],[-13.894,61.205],[-1.581,0.481],[-2.025,24.266],[-5.413,1.006],[7.876,21.576],[-4.573,1.132],[24.589,48.078],[-6.41,1.365],[27.072,28.648],[-4.858,2.346],[26.485,24.286],[-6.451,3.968],[47.266,33.8],[-2.771,2.906],[33.213,3.403],[-1.167,1.667],[46.083,3.696],[-1.333,9],[65.879,-4.134],[-5.083,7.361],[40.303,-18.354],[15.513,2.68],[30.702,-23.187],[1.943,7.096],[25.282,-27.999],[2.5,7.5],[47.12,-47.637],[6.155,7.421],[7.17,-19.139],[5.169,3.176],[15.641,-48.146],[3.103,0.727],[4.257,-19.437],[6.927,0.949],[11.76,-18.098]],"o":[[-5.323,-0.006],[-1.697,-54.919],[-3.052,1.273],[-20.594,-30.923],[-3.728,2.546],[-6.498,-7.737],[-4.346,1.879],[-6.647,-6.6],[-3.22,5.264],[-68.548,-49.463],[-6.872,3.436],[-40.921,-22.258],[-0.377,3.121],[0,0],[-6.756,2.995],[-99.173,-8.979],[3.585,1.695],[-11.602,0.322],[1.08,5.412],[0,0],[-1.42,4.661],[-38.77,17.671],[-0.025,2.213],[-14.575,10.054],[3.439,7.359],[-11.98,9.131],[3.354,3.021],[-28.218,29.53],[1.451,3.503],[-5.619,13.324],[3.041,4.009],[-3.855,13.277],[4.839,0.774],[0,0],[10.589,-1.648],[-2.849,62.516],[2.488,-0.757],[15.206,26.767],[5.413,-1.006],[14.09,15.019],[4.055,-1.004],[42.68,48.017],[6.41,-1.365],[15.989,10.107],[4.858,-2.346],[26.647,14.644],[5.257,-3.234],[52.364,12.754],[3.745,-3.927],[17.38,-4.263],[1.115,-1.592],[74.53,-7.789],[1.333,-9],[11.985,-16.998],[4.419,-6.4],[3.79,-15.061],[-5.258,-0.908],[11.752,-24.375],[-1.495,-5.458],[36.932,-49.434],[-2.202,-6.607],[0.901,-21.183],[-3.464,-4.176],[9.469,-36.552],[-5.169,-3.176],[-2.634,-13.929],[-5.658,-1.325],[-8.107,-4.533],[-6.628,-0.908],[-0.017,-9.362]],"v":[[3.02,-162.999],[8.243,-41.492],[-9.448,-140.273],[5.428,-41.577],[-66.581,-136.474],[-4.982,-50.303],[-94.937,-141.294],[-11.103,-50.883],[-91.631,-117.558],[-15.827,-48.412],[-150.75,-126.625],[-12.148,-33.673],[-76.677,-54.762],[-18.079,-33.683],[-125.935,-48.926],[-28.737,-33.315],[-144.516,-27.935],[-28.241,-30.355],[-129.957,-16.393],[-21.32,-26.218],[-189.443,52.219],[-21.179,-21.553],[-84.613,22.128],[-20.845,-15.595],[-143.927,91.798],[-11.831,-12.788],[-109.552,77.33],[-1.776,-10.549],[-60.574,69.369],[-4.963,-2.109],[-41.055,117.272],[-0.555,5.102],[-30.196,155.83],[5.729,3.604],[8.665,130.425],[12.099,-2.376],[18.32,81.416],[15.669,3.108],[41.962,76.131],[21.577,6.648],[62.49,91.451],[27.654,8.15],[95.923,118.365],[31.011,6.393],[102.975,84.18],[33.954,3.923],[112.957,71.446],[30.136,-4.087],[107.438,31.594],[29.62,-6.903],[94.5,0.667],[35.97,-12.045],[183.167,5],[41.86,-16.772],[147.562,-47.507],[43.258,-24.567],[106.809,-61.148],[38.582,-29.125],[103.057,-80.596],[35.068,-34.233],[141.167,-109],[32.012,-38.849],[76.865,-113.118],[28.198,-41.782],[66.503,-119.657],[25.119,-44.132],[37.324,-105.068],[21.279,-41.807],[29.931,-135.604],[12.71,-45.889]],"c":true}]},{"t":100,"s":[{"i":[[6.52,-0.499],[0,0],[7.552,-1.773],[0,0],[7.419,-3.974],[0,0],[5.063,-2.794],[0,0],[3.869,-4.058],[0,0],[3.25,-5.125],[0,0],[2.323,-7.262],[0,0],[1.065,-7.426],[0,0],[-0.016,-6.935],[0,0],[0.043,-8.393],[0,0],[-1.943,-5.281],[0,0],[-4.613,-6.872],[0,0],[-4.427,-4.702],[0,0],[-5.552,-5.67],[0,0],[-7.074,-5.131],[0,0],[-7.555,-2.728],[0,0],[-8.696,0.33],[0,0],[-5.835,-0.075],[0,0],[-5.179,-0.584],[0,0],[-7.538,4.131],[0,0],[-4.51,1.951],[0,0],[-7.077,4.865],[0,0],[-5.025,3.68],[0,0],[-5.043,6.946],[0,0],[-2.062,4.094],[0,0],[-1.5,7.666],[0,0],[-1.333,7.5],[0,0],[-0.438,6.493],[0,0],[2.309,7.852],[0,0],[1.057,4.404],[0,0],[1.667,6.5],[0,0],[6.365,6.382],[0,0],[3.503,2.843],[0,0],[5.324,3.432],[0,0],[2.931,0.896],[0,0]],"o":[[-6.521,0.499],[0,0],[-7.552,1.773],[0,0],[-7.419,3.974],[0,0],[-5.063,2.794],[0,0],[-3.869,4.058],[0,0],[-3.25,5.125],[0,0],[-2.323,7.262],[0,0],[-1.065,7.426],[0,0],[0.016,6.935],[0,0],[-0.043,8.393],[0,0],[1.943,5.281],[0,0],[4.613,6.872],[0,0],[4.427,4.702],[0,0],[5.552,5.67],[0,0],[7.074,5.131],[0,0],[7.555,2.728],[0,0],[8.696,-0.33],[0,0],[5.835,0.075],[0,0],[5.18,0.584],[0,0],[7.538,-4.131],[0,0],[4.51,-1.951],[0,0],[7.077,-4.865],[0,0],[5.025,-3.68],[0,0],[5.043,-6.946],[0,0],[2.062,-4.094],[0,0],[1.5,-7.666],[0,0],[1.333,-7.5],[0,0],[0.438,-6.493],[0,0],[-2.309,-7.852],[0,0],[-1.057,-4.404],[0,0],[-1.667,-6.5],[0,0],[-6.365,-6.382],[0,0],[-3.503,-2.843],[0,0],[-5.324,-3.432],[0,0],[-2.931,-0.896],[0,0]],"v":[[0.021,-238.999],[6.522,-22.25],[-22.948,-238.773],[6.486,-22.251],[-111.081,-205.974],[6.353,-22.363],[-136.437,-188.794],[6.274,-22.37],[-151.131,-175.058],[6.214,-22.339],[-176.75,-144.125],[6.261,-22.15],[-199.177,-102.262],[6.185,-22.15],[-209.935,-65.926],[6.048,-22.145],[-215.016,-31.435],[6.055,-22.107],[-215.457,-11.893],[6.143,-22.054],[-201.443,57.219],[6.145,-21.994],[-194.113,74.128],[6.149,-21.918],[-171.427,112.298],[6.265,-21.882],[-163.052,122.83],[6.394,-21.853],[-126.074,156.869],[6.353,-21.745],[-65.055,189.772],[6.41,-21.653],[-38.196,196.33],[6.49,-21.672],[9.165,202.425],[6.572,-21.748],[31.821,199.416],[6.618,-21.678],[80.962,187.631],[6.693,-21.633],[105.49,178.451],[6.771,-21.613],[127.923,167.865],[6.814,-21.636],[157.975,144.18],[6.852,-21.668],[174.957,125.946],[6.803,-21.77],[204.438,84.094],[6.796,-21.806],[223.5,34.166],[6.878,-21.872],[227.667,12],[6.953,-21.933],[227.062,-61.507],[6.971,-22.033],[213.309,-104.148],[6.911,-22.091],[198.057,-137.596],[6.866,-22.157],[195.667,-144],[6.827,-22.216],[142.865,-200.118],[6.778,-22.254],[122.503,-211.157],[6.739,-22.284],[84.324,-230.568],[6.689,-22.254],[51.431,-240.604],[6.58,-22.306]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":80},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.001,0.788,0.314,0,0.707,0.776,0.349,0,1,0.765,0.384,0]}},"s":{"a":0,"k":[4,-20]},"e":{"a":0,"k":[177.39,-20]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 3","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0}]},{"id":"comp_2","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"white splashes null","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[244.121,231.173,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,15]},"t":0,"s":[10,10,100]},{"t":45,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"white splashes 16","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":-26.39},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[3.692,-18.443],[0.554,28.851],[-4.71,-0.153],[0.91,-34.729]],"o":[[0,0],[-0.811,-42.252],[4.48,0.146],[-0.566,21.614]],"v":[[3.698,-34.289],[7.153,-97.417],[3.704,-163.044],[9.887,-97.551]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[2.569,-224.224],[-0.708,-233.61],[4.557,-243.199],[6.236,-227.083]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"white splashes 15","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":-37.24},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[2.919,0.028],[0.458,3.186],[-3.113,-0.928],[-0.552,-2.173]],"o":[[-2.457,-0.024],[-0.45,-3.129],[2.517,0.751],[0.826,3.251]],"v":[[7.197,-189.998],[2.396,-196.671],[5.985,-201.733],[10.531,-196.769]],"c":true}]},{"t":105,"s":[{"i":[[0.715,0.672],[-0.218,2.085],[-1.681,-1.58],[0.356,-3.399]],"o":[[-1.528,-1.436],[0.24,-2.295],[2.49,2.34],[-0.102,0.975]],"v":[[3.804,-282.639],[1.73,-288.578],[5.061,-294.644],[6.124,-284.448]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[3.537,-26.73],[0.322,38.379],[-8.051,-1.852],[1.133,-39.172]],"o":[[0,0],[-0.423,-50.388],[5.402,1.243],[-0.739,25.566]],"v":[[8.88,-30.231],[9.352,-111.051],[8.877,-182.906],[15.292,-111.636]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[2.911,-258.66],[-0.366,-268.047],[4.899,-277.636],[6.578,-261.52]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"white splashes 14","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":-76.9},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[1.889,-0.602],[1.33,4.021],[-2.184,1.073],[-0.71,-4.999]],"o":[[-2.155,0.687],[-1.33,-4.021],[3.935,-1.933],[0.554,3.902]],"v":[[8.293,-195.666],[2.395,-202.581],[3.004,-212.136],[10.191,-204.071]],"c":true}]},{"t":105,"s":[{"i":[[0.715,0.672],[-0.218,2.085],[-1.681,-1.58],[0.356,-3.399]],"o":[[-1.528,-1.436],[0.24,-2.295],[2.49,2.34],[-0.102,0.975]],"v":[[3.084,-248.643],[1.01,-254.582],[4.342,-260.648],[5.404,-250.452]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0,0],[-1.319,42.909],[-4.42,3.366],[0.367,-39.751]],"o":[[0,0],[1.684,-54.774],[4.638,-3.532],[-0.457,49.575]],"v":[[9.093,-35.688],[13.821,-104.066],[9.104,-187.932],[19.987,-125.011]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[2.192,-224.664],[-1.086,-234.051],[4.179,-243.64],[5.858,-227.523]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"white splashes 13","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":247.61},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[1.106,-0.312],[0.854,3.094],[-1.739,0.347],[-0.484,-3.307]],"o":[[-1.879,0.53],[-0.854,-3.094],[2.606,-0.52],[0.423,2.893]],"v":[[16.044,-188.261],[11.553,-193.701],[12.489,-200.455],[17.091,-194.354]],"c":true}]},{"t":105,"s":[{"i":[[0.715,0.672],[-0.218,2.085],[-1.681,-1.58],[0.356,-3.399]],"o":[[-1.528,-1.436],[0.24,-2.295],[2.49,2.34],[-0.102,0.975]],"v":[[4.94,-258.315],[2.866,-264.254],[6.198,-270.321],[7.26,-260.124]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0,0],[0.386,32.409],[-2.865,-1.201],[0.359,-8.729]],"o":[[0,0],[-0.486,-40.852],[8.005,3.357],[-1.553,37.802]],"v":[[16.903,-34.891],[18.1,-112.135],[16.912,-182.742],[22.479,-110.91]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[2.192,-224.664],[-1.086,-234.051],[4.179,-243.64],[5.858,-227.523]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"white splashes 12","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":233.03},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[1.981,-0.058],[0.74,5.37],[-3.113,-0.709],[-0.16,-4.987]],"o":[[-1.929,0.057],[-0.74,-5.37],[3.113,0.709],[0.16,4.987]],"v":[[12.221,-233.342],[7.552,-244.188],[10.447,-254.057],[15.155,-243.212]],"c":true}]},{"t":105,"s":[{"i":[[0.715,0.672],[-0.218,2.085],[-1.681,-1.58],[0.356,-3.399]],"o":[[-1.528,-1.436],[0.24,-2.295],[2.49,2.34],[-0.102,0.975]],"v":[[9.651,-292.065],[7.577,-298.004],[10.908,-304.071],[11.971,-293.874]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0,0],[1.356,31.642],[-4.56,1.137],[-0.088,-53.09]],"o":[[4.684,-49.409],[-2.183,-50.944],[5.665,-1.413],[0.11,66.517]],"v":[[12.032,-26.322],[15.369,-148.176],[12.025,-227.456],[21.738,-151.886]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[6.902,-258.414],[3.625,-267.801],[8.89,-277.39],[10.569,-261.273]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"white splashes 11","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":214.81},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[1.507,0.148],[-0.026,1.131],[-1.431,-0.266],[-0.107,-1.096]],"o":[[-1.786,-0.176],[0.031,-1.343],[1.208,0.224],[0.127,1.295]],"v":[[4.516,-198.027],[2.064,-200.467],[4.571,-202.884],[6.56,-200.468]],"c":true}]},{"t":105,"s":[{"i":[[0.372,0.35],[-0.114,1.086],[-0.875,-0.823],[0.185,-1.769]],"o":[[-0.795,-0.747],[0.125,-1.194],[1.296,1.218],[-0.053,0.508]],"v":[[11.374,-296.43],[10.295,-299.521],[12.029,-302.679],[12.582,-297.371]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[1.573,-0.068],[-0.07,2.424],[-1.723,-0.616],[-0.046,-2.055]],"o":[[-2.138,0.092],[0.076,-2.642],[1.541,0.552],[0.052,2.348]],"v":[[4.364,-207.182],[1.458,-211.938],[4.41,-216.605],[6.732,-211.938]],"c":true}]},{"t":105,"s":[{"i":[[0.715,0.672],[-0.218,2.085],[-1.681,-1.58],[0.356,-3.399]],"o":[[-1.528,-1.436],[0.24,-2.295],[2.49,2.34],[-0.102,0.975]],"v":[[12.529,-308.36],[10.456,-314.299],[13.787,-320.366],[14.849,-310.169]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0,0],[1,27.626],[-5.456,-2.345],[0.879,-39.91]],"o":[[1.509,-38.059],[-1.888,-52.164],[5.09,2.188],[-1.019,46.266]],"v":[[5.034,-13.727],[5.006,-113.095],[5.033,-193.482],[10.403,-114.654]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[9.781,-274.709],[6.503,-284.096],[11.769,-293.685],[13.448,-277.569]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"white splashes 10","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":192.69},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0.501,-5.417],[0.307,16.252],[-1.853,0.524],[0.918,-24.972]],"o":[[1.34,-26.772],[-0.617,-32.602],[4.732,-1.338],[-1.024,27.851]],"v":[[9.159,-30.175],[10.331,-94.286],[9.15,-140.552],[13.436,-94.62]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[2.872,-210.009],[-0.406,-219.396],[4.859,-228.985],[6.538,-212.869]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"white splashes 9","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":178.18},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[2.914,0.483],[-0.75,3.266],[-2.159,-0.434],[0.278,-3.069]],"o":[[-2.731,-0.452],[0.75,-3.266],[2.014,0.405],[-0.319,3.512]],"v":[[5.887,-190.899],[3.433,-198.105],[8.313,-203.983],[10.844,-197.439]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[5.198,-239.911],[1.92,-249.298],[7.185,-258.887],[8.864,-242.771]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0,0],[-0.178,30.147],[-3.098,-0.098],[2.065,-33.166]],"o":[[-3.379,-29.806],[0.157,-26.65],[6.602,0.21],[-3.247,52.138]],"v":[[4.103,-38.228],[-0.753,-137.332],[4.092,-183.497],[5.25,-137.283]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[2.872,-210.009],[-0.406,-219.396],[4.859,-228.985],[6.538,-212.869]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"white splashes 8","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":159.43},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[1.785,-0.166],[-0.606,2.256],[-1.258,-0.472],[0.237,-1.901]],"o":[[-2.305,0.214],[0.54,-2.01],[1.419,0.532],[-0.209,1.677]],"v":[[1.45,-209.993],[-0.568,-214.537],[2.505,-217.89],[4.374,-213.361]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[5.198,-239.911],[1.92,-249.298],[7.185,-258.887],[8.864,-242.771]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0,0],[1.193,46.077],[-4.889,-1.15],[1.642,-42.494]],"o":[[0,0],[-1.052,-40.634],[7.955,1.872],[-2.299,59.509]],"v":[[0.57,-29.865],[-3.566,-125.498],[0.579,-202.91],[2.057,-125.567]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[2.872,-210.009],[-0.406,-219.396],[4.859,-228.985],[6.538,-212.869]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"white splashes 7","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":121.14},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0,0],[-0.156,34.036],[-4.468,-1.124],[3.836,-53.869]],"o":[[-9.01,-37.907],[0.232,-50.528],[6.047,1.521],[-4.23,59.393]],"v":[[-1.995,-37.202],[-13.819,-149.054],[-1.999,-236.043],[-8.402,-150.485]],"c":true}]},{"t":105,"s":[{"i":[[0.894,1.266],[-0.999,3.16],[-2.102,-2.979],[1.628,-5.15]],"o":[[-1.911,-2.708],[1.099,-3.477],[3.114,4.413],[-0.467,1.478]],"v":[[-9.187,-267.183],[-10.516,-277.036],[-3.435,-285.375],[-5.021,-269.249]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"white splashes 6","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":99.54},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[2.915,1.639],[-0.439,2.709],[-2.762,-1.266],[0.258,-2.657]],"o":[[-2.152,-1.211],[0.566,-3.493],[1.834,0.841],[-0.374,3.847]],"v":[[0.236,-176.094],[-2.057,-182.925],[3.531,-188.307],[5.714,-182.155]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[5.198,-239.911],[1.92,-249.298],[7.185,-258.887],[8.864,-242.771]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[-2.433,-32.344],[1.471,40.713],[-4.408,-2.527],[1.164,-31.322]],"o":[[0,0],[-1.412,-39.1],[2.484,1.424],[-0.718,19.307]],"v":[[1.045,-42.304],[-7.111,-119.798],[1.052,-167.216],[-0.663,-119.462]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[2.872,-210.009],[-0.406,-219.396],[4.859,-228.985],[6.538,-212.869]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"white splashes 5","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":77.63},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[1.463,0.517],[-0.149,1.097],[-1.132,-0.252],[0.096,-1.199]],"o":[[-1.11,-0.392],[0.199,-1.463],[1.187,0.264],[-0.125,1.557]],"v":[[3.823,-195.789],[2.539,-198.382],[4.909,-201.046],[6.439,-198.381]],"c":true}]},{"t":105,"s":[{"i":[[0.372,0.35],[-0.114,1.086],[-0.875,-0.822],[0.185,-1.769]],"o":[[-0.795,-0.748],[0.125,-1.194],[1.296,1.218],[-0.053,0.508]],"v":[[4.894,-216.411],[3.814,-219.502],[5.548,-222.66],[6.101,-217.353]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[1.326,0.836],[-0.749,2.262],[-1.249,-0.536],[0.89,-2.237]],"o":[[-1.156,-0.729],[0.838,-2.534],[2.124,0.912],[-1.049,2.636]],"v":[[5.046,-203.717],[4.806,-209.142],[8.46,-213.457],[9.665,-207.855]],"c":true}]},{"t":105,"s":[{"i":[[0.715,0.672],[-0.218,2.085],[-1.681,-1.58],[0.356,-3.399]],"o":[[-1.528,-1.436],[0.24,-2.295],[2.49,2.34],[-0.102,0.975]],"v":[[6.049,-228.341],[3.975,-234.28],[7.307,-240.347],[8.369,-230.151]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[-4.814,-40.706],[-1.895,51.518],[-4.761,-1.911],[1.795,-38.673]],"o":[[0,0],[1.569,-42.637],[2.891,1.16],[-1.202,25.903]],"v":[[3.306,-29.924],[-6.601,-132.191],[3.292,-191.348],[-0.482,-130.244]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[3.3,-194.69],[0.023,-204.077],[5.288,-213.666],[6.967,-197.55]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"white splashes 3","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":35.48},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[2.259,0.86],[-0.661,4.424],[-2.433,-0.341],[1.208,-4.987]],"o":[[-2.13,-0.811],[0.754,-5.049],[3.542,0.496],[-1.185,4.895]],"v":[[1.284,-197.622],[-0.569,-207.285],[4.734,-217.037],[7.318,-206.171]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[5.772,-271.189],[2.494,-280.576],[7.759,-290.165],[9.439,-274.049]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0,0],[-1.091,54.207],[-2.162,-1.244],[2.031,-39.647]],"o":[[0,0],[0.873,-43.398],[3.603,2.074],[-2.522,49.212]],"v":[[2.196,-32.287],[-7.332,-149.424],[2.183,-192.763],[-0.137,-136.646]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[3.446,-241.287],[0.168,-250.674],[5.433,-260.263],[7.113,-244.146]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"white splashes 4","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":105,"s":[0]}]},"r":{"a":0,"k":-3.87},"a":{"a":0,"k":[1.121,-11.827,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[2.46,0.092],[-0.034,1.74],[-1.94,0.288],[-0.039,-1.924]],"o":[[-1.711,-0.064],[0.034,-1.74],[1.94,-0.288],[0.039,1.924]],"v":[[2.715,-178.247],[0.16,-181.736],[3.08,-185.561],[6.197,-182.051]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.498],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[5.626,-224.593],[2.349,-233.98],[7.614,-243.569],[9.293,-227.452]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[2.294,0.008],[0.501,2.89],[-1.583,-0.006],[-0.229,-1.323]],"o":[[-2.933,-0.01],[-0.27,-1.56],[1.343,0.005],[0.392,2.26]],"v":[[1.108,-11.6],[-4.67,-18.237],[-0.404,-20.326],[3.025,-18.209]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0,0],[0.85,39.256],[-3.929,-0.2],[0.353,-39.178]],"o":[[0,0],[-0.635,-29.308],[6.408,0.327],[-0.331,36.714]],"v":[[1.787,-33.47],[-2.076,-118.748],[1.789,-173.393],[2.474,-118.386]],"c":true}]},{"t":105,"s":[{"i":[[1.129,1.062],[-0.345,3.296],[-2.657,-2.497],[0.563,-5.372]],"o":[[-2.415,-2.27],[0.38,-3.627],[3.936,3.699],[-0.161,1.542]],"v":[[3.3,-194.69],[0.023,-204.077],[5.288,-213.666],[6.967,-197.55]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.854901969433,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":105,"st":-60,"bm":0}]},{"id":"comp_3","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"pink","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[100]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[100]},{"t":100,"s":[0]}]},"p":{"a":0,"k":[245.75,225.75,0]},"a":{"a":0,"k":[2.75,-17.25,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.629,0.629,0.629],"y":[0,0,0]},"t":30,"s":[43.235,43.235,100]},{"t":40,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.312,8.525],[3.244,0.766],[0.278,-4.348]],"o":[[-1.312,-8.525],[-5.358,-1.265],[-0.278,4.348]],"v":[[6.13,-24.571],[8.951,-66.768],[5.014,-23.638]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.55,23.061],[3.244,0.766],[0.753,-11.762]],"o":[[-3.55,-23.061],[-5.358,-1.265],[-0.753,11.762]],"v":[[11.788,-37.733],[14.47,-92.154],[8.769,-35.211]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.113,0.736],[3.244,0.766],[0.024,-0.376]],"o":[[-0.113,-0.736],[-5.358,-1.265],[-0.024,0.376]],"v":[[1.251,-14.165],[19.643,-137.56],[1.155,-14.085]],"c":true}]},{"t":100,"s":[{"i":[[0.002,0.014],[3.244,0.766],[0,-0.007]],"o":[[-0.002,-0.014],[-5.358,-1.265],[0,0.007]],"v":[[29.568,-215.114],[32.47,-250.154],[29.566,-215.113]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.775,14.209],[6.351,4.603],[2.652,-16.185]],"o":[[0.775,-14.209],[-4.056,-2.94],[-2.652,16.185]],"v":[[7.231,-23.969],[18.873,-61.186],[6.13,-24.571]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-2.096,38.439],[6.351,4.603],[7.173,-43.783]],"o":[[2.096,-38.439],[-4.056,-2.94],[-7.173,43.783]],"v":[[14.767,-36.105],[37.637,-108.647],[11.788,-37.733]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.067,1.227],[6.351,4.603],[0.229,-1.398]],"o":[[0.067,-1.227],[-4.056,-2.94],[-0.229,1.398]],"v":[[1.932,-14.242],[49.85,-146.868],[1.837,-14.294]],"c":true}]},{"t":100,"s":[{"i":[[-0.001,0.023],[6.351,4.603],[0.004,-0.026]],"o":[[0.001,-0.023],[-4.056,-2.94],[-0.004,0.026]],"v":[[75.069,-225.113],[80.137,-241.647],[75.068,-225.114]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.749,6.829],[6.31,5.609],[1.359,-6.48]],"o":[[-0.749,-6.829],[-3.554,-3.159],[-1.359,6.48]],"v":[[8.596,-23.217],[31.24,-73.607],[7.231,-23.969]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[2.026,18.473],[6.31,5.609],[3.677,-17.531]],"o":[[-2.026,-18.473],[-3.554,-3.159],[-3.677,17.531]],"v":[[18.46,-34.07],[47.481,-104.355],[14.767,-36.105]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.065,0.59],[6.31,5.609],[0.117,-0.56]],"o":[[-0.065,-0.59],[-3.554,-3.159],[-0.117,0.56]],"v":[[2.166,-13.836],[63.43,-142.576],[2.048,-13.901]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0.011],[6.31,5.609],[0.002,-0.011]],"o":[[-0.001,-0.011],[-3.554,-3.159],[-0.002,0.011]],"v":[[84.072,-198.612],[102.981,-237.355],[84.069,-198.613]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.199,15.089],[5.581,6.595],[12.758,-17.563]],"o":[[6.199,-15.089],[-2.72,-3.214],[-12.758,17.563]],"v":[[9.486,-22.203],[48.882,-61.987],[8.281,-23.532]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-16.769,40.819],[5.581,6.595],[34.513,-47.512]],"o":[[16.769,-40.819],[-2.72,-3.214],[-34.513,47.512]],"v":[[21.719,-30.475],[103.282,-109.764],[18.46,-34.07]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.535,1.303],[5.581,6.595],[1.102,-1.517]],"o":[[0.535,-1.303],[-2.72,-3.214],[-1.102,1.517]],"v":[[3.204,-13.341],[122.823,-131.604],[3.099,-13.456]],"c":true}]},{"t":100,"s":[{"i":[[-0.01,0.025],[5.581,6.595],[0.021,-0.029]],"o":[[0.01,-0.025],[-2.72,-3.214],[-0.021,0.029]],"v":[[156.574,-169.11],[171.282,-185.764],[156.572,-169.112]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.223,9.945],[2.258,6.104],[6.726,-10.205]],"o":[[4.223,-9.945],[-1.817,-4.912],[-6.726,10.205]],"v":[[11.177,-20.424],[48.778,-58.677],[9.801,-21.888]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-11.423,26.904],[2.258,6.104],[18.195,-27.606]],"o":[[11.423,-26.904],[-1.817,-4.912],[-18.195,27.606]],"v":[[25.439,-26.516],[74.48,-79.649],[21.719,-30.475]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.365,0.859],[2.258,6.104],[0.581,-0.882]],"o":[[0.365,-0.859],[-1.817,-4.912],[-0.581,0.882]],"v":[[3.264,-12.932],[105.66,-107.524],[3.146,-13.058]],"c":true}]},{"t":100,"s":[{"i":[[-0.007,0.016],[2.258,6.104],[0.011,-0.017]],"o":[[0.007,-0.016],[-1.817,-4.912],[-0.011,0.017]],"v":[[152.076,-147.108],[182.98,-176.649],[152.074,-147.11]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-3.334,6.648],[-1.799,5.518],[9.673,-9.931]],"o":[[3.334,-6.648],[1.878,-5.758],[-9.673,9.931]],"v":[[12.897,-19.193],[48.972,-44.204],[11.177,-20.424]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-9.018,17.983],[-1.799,5.518],[26.167,-26.865]],"o":[[9.018,-17.983],[1.878,-5.758],[-26.167,26.865]],"v":[[30.094,-23.186],[81.927,-62.022],[25.439,-26.516]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.288,0.574],[-1.799,5.518],[0.836,-0.858]],"o":[[0.288,-0.574],[1.878,-5.758],[-0.836,0.858]],"v":[[3.883,-12.491],[120.292,-83.862],[3.734,-12.597]],"c":true}]},{"t":100,"s":[{"i":[[-0.005,0.011],[-1.799,5.518],[0.016,-0.016]],"o":[[0.005,-0.011],[1.878,-5.758],[-0.016,0.016]],"v":[[188.579,-121.106],[215.427,-138.022],[188.576,-121.108]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-9.869,8.704],[-3.432,7.058],[12.009,-7.852]],"o":[[9.869,-8.704],[2.981,-6.13],[-12.009,7.852]],"v":[[12.832,-16.628],[74.374,-42.527],[12.897,-19.193]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-26.696,23.547],[-3.432,7.058],[32.485,-21.24]],"o":[[26.696,-23.547],[2.981,-6.13],[-32.485,21.24]],"v":[[29.916,-16.247],[117.42,-56.876],[30.094,-23.186]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.852,0.752],[-3.432,7.058],[1.037,-0.678]],"o":[[0.852,-0.752],[2.981,-6.13],[-1.037,0.678]],"v":[[3.897,-11.786],[151.618,-69.521],[3.902,-12.008]],"c":true}]},{"t":100,"s":[{"i":[[-0.016,0.014],[-3.432,7.058],[0.02,-0.013]],"o":[[0.016,-0.014],[2.981,-6.13],[-0.02,0.013]],"v":[[190.079,-83.601],[236.42,-100.876],[190.079,-83.606]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-14.199,3.553],[-2.249,4.962],[22.38,-4.873]],"o":[[22.287,-5.577],[4.049,-8.935],[-22.38,4.873]],"v":[[10.861,-15.141],[88.919,-15.854],[12.516,-16.944]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-38.412,9.611],[-2.249,4.962],[60.542,-13.183]],"o":[[60.291,-15.085],[4.049,-8.935],[-60.542,13.183]],"v":[[25.439,-11.372],[154.986,-15.381],[29.916,-16.247]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-1.227,0.307],[-2.249,4.962],[1.933,-0.421]],"o":[[1.925,-0.482],[4.049,-8.935],[-1.933,0.421]],"v":[[3.934,-10.704],[182.862,-13.944],[4.077,-10.859]],"c":true}]},{"t":100,"s":[{"i":[[-0.023,0.006],[-2.249,4.962],[0.036,-0.008]],"o":[[0.036,-0.009],[4.049,-8.935],[-0.036,0.008]],"v":[[204.076,-11.599],[251.986,-10.381],[204.079,-11.601]],"c":true}]}]},"nm":"Path 8","hd":false},{"ind":8,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.711,2.16],[-0.839,0.958],[12.251,-0.88]],"o":[[4.711,-2.16],[1.348,-1.539],[-12.251,0.88]],"v":[[9.079,-12.869],[39.383,-12.388],[10.861,-14.826]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-12.744,5.843],[-0.839,0.958],[33.14,-2.38]],"o":[[12.744,-5.843],[1.348,-1.539],[-33.14,2.38]],"v":[[20.619,-6.076],[78.015,-8.131],[25.439,-11.372]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.407,0.187],[-0.839,0.958],[1.058,-0.076]],"o":[[0.407,-0.187],[1.348,-1.539],[-1.058,0.076]],"v":[[4.224,-10.251],[128.305,-2.239],[4.378,-10.421]],"c":true}]},{"t":100,"s":[{"i":[[-0.008,0.004],[-0.839,0.958],[0.02,-0.001]],"o":[[0.008,-0.004],[1.348,-1.539],[-0.02,0.001]],"v":[[238.573,10.405],[253.015,12.369],[238.576,10.401]],"c":true}]}]},"nm":"Path 9","hd":false},{"ind":9,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-19.702,-2.401],[-2.482,3.154],[14.29,0.398]],"o":[[19.702,2.401],[2.595,-3.297],[-14.29,-0.398]],"v":[[9.698,-12.307],[64.597,6.088],[9.395,-13.184]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-53.298,-6.494],[-2.482,3.154],[38.657,1.077]],"o":[[53.298,6.494],[2.595,-3.297],[-38.657,-1.077]],"v":[[21.439,-3.705],[93.137,16.968],[20.619,-6.076]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-1.702,-0.207],[-2.482,3.154],[1.234,0.034]],"o":[[1.702,0.207],[2.595,-3.297],[-1.234,-0.034]],"v":[[3.71,-9.493],[135.812,35.216],[3.683,-9.569]],"c":true}]},{"t":100,"s":[{"i":[[-0.032,-0.004],[-2.482,3.154],[0.023,0.001]],"o":[[0.032,0.004],[2.595,-3.297],[-0.023,-0.001]],"v":[[196.573,63.406],[241.637,80.468],[196.573,63.405]],"c":true}]}]},"nm":"Path 10","hd":false},{"ind":10,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-9.253,-3.386],[-5.064,4.293],[16.792,8.51]],"o":[[9.253,3.386],[4.126,-3.498],[-16.792,-8.51]],"v":[[11.313,-9.614],[54.159,17.809],[9.698,-11.992]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-25.032,-9.16],[-5.064,4.293],[45.426,23.022]],"o":[[25.032,9.16],[4.126,-3.498],[-45.426,-23.022]],"v":[[25.808,2.729],[103.197,50.764],[21.439,-3.705]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.799,-0.292],[-5.064,4.293],[1.451,0.735]],"o":[[0.799,0.292],[4.126,-3.498],[-1.451,-0.735]],"v":[[3.881,-8.702],[137.251,71.886],[3.742,-8.908]],"c":true}]},{"t":100,"s":[{"i":[[-0.015,-0.006],[-5.064,4.293],[0.027,0.014]],"o":[[0.015,0.006],[4.126,-3.498],[-0.027,-0.014]],"v":[[199.076,108.91],[221.697,124.264],[199.073,108.906]],"c":true}]}]},"nm":"Path 11","hd":false},{"ind":11,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.526,-3.215],[-3.502,3.162],[9.671,6.481]],"o":[[6.526,3.215],[3.502,-3.162],[-9.671,-6.481]],"v":[[10.491,-8.994],[73.057,40.569],[11.313,-9.929]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-17.653,-8.697],[-3.502,3.162],[26.163,17.532]],"o":[[17.653,8.697],[3.502,-3.162],[-26.163,-17.532]],"v":[[23.585,5.258],[96.236,63.117],[25.808,2.729]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.564,-0.278],[-3.502,3.162],[0.835,0.56]],"o":[[0.564,0.278],[3.502,-3.162],[-0.835,-0.56]],"v":[[3.102,-8.57],[126.267,90.274],[3.173,-8.651]],"c":true}]},{"t":100,"s":[{"i":[[-0.011,-0.005],[-3.502,3.162],[0.016,0.011]],"o":[[0.011,0.005],[3.502,-3.162],[-0.016,-0.011]],"v":[[144.075,112.911],[200.736,157.617],[144.076,112.91]],"c":true}]}]},"nm":"Path 12","hd":false},{"ind":12,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-13.475,-10.482],[-4.458,3.463],[10.073,7.849]],"o":[[13.475,10.482],[4.458,-3.463],[-10.073,-7.849]],"v":[[9.502,-7.951],[52.87,45.892],[10.491,-8.678]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-36.453,-28.355],[-4.458,3.463],[27.249,21.233]],"o":[[36.453,28.355],[4.458,-3.463],[-27.249,-21.233]],"v":[[20.909,7.226],[94.655,93.669],[23.585,5.258]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-1.164,-0.905],[-4.458,3.463],[0.87,0.678]],"o":[[1.164,0.905],[4.458,-3.463],[-0.87,-0.678]],"v":[[3.049,-7.986],[116.926,120.825],[3.134,-8.049]],"c":true}]},{"t":100,"s":[{"i":[[-0.022,-0.017],[-4.458,3.463],[0.016,0.013]],"o":[[0.022,0.017],[4.458,-3.463],[-0.016,-0.013]],"v":[[146.573,153.413],[172.155,188.169],[146.575,153.411]],"c":true}]}]},"nm":"Path 13","hd":false},{"ind":13,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.775,-3.087],[-3.488,2.449],[10.229,14.193]],"o":[[4.775,3.087],[3.488,-2.449],[-10.229,-14.193]],"v":[[7.167,-8.135],[48.091,56.332],[9.187,-7.951]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-12.917,-8.352],[-3.488,2.449],[27.671,38.395]],"o":[[12.917,8.352],[3.488,-2.449],[-27.671,-38.395]],"v":[[15.445,6.729],[62.124,74.78],[20.909,7.226]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.412,-0.267],[-3.488,2.449],[0.884,1.226]],"o":[[0.412,0.267],[3.488,-2.449],[-0.884,-1.226]],"v":[[2.321,-8.079],[84.827,113.576],[2.495,-8.063]],"c":true}]},{"t":100,"s":[{"i":[[-0.008,-0.005],[-3.488,2.449],[0.017,0.023]],"o":[[0.008,0.005],[3.488,-2.449],[-0.017,-0.023]],"v":[[103.57,147.412],[141.124,209.78],[103.573,147.413]],"c":true}]}]},"nm":"Path 14","hd":false},{"ind":14,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-5.362,-6.334],[-2.659,0.857],[3.694,6.735]],"o":[[5.361,6.334],[6.495,-2.093],[-3.694,-6.735]],"v":[[5.102,-9.295],[26.477,35.494],[7.167,-8.45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-14.504,-17.136],[-2.659,0.857],[9.994,18.219]],"o":[[14.504,17.136],[6.495,-2.093],[-9.994,-18.219]],"v":[[9.859,4.445],[41.772,64.349],[15.445,6.729]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.463,-0.547],[-2.659,0.857],[0.319,0.582]],"o":[[0.463,0.547],[6.495,-2.093],[-0.319,-0.582]],"v":[[2.027,-7.553],[61.313,111.91],[2.205,-7.48]],"c":true}]},{"t":100,"s":[{"i":[[-0.009,-0.01],[-2.659,0.857],[0.006,0.011]],"o":[[0.009,0.01],[6.495,-2.093],[-0.006,-0.011]],"v":[[94.566,193.911],[109.772,229.849],[94.57,193.912]],"c":true}]}]},"nm":"Path 15","hd":false},{"ind":15,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.43,-4.038],[-5.792,0.94],[0.557,8.126]],"o":[[0.43,4.038],[3.71,-0.602],[-0.557,-8.126]],"v":[[3.69,-10.876],[17.077,55.965],[5.102,-9.295]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-1.164,-10.924],[-5.792,0.94],[1.507,21.981]],"o":[[1.164,10.924],[3.71,-0.602],[-1.507,-21.981]],"v":[[6.039,0.167],[22.28,71.891],[9.859,4.445]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.037,-0.349],[-5.792,0.94],[0.048,0.702]],"o":[[0.037,0.349],[3.71,-0.602],[-0.048,-0.702]],"v":[[1.242,-7.799],[31.62,122.9],[1.364,-7.663]],"c":true}]},{"t":100,"s":[{"i":[[-0.001,-0.007],[-5.792,0.94],[0.001,0.013]],"o":[[0.001,0.007],[3.71,-0.602],[-0.001,-0.013]],"v":[[43.064,185.408],[54.78,249.391],[43.066,185.411]],"c":true}]}]},"nm":"Path 16","hd":false},{"ind":16,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.268,-1.072],[-8.766,2.77],[-1.453,20.282]],"o":[[-0.268,1.072],[8.956,-2.83],[1.453,-20.282]],"v":[[2.258,-8.649],[11.352,48.031],[4.005,-10.876]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[0.726,-2.901],[-8.766,2.77],[-3.93,54.866]],"o":[[-0.726,2.901],[8.956,-2.83],[3.93,-54.866]],"v":[[1.312,6.192],[20.655,115.675],[6.039,0.167]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.023,-0.093],[-8.766,2.77],[-0.125,1.752]],"o":[[-0.023,0.093],[8.956,-2.83],[0.125,-1.752]],"v":[[0.956,-7.06],[24.966,155.333],[1.106,-7.252]],"c":true}]},{"t":100,"s":[{"i":[[0,-0.002],[-8.766,2.77],[-0.002,0.033]],"o":[[0,0.002],[8.956,-2.83],[0.002,-0.033]],"v":[[32.561,227.912],[35.655,253.675],[32.564,227.908]],"c":true}]}]},"nm":"Path 17","hd":false},{"ind":17,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.674,-5.118],[-9.34,-10.304],[-5.656,20.469]],"o":[[-0.674,5.118],[5.335,5.886],[5.656,-20.469]],"v":[[0.319,-7.862],[-7.754,79.004],[2.258,-8.649]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[1.822,-13.845],[-9.34,-10.304],[-15.299,55.372]],"o":[[-1.822,13.845],[5.335,5.886],[15.299,-55.372]],"v":[[-3.933,8.322],[-10.119,142.549],[1.312,6.192]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.058,-0.442],[-9.34,-10.304],[-0.489,1.768]],"o":[[-0.058,0.442],[5.335,5.886],[0.489,-1.768]],"v":[[0.157,-7.436],[-14.573,174.16],[0.325,-7.504]],"c":true}]},{"t":100,"s":[{"i":[[0.001,-0.008],[-9.34,-10.304],[-0.009,0.033]],"o":[[-0.001,0.008],[5.335,5.886],[0.009,-0.033]],"v":[[-16.442,193.413],[-25.619,252.549],[-16.439,193.412]],"c":true}]}]},"nm":"Path 18","hd":false},{"ind":18,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.134,-4.491],[-8.529,-8.273],[-6.696,18.034]],"o":[[-1.134,4.491],[3.166,3.071],[6.696,-18.034]],"v":[[-1.761,-9.64],[-13.598,51.106],[0.003,-7.546]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.068,-12.148],[-8.529,-8.273],[-18.113,48.786]],"o":[[-3.068,12.148],[3.166,3.071],[18.113,-48.786]],"v":[[-8.707,2.658],[-24.162,110.866],[-3.933,8.322]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.098,-0.388],[-8.529,-8.273],[-0.578,1.558]],"o":[[-0.098,0.388],[3.166,3.071],[0.578,-1.558]],"v":[[-0.446,-7.378],[-34.795,149.374],[-0.293,-7.198]],"c":true}]},{"t":100,"s":[{"i":[[0.002,-0.007],[-8.529,-8.273],[-0.011,0.029]],"o":[[-0.002,0.007],[3.166,3.071],[0.011,-0.029]],"v":[[-51.445,211.91],[-61.162,244.866],[-51.442,211.913]],"c":true}]}]},"nm":"Path 19","hd":false},{"ind":19,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[9.217,-13.161],[-2.977,-4.918],[-5.725,7.174]],"o":[[-9.217,13.161],[1.721,2.843],[5.725,-7.174]],"v":[[-1.154,-12.499],[-42.53,56.34],[-1.761,-9.64]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[24.934,-35.602],[-2.977,-4.918],[-15.488,19.406]],"o":[[-24.934,35.602],[1.721,2.843],[15.488,-19.406]],"v":[[-7.064,-5.076],[-47.418,71.95],[-8.707,2.658]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.796,-1.137],[-2.977,-4.918],[-0.495,0.62]],"o":[[-0.796,1.137],[1.721,2.843],[0.495,-0.62]],"v":[[-0.876,-8.404],[-71.414,113.188],[-0.929,-8.157]],"c":true}]},{"t":100,"s":[{"i":[[0.015,-0.021],[-2.977,-4.918],[-0.009,0.012]],"o":[[-0.015,0.021],[1.721,2.843],[0.009,-0.012]],"v":[[-88.944,151.405],[-130.918,215.45],[-88.945,151.41]],"c":true}]}]},"nm":"Path 20","hd":false},{"ind":20,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[7.626,-7.899],[-8.084,-5.313],[-13.235,7.936]],"o":[[-2.855,2.957],[3.306,2.173],[10.979,-6.583]],"v":[[-4.169,-12.723],[-45.434,37.243],[-0.839,-12.499]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[20.63,-21.369],[-8.084,-5.313],[-35.802,21.467]],"o":[[-7.724,8],[3.306,2.173],[29.699,-17.808]],"v":[[-16.072,-5.684],[-88.796,85.335],[-7.064,-5.076]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.659,-0.682],[-8.084,-5.313],[-1.143,0.686]],"o":[[-0.247,0.255],[3.306,2.173],[0.948,-0.569]],"v":[[-1.962,-8.308],[-113.223,114.36],[-1.674,-8.288]],"c":true}]},{"t":100,"s":[{"i":[[0.012,-0.013],[-8.084,-5.313],[-0.022,0.013]],"o":[[-0.005,0.005],[3.306,2.173],[0.018,-0.011]],"v":[[-150.949,160.405],[-173.796,186.335],[-150.944,160.405]],"c":true}]}]},"nm":"Path 21","hd":false},{"ind":21,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[14.117,-10.21],[-5.628,-14.818],[-24.822,15.796]],"o":[[-14.086,10.188],[3.081,4.733],[21.924,-13.951]],"v":[[-3.765,-15.075],[-82.448,63.651],[-4.484,-12.723]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[38.189,-27.621],[0.797,-25.979],[-67.149,42.73]],"o":[[-38.106,27.561],[-0.163,5.311],[59.307,-37.74]],"v":[[-14.127,-12.046],[-117.611,102.283],[-16.072,-5.684]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[1.219,-0.882],[-2.13,-20.894],[-2.144,1.364]],"o":[[-1.217,0.88],[1.315,5.048],[1.894,-1.205]],"v":[[-1.552,-9.148],[-137.871,121.393],[-1.614,-8.945]],"c":true}]},{"t":100,"s":[{"i":[[0.023,-0.017],[-9.389,-8.283],[-0.04,0.026]],"o":[[-0.023,0.017],[4.981,4.394],[0.036,-0.023]],"v":[[-123.948,110.901],[-188.111,168.783],[-123.949,110.905]],"c":true}]}]},"nm":"Path 22","hd":false},{"ind":22,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.794,-1.349],[0.656,-8.742],[-9.144,3.299]],"o":[[-0.388,0.187],[-0.279,3.717],[9.144,-3.298]],"v":[[-4.357,-15.318],[-39.206,7.131],[-3.283,-15.062]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[7.56,-3.648],[1.507,-8.661],[-24.737,8.919]],"o":[[-1.051,0.507],[-0.641,3.682],[24.737,-8.919]],"v":[[-17.032,-12.737],[-65.957,17.111],[-14.127,-12.046]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.241,-0.117],[1.12,-8.698],[-0.79,0.285]],"o":[[-0.034,0.016],[-0.476,3.698],[0.79,-0.285]],"v":[[-2.581,-9.26],[-111.172,47.264],[-2.489,-9.238]],"c":true}]},{"t":100,"s":[{"i":[[0.004,-0.003],[0.158,-8.79],[-0.014,0.008]],"o":[[-0.001,0],[-0.067,3.737],[0.014,-0.008]],"v":[[-196.685,103.922],[-223.294,122.037],[-196.683,103.922]],"c":true}]}]},"nm":"Path 23","hd":false},{"ind":23,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.652,-0.002],[9.621,-23.195],[-30.797,11.034]],"o":[[-0.652,0.002],[1.201,4.343],[30.797,-11.034]],"v":[[-5.938,-16.534],[-91.485,36.162],[-4.524,-15.646]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[1.764,-0.005],[34.94,-49.93],[-83.311,29.848]],"o":[[-1.764,0.005],[-2.448,3.499],[83.311,-29.848]],"v":[[-20.857,-15.139],[-161.337,74.163],[-17.032,-12.737]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.056,0],[23.404,-37.749],[-2.66,0.953]],"o":[[-0.056,0],[-0.786,3.883],[2.66,-0.953]],"v":[[-2.437,-9.678],[-180.879,84.077],[-2.314,-9.602]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0],[-5.203,-7.542],[-0.05,0.018]],"o":[[-0.001,0],[3.337,4.837],[0.05,-0.018]],"v":[[-175.952,77.399],[-229.337,108.663],[-175.95,77.401]],"c":true}]}]},"nm":"Path 24","hd":false},{"ind":24,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[3.079,-0.51],[0.18,-8.491],[-14.645,0.239]],"o":[[-3.079,0.51],[-0.099,4.7],[14.645,-0.239]],"v":[[-8.996,-17.288],[-60.714,-4.682],[-6.253,-16.534]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[8.33,-1.38],[0.18,-8.491],[-39.617,0.647]],"o":[[-8.33,1.38],[-0.099,4.7],[39.617,-0.647]],"v":[[-28.279,-17.178],[-119.213,6.829],[-20.857,-15.139]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.266,-0.044],[0.18,-8.491],[-1.265,0.021]],"o":[[-0.266,0.044],[-0.099,4.7],[1.265,-0.021]],"v":[[-3.288,-10.423],[-157.506,13.654],[-3.051,-10.357]],"c":true}]},{"t":100,"s":[{"i":[[0.005,-0.001],[0.18,-8.491],[-0.024,0]],"o":[[-0.005,0.001],[-0.099,4.7],[0.024,0]],"v":[[-223.707,24.648],[-252.463,30.579],[-223.702,24.649]],"c":true}]}]},"nm":"Path 25","hd":false},{"ind":25,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[5.002,-0.018],[4.376,-11.742],[-21.034,-0.505]],"o":[[-5.002,0.018],[-1,2.682],[21.034,0.505]],"v":[[-8.482,-18.09],[-80.041,-5.651],[-8.996,-16.972]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[13.533,-0.05],[4.376,-11.742],[-56.9,-1.366]],"o":[[-13.533,0.05],[-1,2.682],[56.9,1.366]],"v":[[-26.888,-20.202],[-133.179,-1.393],[-28.279,-17.178]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.432,-0.002],[4.376,-11.742],[-1.817,-0.044]],"o":[[-0.432,0.002],[-1,2.682],[1.817,0.044]],"v":[[-2.957,-10.802],[-167.52,2.271],[-3.002,-10.706]],"c":true}]},{"t":100,"s":[{"i":[[0.008,0],[4.376,-11.742],[-0.034,-0.001]],"o":[[-0.008,0],[-1,2.682],[0.034,0.001]],"v":[[-201.456,2.646],[-252.679,11.357],[-201.457,2.648]],"c":true}]}]},"nm":"Path 26","hd":false},{"ind":26,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.015,0.648],[9.285,-5.938],[-12.687,-1.577]],"o":[[-1.837,-0.296],[-5.457,3.49],[12.687,1.577]],"v":[[-7.193,-19.109],[-55.539,-18.566],[-8.167,-18.09]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[10.862,1.752],[9.285,-5.938],[-34.32,-4.266]],"o":[[-4.969,-0.802],[-5.457,3.49],[34.32,4.266]],"v":[[-24.253,-22.959],[-119.714,-21.877],[-26.888,-20.202]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.347,0.056],[9.285,-5.938],[-1.096,-0.136]],"o":[[-0.159,-0.026],[-5.457,3.49],[1.096,0.136]],"v":[[-3.256,-11.254],[-157.719,-22.883],[-3.34,-11.166]],"c":true}]},{"t":100,"s":[{"i":[[0.007,0.001],[9.285,-5.938],[-0.021,-0.003]],"o":[[-0.003,0],[-5.457,3.49],[0.021,0.003]],"v":[[-231.204,-25.606],[-251.964,-25.377],[-231.206,-25.604]],"c":true}]}]},"nm":"Path 27","hd":false},{"ind":27,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.393,1.499],[5.601,-5.319],[-6.035,-3.074]],"o":[[-4.393,-1.499],[-2.043,1.94],[7.22,3.678]],"v":[[-5.608,-19.397],[-48.68,-27.903],[-7.508,-19.109]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[11.885,4.055],[5.601,-5.319],[-16.326,-8.316]],"o":[[-11.885,-4.055],[-2.043,1.94],[19.531,9.948]],"v":[[-19.113,-23.736],[-77.851,-34.052],[-24.253,-22.959]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.38,0.13],[5.601,-5.319],[-0.521,-0.266]],"o":[[-0.38,-0.129],[-2.043,1.94],[0.624,0.318]],"v":[[-2.828,-11.723],[-125.77,-43.679],[-2.992,-11.698]],"c":true}]},{"t":100,"s":[{"i":[[0.007,0.002],[5.601,-5.319],[-0.01,-0.005]],"o":[[-0.007,-0.002],[-2.043,1.94],[0.012,0.006]],"v":[[-210.701,-60.106],[-244.601,-67.552],[-210.704,-60.106]],"c":true}]}]},"nm":"Path 28","hd":false},{"ind":28,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[5.221,2.81],[2.362,-11.3],[-20.382,-17.294]],"o":[[-5.221,-2.81],[-1.358,6.576],[20.382,17.294]],"v":[[-7.193,-23.937],[-71.1,-48.972],[-5.293,-19.397]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[14.123,7.602],[4.156,-15.533],[-55.138,-46.784]],"o":[[-14.123,-7.602],[-2.317,8.659],[55.138,46.784]],"v":[[-24.253,-36.018],[-168.703,-94.857],[-19.113,-23.736]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.451,0.243],[3.339,-13.604],[-1.761,-1.494]],"o":[[-0.451,-0.243],[-1.88,7.71],[1.761,1.494]],"v":[[-2.925,-12.804],[-184.078,-101.754],[-2.761,-12.412]],"c":true}]},{"t":100,"s":[{"i":[[0.008,0.005],[1.312,-8.822],[-0.033,-0.028]],"o":[[-0.008,-0.005],[-0.797,5.357],[0.033,0.028]],"v":[[-205.454,-113.613],[-222.203,-118.857],[-205.451,-113.606]],"c":true}]}]},"nm":"Path 29","hd":false},{"ind":29,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[7.26,4.445],[7.011,-4.124],[-10.385,-10.766]],"o":[[-0.326,-0.199],[-7.011,4.124],[16.897,17.517]],"v":[[-5.801,-24.959],[-56.419,-56.973],[-7.193,-23.937]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[19.64,12.023],[7.011,-4.124],[-28.094,-29.123]],"o":[[-0.881,-0.54],[-7.011,4.124],[45.711,47.385]],"v":[[-20.488,-38.784],[-98.519,-86.459],[-24.253,-36.018]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.627,0.384],[7.011,-4.124],[-0.897,-0.93]],"o":[[-0.028,-0.017],[-7.011,4.124],[1.46,1.513]],"v":[[-2.277,-13.13],[-127.401,-106.145],[-2.397,-13.042]],"c":true}]},{"t":100,"s":[{"i":[[0.012,0.007],[7.011,-4.124],[-0.017,-0.018]],"o":[[-0.001,0],[-7.011,4.124],[0.028,0.029]],"v":[[-164.452,-132.115],[-199.019,-154.959],[-164.454,-132.113]],"c":true}]}]},"nm":"Path 30","hd":false},{"ind":30,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[23.866,20.95],[-0.775,-4.182],[-8.843,-10.116]],"o":[[-23.866,-20.95],[0.799,4.308],[8.843,10.116]],"v":[[-4.127,-25.389],[-47.359,-57.266],[-6.116,-25.275]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[64.562,56.675],[-0.775,-4.182],[-23.921,-27.366]],"o":[[-64.562,-56.675],[0.799,4.308],[23.921,27.366]],"v":[[-15.108,-39.093],[-105.227,-105.674],[-20.488,-38.784]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[2.062,1.81],[-0.775,-4.182],[-0.764,-0.874]],"o":[[-2.062,-1.81],[0.799,4.308],[0.764,0.874]],"v":[[-2.169,-13.514],[-128.217,-124.928],[-2.341,-13.504]],"c":true}]},{"t":100,"s":[{"i":[[0.039,0.034],[-0.775,-4.182],[-0.014,-0.016]],"o":[[-0.039,-0.034],[0.799,4.308],[0.014,0.016]],"v":[[-169.449,-161.115],[-185.227,-172.674],[-169.452,-161.115]],"c":true}]}]},"nm":"Path 31","hd":false},{"ind":31,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.861,0.835],[6.082,-5.676],[-10.296,-14.684]],"o":[[-0.861,-0.835],[-2.893,2.699],[10.296,14.684]],"v":[[-0.366,-23.104],[-55.133,-75.787],[-4.127,-25.389]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[2.329,2.258],[6.082,-5.676],[-27.852,-39.724]],"o":[[-2.329,-2.258],[-2.893,2.699],[27.852,39.724]],"v":[[-4.933,-32.911],[-79.889,-105.589],[-15.108,-39.093]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.074,0.072],[6.082,-5.676],[-0.889,-1.268]],"o":[[-0.074,-0.072],[-2.893,2.699],[0.889,1.268]],"v":[[-1.214,-13.207],[-103.885,-130.878],[-1.539,-13.404]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0.001],[6.082,-5.676],[-0.017,-0.024]],"o":[[-0.001,-0.001],[-2.893,2.699],[0.017,0.024]],"v":[[-120.443,-152.612],[-163.389,-193.589],[-120.449,-152.615]],"c":true}]}]},"nm":"Path 32","hd":false},{"ind":32,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[3.542,19.181],[3.073,-0.981],[-0.152,-9.722]],"o":[[-3.542,-19.181],[-2.453,0.783],[0.152,9.722]],"v":[[0.539,-22.899],[-14.028,-61.928],[-0.366,-22.788]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[9.581,51.887],[3.073,-0.981],[-0.412,-26.3]],"o":[[-9.581,-51.887],[-2.453,0.783],[0.412,26.3]],"v":[[-2.483,-33.211],[-31.215,-116.327],[-4.933,-32.911]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.306,1.657],[3.073,-0.981],[-0.013,-0.84]],"o":[[-0.306,-1.657],[-2.453,0.783],[0.013,0.84]],"v":[[-0.517,-14.182],[-44.865,-152.105],[-0.596,-14.172]],"c":true}]},{"t":100,"s":[{"i":[[0.006,0.031],[3.073,-0.981],[0,-0.016]],"o":[[-0.006,-0.031],[-2.453,0.783],[0,0.016]],"v":[[-72.441,-227.612],[-78.715,-240.827],[-72.443,-227.612]],"c":true}]}]},"nm":"Path 33","hd":false},{"ind":33,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.512,3.311],[7.408,-2.515],[0.879,-9.578]],"o":[[-0.512,-3.311],[-4.274,1.451],[-0.879,9.578]],"v":[[1.755,-24.526],[-15.758,-89.049],[0.539,-22.899]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[1.386,8.956],[7.408,-2.515],[2.377,-25.91]],"o":[[-1.386,-8.956],[-4.274,1.451],[-2.377,25.91]],"v":[[0.805,-37.612],[-23.169,-137.772],[-2.483,-33.211]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.044,0.286],[7.408,-2.515],[0.076,-0.827]],"o":[[-0.044,-0.286],[-4.274,1.451],[-0.076,0.827]],"v":[[0.051,-14.02],[-30.928,-169.383],[-0.054,-13.879]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0.005],[7.408,-2.515],[0.001,-0.016]],"o":[[-0.001,-0.005],[-4.274,1.451],[-0.001,0.016]],"v":[[-36.439,-204.114],[-50.169,-247.772],[-36.441,-204.112]],"c":true}]}]},"nm":"Path 34","hd":false},{"ind":34,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.275,0.477],[4.481,1.632],[2.961,-6.55],[-1.425,0.478]],"o":[[-2.55,-0.954],[-6.936,-2.526],[-1.48,3.275],[1.425,-0.478]],"v":[[5.014,-23.954],[2.623,-65.932],[2.07,-24.842],[3.754,-13.991]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.449,1.291],[4.481,1.632],[8.009,-17.719],[-3.855,1.292]],"o":[[-6.899,-2.581],[-6.936,-2.526],[-4.004,8.859],[3.855,-1.292]],"v":[[8.769,-35.211],[4.357,-117.808],[0.805,-37.612],[5.361,-8.261]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.11,0.041],[4.481,1.632],[0.256,-0.566],[-0.123,0.041]],"o":[[-0.22,-0.082],[-6.936,-2.526],[-0.128,0.283],[0.123,-0.041]],"v":[[0.788,-14.342],[3.064,-156.748],[0.534,-14.419],[0.679,-13.482]],"c":true}]},{"t":100,"s":[{"i":[[0.002,0.001],[4.481,1.632],[0.005,-0.011],[-0.002,0.001]],"o":[[-0.004,-0.002],[-6.936,-2.526],[-0.002,0.005],[0.002,-0.001]],"v":[[1.066,-235.113],[-0.143,-253.308],[1.061,-235.114],[1.064,-235.097]],"c":true}]}]},"nm":"Path 35","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":7,"k":{"a":0,"k":[0.166,1,1,1,0.291,1,0.894,0.947,0.379,1,0.788,0.894,0.522,0.961,0.394,0.72,0.708,0.922,0,0.545,0.865,0.961,0.206,0.508,0.999,1,0.412,0.471,0.166,1,0.291,1,0.379,1,0.522,0.9,0.708,0.8,0.865,0.8,0.999,0.8]}},"s":{"a":0,"k":[-12,-18]},"e":{"a":0,"k":[-123.596,-129.596]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-6.793,2.506]},"a":{"a":0,"k":[-6.793,2.506]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"white splashes precomp","refId":"comp_2","sr":1,"ks":{"p":{"a":0,"k":[243,243,0]},"a":{"a":0,"k":[243,243,0]}},"ao":0,"w":486,"h":486,"ip":-4,"op":99,"st":-5,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"ogonek5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[1],"y":[0]},"t":30,"s":[100]},{"t":45,"s":[0]}]},"p":{"a":0,"k":[241,232.25,0]},"a":{"a":0,"k":[-51,43,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,15.828]},"t":30,"s":[0,0,100]},{"t":45,"s":[151,151,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[164,164]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":7,"k":{"a":0,"k":[0.166,1,1,1,0.291,1,0.894,0.947,0.379,1,0.788,0.894,0.522,0.961,0.394,0.72,0.708,0.922,0,0.545,0.865,0.961,0.206,0.508,0.999,1,0.412,0.471,0.166,1,0.291,1,0.379,1,0.522,0.9,0.708,0.8,0.865,0.8,0.999,0.8]}},"s":{"a":0,"k":[1,0.5]},"e":{"a":0,"k":[-61.596,-53.096]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 9","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,43]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":30,"op":45,"st":-45,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"rose splashes 80%","sr":1,"ks":{"o":{"a":0,"k":80},"p":{"a":0,"k":[283.287,244.6,0]},"a":{"a":0,"k":[40.287,1.6,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[8.458,2.584],[-1.178,1.927]],"o":[[0,0],[1.178,-1.927]],"v":[[47.396,-2.495],[66.132,8.305]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[17.915,5.472],[-2.495,4.082]],"o":[[0,0],[2.494,-4.082]],"v":[[127.416,14.655],[167.1,37.528]],"c":true}]},{"t":100,"s":[{"i":[[2.922,0.892],[-0.407,0.666]],"o":[[0,0],[0.407,-0.666]],"v":[[231.544,79.714],[238.016,83.445]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-7.371,11.75],[-2.605,-2.954]],"o":[[0,0],[2.605,2.954]],"v":[[-19.417,21.61],[-33.613,50.565]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-16.511,26.322],[-5.835,-6.617]],"o":[[0,0],[5.835,6.617]],"v":[[-54.289,78.774],[-86.09,143.637]],"c":true}]},{"t":100,"s":[{"i":[[-2.019,3.219],[-0.714,-0.809]],"o":[[0,0],[0.714,0.809]],"v":[[-120,208.391],[-123.889,216.322]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.272,18.752],[3.489,0]],"o":[[0,0],[-2.919,0]],"v":[[13.337,36.564],[18.266,63.997]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[9.5,41.701],[7.758,0]],"o":[[0,0],[-6.491,0]],"v":[[34.44,120.578],[45.401,181.583]],"c":true}]},{"t":100,"s":[{"i":[[1.211,5.316],[0.989,0]],"o":[[0,0],[-0.827,0]],"v":[[49.981,239.625],[51.378,247.402]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[5.357,10.649],[2.065,-1.671]],"o":[[0,0],[-2.092,1.693]],"v":[[27.785,38.344],[37.752,60.836]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[12.682,25.21],[4.889,-3.955]],"o":[[0,0],[-4.953,4.007]],"v":[[73.965,124.33],[97.561,177.576]],"c":true}]},{"t":100,"s":[{"i":[[1.068,2.124],[0.412,-0.333]],"o":[[0,0],[-0.417,0.338]],"v":[[112.746,219.25],[114.734,223.735]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[7.125,6.764],[2.818,-3.288]],"o":[[0,0],[-1.952,2.278]],"v":[[39.186,19.394],[56.431,42.171]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[16.508,15.671],[6.529,-7.617]],"o":[[0,0],[-4.523,5.277]],"v":[[103.804,73.868],[143.76,126.64]],"c":true}]},{"t":100,"s":[{"i":[[1.631,1.548],[0.645,-0.753]],"o":[[0,0],[-0.447,0.521]],"v":[[178.352,168.75],[182.3,173.964]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[14.003,-18.483],[-2.995,-2.598]],"o":[[0,0],[2.346,2.035]],"v":[[19.896,-54.149],[37.88,-77.508]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[31.33,-41.355],[-6.701,-5.813]],"o":[[0,0],[5.249,4.553]],"v":[[52.828,-125.71],[93.066,-177.974]],"c":true}]},{"t":100,"s":[{"i":[[3.858,-5.092],[-0.825,-0.716]],"o":[[0,0],[0.646,0.561]],"v":[[119.615,-214],[124.57,-220.435]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.092,-13.223],[3.225,-0.461]],"o":[[0,0],[-3.225,0.461]],"v":[[-11.7,-53.501],[-23.447,-74.576]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-13.694,-29.721],[7.248,-1.036]],"o":[[0,0],[-7.248,1.036]],"v":[[-33.744,-123.528],[-60.149,-170.902]],"c":true}]},{"t":100,"s":[{"i":[[-1.641,-3.562],[0.869,-0.124]],"o":[[0,0],[-0.869,0.124]],"v":[[-92.793,-229.25],[-95.958,-234.928]],"c":true}]}]},"nm":"Path 7","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.403921574354,0.454901963472,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 7","hd":false},{"ty":"tr","p":{"a":0,"k":[40.287,1.6]},"a":{"a":0,"k":[40.287,1.6]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"rose splashes 40%","sr":1,"ks":{"o":{"a":0,"k":40},"p":{"a":0,"k":[255.462,250.532,0]},"a":{"a":0,"k":[12.462,7.532,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.498,-1.717],[-2.802,-3.967]],"o":[[-1.32,0.907],[0,0]],"v":[[-27.909,-69.864],[-23.485,-62.918]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[5.583,-3.836],[-6.261,-8.864]],"o":[[-2.949,2.026],[0,0]],"v":[[-77.365,-168.395],[-67.479,-152.872]],"c":true}]},{"t":100,"s":[{"i":[[0.692,-0.476],[-0.776,-1.099]],"o":[[-0.366,0.251],[0,0]],"v":[[-104.452,-204.425],[-103.226,-202.5]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.758,-1.202],[3.089,-1.172]],"o":[[0,0],[-3.089,1.172]],"v":[[-40.584,-41.866],[-51.165,-45.778]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-10.775,-2.722],[6.996,-2.654]],"o":[[0,0],[-6.996,2.654]],"v":[[-111.836,-92.023],[-135.799,-100.881]],"c":true}]},{"t":100,"s":[{"i":[[-1.235,-0.312],[0.802,-0.304]],"o":[[0,0],[-0.802,0.304]],"v":[[-194.867,-116.25],[-197.612,-117.265]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.418,17.711],[-4.713,-1.091]],"o":[[0,0],[2.758,0.638]],"v":[[-22.297,35.041],[-30.452,64.628]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-14.835,40.938],[-10.895,-2.522]],"o":[[0,0],[6.375,1.475]],"v":[[-62.088,115.52],[-80.938,183.907]],"c":true}]},{"t":100,"s":[{"i":[[-1.49,4.112],[-1.094,-0.253]],"o":[[0,0],[0.64,0.148]],"v":[[-89,203.172],[-90.893,210.041]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.125,2.009],[-1.205,-2.41]],"o":[[-0.644,-1.15],[1.205,2.41]],"v":[[60.208,34.488],[58.28,36.577]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[2.266,4.046],[-2.428,-4.856]],"o":[[-1.297,-2.316],[2.428,4.856]],"v":[[161.46,114.372],[157.576,118.58]],"c":true}]},{"t":100,"s":[{"i":[[0.457,0.815],[-0.489,-0.978]],"o":[[-0.261,-0.467],[0.489,0.978]],"v":[[178.425,143.467],[177.643,144.315]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.378,1.888],[-0.054,-1.294]],"o":[[-0.244,-1.218],[0.054,1.294]],"v":[[57.201,-37.513],[53.318,-36.704]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[0.721,3.602],[-0.103,-2.47]],"o":[[-0.465,-2.323],[0.103,2.47]],"v":[[152.987,-80.688],[145.577,-79.144]],"c":true}]},{"t":100,"s":[{"i":[[0.177,0.884],[-0.025,-0.606]],"o":[[-0.114,-0.57],[0.025,0.606]],"v":[[207.62,-96.485],[205.801,-96.106]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.033,1.484],[-0.082,-1.894]],"o":[[0.031,-1.392],[0.082,1.894]],"v":[[53.595,-30.294],[48.408,-29.669]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-0.074,3.328],[-0.185,-4.247]],"o":[[0.069,-3.121],[0.185,4.247]],"v":[[143.792,-61.221],[132.159,-59.821]],"c":true}]},{"t":100,"s":[{"i":[[-0.009,0.404],[-0.022,-0.516]],"o":[[0.008,-0.379],[0.022,0.516]],"v":[[216.285,-76.436],[214.873,-76.266]],"c":true}]}]},"nm":"Path 6","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.403921574354,0.454901963472,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 6","hd":false},{"ty":"tr","p":{"a":0,"k":[12.462,7.532]},"a":{"a":0,"k":[12.462,7.532]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"violet","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[33.333]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[100]},{"t":70,"s":[0]}]},"p":{"a":0,"k":[250.172,221.61,0]},"a":{"a":0,"k":[7.172,-21.39,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.629,0.629,0.629],"y":[0,0,0]},"t":30,"s":[43.235,43.235,100]},{"t":40,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[8.424,-5.071],[2.653,-10.693],[8.493,-0.25],[-0.281,-21.575],[8.206,2.479],[-9.071,-18.319],[10.163,-0.826],[-8.504,-13.367],[7.925,-6.142],[-9.44,-13.344],[2.854,-8.942],[-21.641,-24.17],[6.753,-1.287],[-6.374,-4.022],[10.533,-2.098],[-13.32,-3.511],[-1.839,-6.483],[-21.075,-3.827],[-5.954,-6.816],[-11.932,-2.571],[3.257,-17.575],[-38.942,9.134],[-0.325,-7.706],[-8.742,1.73],[-8.801,-7.785],[-30.134,14.717],[-9.382,-4.129],[-16.279,6.661],[1.088,-8.515],[-7.687,7.097],[-6.012,-8.868],[-10.651,19.166],[-10.35,0.696],[-9.579,21.376],[-8.09,4.605],[-5.131,22.602],[-3.808,-4.758],[-0.748,8.961],[-7.254,-2.788],[2.909,7.968],[-7.635,-1.047],[9.08,17.754],[-7.507,3.193],[9.997,10.579],[-6.351,4.692],[9.781,8.968],[0.178,6.817],[17.454,12.482],[0.03,6.973],[12.265,1.257],[-5.162,4.439],[17.018,1.365],[-5.218,7.12],[24.328,-1.527],[-3.121,9.666],[14.883,-6.778],[10.864,5.993],[11.338,-8.563],[5.168,9.097],[9.336,-10.339],[5.422,9.555],[17.401,-17.591],[8.409,7.467],[2.648,-7.068],[8.104,5.903],[5.776,-17.779],[7.39,-3.513],[1.572,-7.178],[9.221,3.529],[4.343,-6.683]],"o":[[-7.125,5.075],[-0.627,-20.281],[-8.492,0.25],[-7.605,-11.419],[-6.68,-3.509],[-2.4,-2.857],[-8.37,0.053],[-2.455,-2.437],[-7.318,5.151],[-25.313,-18.266],[-0.866,7.957],[-15.111,-8.219],[-6.731,1.147],[0,0],[-8.775,1.322],[-36.623,-3.316],[2.35,6.725],[-4.284,0.119],[5.62,5.119],[0,0],[-0.325,7.953],[-14.317,6.526],[0.333,6.979],[-5.382,3.713],[7.32,4.607],[-4.424,3.372],[7.613,2.532],[-10.42,10.905],[-1.668,7.104],[-2.075,4.92],[4.11,6.36],[-1.424,4.903],[7.699,-1.121],[0,0],[8.176,-4.621],[-1.052,23.086],[4.14,4.652],[5.615,9.885],[7.25,2.786],[5.203,5.546],[7.441,1.093],[15.761,17.732],[7.507,-3.193],[5.904,3.732],[6.351,-4.692],[9.84,5.408],[-0.622,-6.552],[19.337,4.71],[0.329,-7.351],[6.418,-1.574],[5.14,-4.409],[27.522,-2.876],[5.215,-7.117],[4.426,-6.277],[2.875,-9.309],[1.399,-5.562],[-7.079,-5.34],[4.34,-9.001],[-5.002,-8.493],[13.638,-18.255],[-5.312,-9.225],[0.333,-7.822],[-7.414,-6.268],[3.497,-13.498],[-8.104,-5.903],[-0.973,-5.144],[-8.335,3.293],[-2.994,-1.674],[-9.108,-3.513],[-0.006,-3.457]],"v":[[5.228,-95.512],[7.462,-28.929],[-2.195,-87.293],[6.416,-28.961],[-31.261,-81.602],[2.547,-32.204],[-43.849,-81.375],[0.273,-32.419],[-44.643,-71.2],[-1.483,-31.501],[-68.757,-71.437],[-0.116,-26.024],[-42.303,-41.201],[-2.32,-26.028],[-60.656,-35.365],[-6.28,-25.891],[-67.569,-24.781],[-6.096,-24.791],[-61.84,-18.285],[-3.524,-23.253],[-83.167,12.168],[-3.472,-21.52],[-43.616,2.576],[-3.348,-19.306],[-64.141,31.565],[0.002,-18.262],[-50.895,27.819],[3.739,-17.431],[-29.038,27.111],[2.554,-14.294],[-16.457,46.001],[4.192,-11.614],[-10.643,61.853],[6.528,-12.171],[7.719,52.216],[8.895,-14.393],[13.275,34.112],[10.222,-12.355],[26.825,31.666],[12.417,-11.04],[35.999,36.579],[14.675,-10.481],[49.25,44.887],[15.923,-11.135],[54.41,30.884],[17.017,-12.053],[59.976,24.143],[15.598,-15.029],[60.134,6.365],[15.406,-16.076],[56.972,-8.479],[17.766,-17.986],[90.136,-9.191],[19.955,-19.743],[79.759,-35.208],[20.474,-22.64],[63.92,-44.118],[18.736,-24.334],[63.006,-55.052],[17.431,-26.232],[77.149,-67.057],[16.295,-27.947],[47.537,-73.383],[14.878,-29.037],[41.274,-77.714],[13.733,-29.91],[25.971,-72.901],[12.306,-29.047],[19.524,-85.462],[9.122,-30.564]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[8.849,0.01],[7.184,-28.957],[3.052,-1.273],[-0.761,-58.423],[7.839,-5.353],[-24.563,-49.607],[9.192,-3.975],[-23.029,-36.198],[4.861,-7.946],[-25.563,-36.135],[12.25,-6.125],[-58.602,-65.452],[0.423,-3.502],[-17.26,-10.89],[11.497,-5.096],[-36.071,-9.509],[-2.201,-1.041],[-57.07,-10.364],[-1.998,-10.013],[-32.312,-6.962],[9.36,-30.715],[-105.454,24.735],[0.047,-4.178],[-23.672,4.686],[-7.461,-15.968],[-81.601,39.854],[-8.157,-7.347],[-44.083,18.039],[-3.026,-7.306],[-20.815,19.217],[-8.186,-10.792],[-28.842,51.901],[-12.022,-1.924],[-25.94,57.885],[-10.365,1.613],[-13.894,61.205],[-1.581,0.481],[-2.025,24.266],[-5.413,1.006],[7.876,21.576],[-4.573,1.132],[24.589,48.078],[-6.41,1.365],[27.072,28.648],[-4.858,2.346],[26.485,24.286],[-6.451,3.968],[47.266,33.8],[-2.771,2.906],[33.213,3.403],[-1.167,1.667],[46.083,3.696],[-1.333,9],[65.879,-4.134],[-5.083,7.361],[40.303,-18.354],[15.513,2.68],[30.702,-23.187],[1.943,7.096],[25.282,-27.999],[2.5,7.5],[47.12,-47.637],[6.155,7.421],[7.17,-19.139],[5.169,3.176],[15.641,-48.146],[3.103,0.727],[4.257,-19.437],[6.927,0.949],[11.76,-18.098]],"o":[[-5.323,-0.006],[-1.697,-54.919],[-3.052,1.273],[-20.594,-30.923],[-3.728,2.546],[-6.498,-7.737],[-4.346,1.879],[-6.647,-6.6],[-3.22,5.264],[-68.548,-49.463],[-6.872,3.436],[-40.921,-22.258],[-0.377,3.121],[0,0],[-6.756,2.995],[-99.173,-8.979],[3.585,1.695],[-11.602,0.322],[1.08,5.412],[0,0],[-1.42,4.661],[-38.77,17.671],[-0.025,2.213],[-14.575,10.054],[3.439,7.359],[-11.98,9.131],[3.354,3.021],[-28.218,29.53],[1.451,3.503],[-5.619,13.324],[3.041,4.009],[-3.855,13.277],[4.839,0.774],[0,0],[10.589,-1.648],[-2.849,62.516],[2.488,-0.757],[15.206,26.767],[5.413,-1.006],[14.09,15.019],[4.055,-1.004],[42.68,48.017],[6.41,-1.365],[15.989,10.107],[4.858,-2.346],[26.647,14.644],[5.257,-3.234],[52.364,12.754],[3.745,-3.927],[17.38,-4.263],[1.115,-1.592],[74.53,-7.789],[1.333,-9],[11.985,-16.998],[4.419,-6.4],[3.79,-15.061],[-5.258,-0.908],[11.752,-24.375],[-1.495,-5.458],[36.932,-49.434],[-2.202,-6.607],[0.901,-21.183],[-3.464,-4.176],[9.469,-36.552],[-5.169,-3.176],[-2.634,-13.929],[-5.658,-1.325],[-8.107,-4.533],[-6.628,-0.908],[-0.017,-9.362]],"v":[[3.02,-162.999],[8.243,-41.492],[-9.448,-140.273],[5.428,-41.577],[-66.581,-136.474],[-4.982,-50.303],[-94.937,-141.294],[-11.103,-50.883],[-91.631,-117.558],[-15.827,-48.412],[-150.75,-126.625],[-12.148,-33.673],[-76.677,-54.762],[-18.079,-33.683],[-125.935,-48.926],[-28.737,-33.315],[-144.516,-27.935],[-28.241,-30.355],[-129.957,-16.393],[-21.32,-26.218],[-189.443,52.219],[-21.179,-21.553],[-84.613,22.128],[-20.845,-15.595],[-143.927,91.798],[-11.831,-12.788],[-109.552,77.33],[-1.776,-10.549],[-60.574,69.369],[-4.963,-2.109],[-41.055,117.272],[-0.555,5.102],[-30.196,155.83],[5.729,3.604],[8.665,130.425],[12.099,-2.376],[18.32,81.416],[15.669,3.108],[41.962,76.131],[21.577,6.648],[62.49,91.451],[27.654,8.15],[95.923,118.365],[31.011,6.393],[102.975,84.18],[33.954,3.923],[112.957,71.446],[30.136,-4.087],[107.438,31.594],[29.62,-6.903],[94.5,0.667],[35.97,-12.045],[183.167,5],[41.86,-16.772],[147.562,-47.507],[43.258,-24.567],[106.809,-61.148],[38.582,-29.125],[103.057,-80.596],[35.068,-34.233],[141.167,-109],[32.012,-38.849],[76.865,-113.118],[28.198,-41.782],[66.503,-119.657],[25.119,-44.132],[37.324,-105.068],[21.279,-41.807],[29.931,-135.604],[12.71,-45.889]],"c":true}]},{"t":100,"s":[{"i":[[6.52,-0.499],[0,0],[7.552,-1.773],[0,0],[7.419,-3.974],[0,0],[5.063,-2.794],[0,0],[3.869,-4.058],[0,0],[3.25,-5.125],[0,0],[2.323,-7.262],[0,0],[1.065,-7.426],[0,0],[-0.016,-6.935],[0,0],[0.043,-8.393],[0,0],[-1.943,-5.281],[0,0],[-4.613,-6.872],[0,0],[-4.427,-4.702],[0,0],[-5.552,-5.67],[0,0],[-7.074,-5.131],[0,0],[-7.555,-2.728],[0,0],[-8.696,0.33],[0,0],[-5.835,-0.075],[0,0],[-5.179,-0.584],[0,0],[-7.538,4.131],[0,0],[-4.51,1.951],[0,0],[-7.077,4.865],[0,0],[-5.025,3.68],[0,0],[-5.043,6.946],[0,0],[-2.062,4.094],[0,0],[-1.5,7.666],[0,0],[-1.333,7.5],[0,0],[-0.438,6.493],[0,0],[2.309,7.852],[0,0],[1.057,4.404],[0,0],[1.667,6.5],[0,0],[6.365,6.382],[0,0],[3.503,2.843],[0,0],[5.324,3.432],[0,0],[2.931,0.896],[0,0]],"o":[[-6.521,0.499],[0,0],[-7.552,1.773],[0,0],[-7.419,3.974],[0,0],[-5.063,2.794],[0,0],[-3.869,4.058],[0,0],[-3.25,5.125],[0,0],[-2.323,7.262],[0,0],[-1.065,7.426],[0,0],[0.016,6.935],[0,0],[-0.043,8.393],[0,0],[1.943,5.281],[0,0],[4.613,6.872],[0,0],[4.427,4.702],[0,0],[5.552,5.67],[0,0],[7.074,5.131],[0,0],[7.555,2.728],[0,0],[8.696,-0.33],[0,0],[5.835,0.075],[0,0],[5.18,0.584],[0,0],[7.538,-4.131],[0,0],[4.51,-1.951],[0,0],[7.077,-4.865],[0,0],[5.025,-3.68],[0,0],[5.043,-6.946],[0,0],[2.062,-4.094],[0,0],[1.5,-7.666],[0,0],[1.333,-7.5],[0,0],[0.438,-6.493],[0,0],[-2.309,-7.852],[0,0],[-1.057,-4.404],[0,0],[-1.667,-6.5],[0,0],[-6.365,-6.382],[0,0],[-3.503,-2.843],[0,0],[-5.324,-3.432],[0,0],[-2.931,-0.896],[0,0]],"v":[[0.021,-238.999],[6.522,-22.25],[-22.948,-238.773],[6.486,-22.251],[-111.081,-205.974],[6.353,-22.363],[-136.437,-188.794],[6.274,-22.37],[-151.131,-175.058],[6.214,-22.339],[-176.75,-144.125],[6.261,-22.15],[-199.177,-102.262],[6.185,-22.15],[-209.935,-65.926],[6.048,-22.145],[-215.016,-31.435],[6.055,-22.107],[-215.457,-11.893],[6.143,-22.054],[-201.443,57.219],[6.145,-21.994],[-194.113,74.128],[6.149,-21.918],[-171.427,112.298],[6.265,-21.882],[-163.052,122.83],[6.394,-21.853],[-126.074,156.869],[6.353,-21.745],[-65.055,189.772],[6.41,-21.653],[-38.196,196.33],[6.49,-21.672],[9.165,202.425],[6.572,-21.748],[31.821,199.416],[6.618,-21.678],[80.962,187.631],[6.693,-21.633],[105.49,178.451],[6.771,-21.613],[127.923,167.865],[6.814,-21.636],[157.975,144.18],[6.852,-21.668],[174.957,125.946],[6.803,-21.77],[204.438,84.094],[6.796,-21.806],[223.5,34.166],[6.878,-21.872],[227.667,12],[6.953,-21.933],[227.062,-61.507],[6.971,-22.033],[213.309,-104.148],[6.911,-22.091],[198.057,-137.596],[6.866,-22.157],[195.667,-144],[6.827,-22.216],[142.865,-200.118],[6.778,-22.254],[122.503,-211.157],[6.739,-22.284],[84.324,-230.568],[6.689,-22.254],[51.431,-240.604],[6.58,-22.306]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":80},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.001,0.467,0.031,0.541,0.707,0.435,0.108,0.561,1,0.404,0.184,0.58]}},"s":{"a":0,"k":[4,-20]},"e":{"a":0,"k":[177.39,-20]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 3","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0}]},{"id":"comp_4","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"picies2","sr":1,"ks":{"p":{"a":0,"k":[257.33,185.73,0]},"a":{"a":0,"k":[-265.67,-10.27,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,-0.35],[53.84,0],[-19.93,11.56],[-1,-40.01]],"o":[[0,50.03],[-31.67,-34.87],[39.26,-0.87],[0.01,0.35]],"v":[[-168.19,-10.27],[-265.67,80.32],[-266.5,-79.82],[-168.2,-11.32]],"c":true}]},{"t":35,"s":[{"i":[[0,-0.004],[0.613,0],[-0.227,0.132],[-0.011,-0.455]],"o":[[0,0.569],[-0.36,-0.397],[0.447,-0.01],[0,0.004]],"v":[[-145.03,70.7],[-146.14,71.731],[-146.149,69.909],[-145.03,70.688]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-2.5,-7],[15.65,-61.05],[-53.84,0]],"o":[[0,0],[0,-50.03],[0,0]],"v":[[-266.5,-79.82],[-363.15,-10.27],[-265.67,-100.86]],"c":true}]},{"t":35,"s":[{"i":[[-0.013,-0.036],[0.08,-0.312],[-0.275,0]],"o":[[0,0],[0,-0.256],[0,0]],"v":[[-383.165,-126.689],[-383.659,-126.333],[-383.161,-126.797]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.6,-49.55],[39.26,-0.87],[0,0]],"o":[[-1,-40.01],[-2.5,-7],[53.46,0]],"v":[[-168.2,-11.32],[-266.5,-79.82],[-265.67,-100.86]],"c":true}]},{"t":35,"s":[{"i":[[-0.018,-1.483],[1.175,-0.026],[0,0]],"o":[[-0.03,-1.198],[-0.075,-0.21],[1.6,0]],"v":[[-162.091,-102.75],[-165.034,-104.8],[-165.009,-105.43]],"c":true}]}]},"nm":"Path 3","hd":false},{"ty":"fl","c":{"a":0,"k":[0.749019607843,0,0.690196078431,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 3","hd":false},{"ty":"tr","p":{"a":0,"k":[-265.67,-10.27]},"a":{"a":0,"k":[-265.67,-10.27]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-31.67,-34.87],[0,50.03],[0,0]],"o":[[-53.84,0],[15.65,-61.05],[-19.93,11.56]],"v":[[-265.67,80.32],[-363.15,-10.27],[-266.5,-79.82]],"c":true}]},{"t":35,"s":[{"i":[[-0.099,-0.109],[0,0.156],[0,0]],"o":[[-0.168,0],[0.049,-0.191],[-0.062,0.036]],"v":[[-415.258,90.5],[-415.562,90.217],[-415.26,90]],"c":true}]}]},"nm":"Path 4","hd":false},{"ty":"fl","c":{"a":0,"k":[0.529411764706,0,0.486274539723,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 3","hd":false},{"ty":"tr","p":{"a":0,"k":[-314.41,0.25]},"a":{"a":0,"k":[-314.41,0.25]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"st","c":{"a":0,"k":[0.301960784314,0,0.274509803922,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":24},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-265.67,-10.27]},"a":{"a":0,"k":[-265.67,-10.27]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":30,"op":35,"st":2,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"picies","sr":1,"ks":{"p":{"a":0,"k":[255,191.406,0]},"a":{"a":0,"k":[0,45.406,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[-14.81,-13.46],[11.95,21.92],[1.11,16.11],[-22.44,-7.84],[-11.16,1.15]],"o":[[-22.91,4.11],[-6.67,-12.23],[5.79,24.31],[9.97,3.48],[-0.13,0.68]],"v":[[10.978,133.929],[-49.332,107.219],[-76.002,35.549],[-29.982,86.199],[2.028,89.909]],"c":true}]},{"t":36,"s":[{"i":[[0.042,0.009],[-0.025,-0.045],[-0.002,-0.033],[0.03,0.046],[0.013,0.008]],"o":[[0.048,-0.009],[0.014,0.025],[-0.012,-0.05],[-0.009,-0.013],[-0.02,-0.013]],"v":[[-71.68,195.17],[-71.555,195.225],[-71.5,195.374],[-71.582,195.231],[-71.615,195.198]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[-20.7,35.34],[6.66,-12.23],[15.67,-2.82],[0,0],[-0.13,0.68]],"o":[[-1.11,16.11],[-8.18,15],[0,0],[-14.81,-13.46],[25.91,-2.68]],"v":[[75.998,35.549],[49.338,107.219],[10.988,133.929],[10.978,133.929],[2.028,89.909]],"c":true}]},{"t":36,"s":[{"i":[[-0.149,0.254],[0.048,-0.088],[0.112,-0.02],[0,0],[-0.001,0.005]],"o":[[-0.008,0.116],[-0.059,0.108],[0,0],[0.129,-0.1],[0.093,-0.157]],"v":[[73.967,180.898],[73.775,181.412],[73.5,181.604],[73.5,181.604],[73.665,181.395]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[4.69,-50.46],[25.91,-2.68],[9.97,3.48],[-5.65,29.96],[19.36,23.12]],"o":[[-20.7,35.34],[-11.16,1.15],[25.15,-11.39],[3.71,-19.59],[38.33,2.51]],"v":[[75.998,35.549],[2.028,89.909],[-29.982,86.199],[26.648,21.049],[6.078,-43.891]],"c":true}]},{"t":36,"s":[{"i":[[0.019,-0.202],[0.104,-0.011],[0.04,0.014],[-0.023,0.12],[0.077,0.092]],"o":[[-0.083,0.141],[-0.045,0.005],[0.101,-0.046],[0.015,-0.078],[0.153,0.01]],"v":[[154.424,34.876],[154.128,35.094],[154,35.079],[154.226,34.818],[154.144,34.559]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[3.71,-19.59],[21,-18.31],[-37.83,-2.49]],"o":[[-10.36,-22.61],[4.41,-44.16],[19.36,23.12]],"v":[[26.648,21.049],[-76.002,18.639],[6.078,-43.891]],"c":true}]},{"t":36,"s":[{"i":[[0.012,-0.061],[0.066,-0.057],[-0.118,-0.008]],"o":[[-0.032,-0.071],[0.014,-0.138],[0.06,0.072]],"v":[[1.192,-97.167],[0.872,-97.174],[1.128,-97.369]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[-10.36,-22.61],[25.15,-11.39],[5.79,24.31],[-0.53,5.3]],"o":[[-5.65,29.96],[-22.44,-7.84],[-0.55,-5.97],[21,-18.31]],"v":[[26.648,21.049],[-29.982,86.199],[-76.002,35.549],[-76.002,18.639]],"c":true}]},{"t":36,"s":[{"i":[[-0.156,-0.229],[0.005,-0.198],[0.125,0.524],[-0.011,0.114]],"o":[[0.175,0.257],[-0.483,-0.169],[-0.012,-0.129],[0.113,0.148]],"v":[[-149.053,15.247],[-148.42,15.955],[-149.412,14.864],[-149.412,14.5]],"c":true}]}]},"nm":"Path 5","hd":false},{"ty":"fl","c":{"a":0,"k":[0.749019607843,0,0.690196078431,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 5","hd":false},{"ty":"st","c":{"a":0,"k":[0.301960784314,0,0.274509803922,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":24},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,45.406]},"a":{"a":0,"k":[0,45.406]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":31,"op":36,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 3","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[256]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":21,"s":[257.445]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[251.735]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[262.162]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[256.384]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[256.888]},{"t":29,"s":[254.404]}]},"y":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[198]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[190.425]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[197.133]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[186.523]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[194.791]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26,"s":[186.058]},{"t":29,"s":[194.761]}]}},"a":{"a":0,"k":[50,50,0]}},"ao":0,"ip":20,"op":30,"st":-60,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Null 19","parent":3,"sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[50,108,0],"to":[0,0,0],"ti":[0,0,0]},{"t":30,"s":[50,1,0]}]}},"ao":0,"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"blik","parent":6,"sr":1,"ks":{"p":{"a":0,"k":[20.897,-88.771,0]},"a":{"a":0,"k":[19.25,-92,0]},"s":{"a":0,"k":[70,70,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.427,-0.298],[-30.5,146.5]],"o":[[0,13.193],[-55,1.5],[-0.5,0]],"v":[[80.444,-33.889],[0,-10],[0,-200]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-61.227],[24.905,-1.3],[-8.202,27.448]],"o":[[0,40.169],[-45,-4.333],[2.417,-2.431]],"v":[[98.282,62.507],[32.976,130.655],[10.893,-24.206]],"c":true}]},{"t":30,"s":[{"i":[[0,-77.778],[23.059,-1.647],[-4.109,3.995]],"o":[[0,50.031],[-47.216,-6.039],[3.294,-3.203]],"v":[[109.918,84.947],[41.535,169.497],[12.437,7.17]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":8},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-131]},{"t":30,"s":[-5]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8.571},"lc":2,"lj":2,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":2}},{"n":"g","nm":"gap","v":{"a":0,"k":12}},{"n":"d","nm":"dash2","v":{"a":0,"k":297}},{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":30,"st":-60,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"rocket_cap","parent":10,"sr":1,"ks":{"p":{"a":0,"k":[0,-33.889,0]},"a":{"a":0,"k":[0,-33.889,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.427,-0.298],[-30.5,146.5]],"o":[[0,13.193],[-55,1.5],[-0.5,0]],"v":[[80.444,-33.889],[0,-10],[0,-200]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-61.227],[24.905,-1.3],[-8.202,27.448]],"o":[[0,40.169],[-45,-4.333],[2.417,-2.431]],"v":[[87.389,21.435],[22.083,89.583],[0,-65.278]],"c":true}]},{"t":30,"s":[{"i":[[0,-77.778],[23.059,-1.647],[-4.109,3.995]],"o":[[0,50.031],[-47.216,-6.039],[3.294,-3.203]],"v":[[97.776,39.412],[29.392,123.961],[0.294,-38.366]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.749019607843,0,0.690196078431,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.428,0],[0,13.193],[0,0]],"o":[[0,13.193],[-44.428,0],[0,-13.193],[0,0]],"v":[[80.444,-33.889],[0,-10],[-80.444,-33.889],[0,-200]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-61.227],[48.264,0],[0,40.168],[0,0]],"o":[[0,40.168],[-48.264,0],[0,-61.227],[0,0]],"v":[[87.389,21.435],[0,94.167],[-87.389,21.435],[0,-65.278]],"c":true}]},{"t":30,"s":[{"i":[[0,-77.778],[53.838,0],[0,50.031],[0,0]],"o":[[0,50.031],[-53.838,0],[0,-77.778],[0,0]],"v":[[97.776,39.412],[0.294,130],[-97.187,39.412],[0.294,-38.366]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.529411764706,0,0.486274539723,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-105]},"a":{"a":0,"k":[0,-105]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.428,0],[0,13.193],[-44.428,0]],"o":[[0,13.193],[-44.428,0],[0,-13.193],[44.428,0]],"v":[[80.444,-33.889],[0,-10],[-80.444,-33.889],[0,-57.778]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-40.168],[48.264,0],[0,40.168],[-48.264,0]],"o":[[0,40.168],[-48.264,0],[0,-40.168],[48.264,0]],"v":[[87.389,21.435],[0,94.167],[-87.389,21.435],[0,-51.296]],"c":true}]},{"t":30,"s":[{"i":[[0,-50.031],[53.838,0],[0,50.031],[-53.838,0]],"o":[[0,50.031],[-53.838,0],[0,-50.031],[53.838,0]],"v":[[97.776,39.412],[0.294,130],[-97.187,39.412],[0.294,-51.176]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[0.749019607843,0,0.690196078431,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-33.889]},"a":{"a":0,"k":[0,-33.889]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"st","c":{"a":0,"k":[0.3,0,0.276439771465,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":24},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"rocket body 2 outlines","parent":10,"sr":1,"ks":{"p":{"a":0,"k":[0,71.389,0]},"a":{"a":0,"k":[0,71.389,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[2.222,-10],[7.734,34.804],[0,0]],"o":[[0,0],[-7.676,34.543],[-2.222,-10],[0,0]],"v":[[68.778,-37.222],[59.333,153.333],[-59.333,153.333],[-68.778,-37.222]],"c":true}]},{"t":30,"s":[{"i":[[0,0],[6.667,-12.222],[20.133,36.91],[1.111,16.111]],"o":[[-1.111,16.111],[-20.133,36.911],[-6.667,-12.222],[0,0]],"v":[[76,35.556],[49.333,107.222],[-49.333,107.222],[-76,35.556]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.301960784314,0,0.274509803922,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":12},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"rocket body 2 shadows","parent":10,"sr":1,"ks":{"o":{"a":0,"k":50},"p":{"a":0,"k":[0,71.389,0]},"a":{"a":0,"k":[0,71.389,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0.667,-19.333],[7.333,22.167],[0,0]],"o":[[0,0],[0.167,0.667],[-3.217,-9.726],[0,0]],"v":[[-13.222,-18.222],[-12.667,177.333],[-59.333,153.333],[-68.778,-37.222]],"c":true}]},{"t":30,"s":[{"i":[[0,0],[0.167,-11.222],[18.833,29.278],[1.111,16.111]],"o":[[-1.111,16.111],[-15.833,-1.222],[-7.532,-11.709],[0,0]],"v":[[8,37.056],[7.333,133.222],[-49.333,107.222],[-76,35.556]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.3,0,0.274509803922,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"lines2","parent":10,"sr":1,"ks":{"p":{"a":0,"k":[-4.5,135.446,0]},"a":{"a":0,"k":[-4.5,135.446,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[19,23.25],[0,0],[-72.25,-0.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-45,-7]],"v":[[-67.5,-34.25],[-66,-10.75],[65.25,58.5],[66.5,12.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[19,23.25],[0,0],[-70.25,-2.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-45,-7]],"v":[[-67.5,-34.25],[-67,-26.25],[65.75,25],[68.5,-17]],"c":true}]},{"t":10,"s":[{"i":[[19,23.25],[0,0],[-70.25,-2.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-45,-7]],"v":[[-67.5,-34.25],[-67,-26.25],[68.25,-15],[68.5,-17]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[25,44.25],[0,0],[-71.75,9],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-65,33.75],[-62.5,74.75],[60.75,137],[62.5,98]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[25,44.25],[0,0],[-71.75,10.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-66.5,2.75],[-65,41.25],[61.75,99],[65,63]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[26,30.25],[0,0],[-71.75,10.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-68.5,-6.75],[-66,19.75],[62.75,70.5],[68,37]],"c":true}]},{"t":15,"s":[{"i":[[26,30.25],[0,0],[-54.75,16],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-68.5,-6.75],[-65.5,15.25],[65.75,49],[71,14]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[20,16.5],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[13.25,13.75],[-0.25,0],[-50.75,19]],"v":[[-56.25,160.5],[-56,160.5],[49.25,167.75],[49.75,168]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[20,16.5],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[13.25,13.75],[-0.25,0],[-50.75,19]],"v":[[-54.5,154.25],[-54.25,154.25],[43,161.5],[43.5,161.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[20,16.5],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[13.25,13.75],[-0.25,0],[-50.75,19]],"v":[[-53.75,148],[-53.5,148],[42.25,155.5],[42.75,155.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[17.5,26],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-52.5,136.25],[-52.75,136.25],[42.25,147.75],[42.5,147.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-57.25,102.5],[-55,120.25],[43.25,137.5],[44,133.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-59.25,89.5],[-54.25,106.75],[42.5,128],[48,118.75]],"c":true}]},{"t":30,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-58.5,71.75],[-53.5,89],[43.25,110.25],[48.75,101]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[19,27.5],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-58.5,158.5],[-58.25,158.5],[53.5,166],[54,165]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[19,27.5],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-57.25,147],[-57.25,147.5],[50.5,160],[50,161]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[24.25,45.5],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-60.5,106.5],[-58.75,138.5],[51,150],[51.25,149.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[24.25,45.5],[0,0],[-54.5,29.25],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-62.5,78],[-58.25,106.75],[49,141.75],[53.5,124.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-62.5,67.5],[-59.5,89.75],[50,121.5],[54.5,104.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[21.25,35.75],[0,0],[-49.75,27],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-62.5,63.25],[-60.75,81.25],[52.25,106.5],[57.25,81]],"c":true}]},{"t":30,"s":[{"i":[[21.25,35.75],[0,0],[-49.75,27],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-62.5,63.25],[-60.75,70.5],[56.5,84.75],[57.25,81]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[25,44.25],[0,0],[-47,36.25],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.5,40.25]],"v":[[-61.5,114.75],[-59,155.75],[63.25,158],[63.5,157.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[25,44.25],[0,0],[-55,45.75],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-62,77.5],[-60.75,112.25],[60.75,151.25],[59.5,132.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[25,44.25],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-64.25,50.75],[-60.75,80.5],[58.25,128],[60.75,101.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[25,39],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-65.5,33.75],[-63.75,59.5],[59,101.75],[61.5,76.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[25,39],[0,0],[-55.75,26.75],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-69.5,30],[-65,49],[61,82.75],[63.75,62.25]],"c":true}]},{"t":25,"s":[{"i":[[25,39],[0,0],[-55.75,26.75],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-69.5,30],[-65,54.75],[62.25,76.25],[63.75,62.25]],"c":true}]}]},"nm":"Path 4","hd":false},{"ty":"fl","c":{"a":0,"k":[0.913725490196,0.81568627451,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"rocket body","parent":4,"sr":1,"ks":{"p":{"a":0,"k":[0,71.389,0]},"a":{"a":0,"k":[0,71.389,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[2.222,-10],[7.734,34.804],[0,0]],"o":[[0,0],[-7.676,34.543],[-2.222,-10],[0,0]],"v":[[68.778,-37.222],[59.333,153.333],[-59.333,153.333],[-68.778,-37.222]],"c":true}]},{"t":30,"s":[{"i":[[0,0],[6.667,-12.222],[20.133,36.91],[1.111,16.111]],"o":[[-1.111,16.111],[-20.133,36.911],[-6.667,-12.222],[0,0]],"v":[[76,35.556],[49.333,107.222],[-49.333,107.222],[-76,35.556]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.749019607843,0,0.690196078431,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[283,390,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[257,472,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11,"s":[283,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":17,"s":[262,427.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[280,323,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[279,288,0],"to":[0,0,0],"ti":[0,0,0]},{"t":31,"s":[259,328,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":4,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":10,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":11,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":17,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":18,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":24,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":25,"s":[100,100,100]},{"t":31,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":4,"op":31,"st":4,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[255,394,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[257,472,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":13,"s":[258,356,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":19,"s":[262,427.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[255,327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[261,292,0],"to":[0,0,0],"ti":[0,0,0]},{"t":33,"s":[259,328,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":12,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":13,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":19,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":26,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":27,"s":[100,100,100]},{"t":33,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":6,"op":33,"st":6,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[231,397,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14,"s":[252,494,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[232,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[254,428,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[230,336,0],"to":[0,0,0],"ti":[0,0,0]},{"t":28,"s":[256.5,371.5,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":14,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":15,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":21,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":22,"s":[100,100,100]},{"t":28,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":8,"op":28,"st":8,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[283,390,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[257,472,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":9,"s":[283,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[262,427.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[280,323,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[279,288,0],"to":[0,0,0],"ti":[0,0,0]},{"t":29,"s":[259,328,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":2,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":8,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":9,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":15,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":16,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":22,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":23,"s":[100,100,100]},{"t":29,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":2,"op":29,"st":2,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[231,397,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[252,494,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[232,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":13,"s":[254,428,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14,"s":[230,336,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[234,298,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[259,328,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[239,262,0],"to":[0,0,0],"ti":[0,0,0]},{"t":33,"s":[264,326,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":7,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":13,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":14,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":21,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":27,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":28,"s":[100,100,100]},{"t":33,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":34,"st":0,"bm":0}]},{"id":"comp_5","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"picies2","sr":1,"ks":{"p":{"a":0,"k":[257.33,185.73,0]},"a":{"a":0,"k":[-265.67,-10.27,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,-0.35],[53.84,0],[-19.93,11.56],[-1,-40.01]],"o":[[0,50.03],[-31.67,-34.87],[39.26,-0.87],[0.01,0.35]],"v":[[-168.19,-10.27],[-265.67,80.32],[-266.5,-79.82],[-168.2,-11.32]],"c":true}]},{"t":35,"s":[{"i":[[0,-0.004],[0.613,0],[-0.227,0.132],[-0.011,-0.455]],"o":[[0,0.569],[-0.36,-0.397],[0.447,-0.01],[0,0.004]],"v":[[-145.03,70.7],[-146.14,71.731],[-146.149,69.909],[-145.03,70.688]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-2.5,-7],[15.65,-61.05],[-53.84,0]],"o":[[0,0],[0,-50.03],[0,0]],"v":[[-266.5,-79.82],[-363.15,-10.27],[-265.67,-100.86]],"c":true}]},{"t":35,"s":[{"i":[[-0.013,-0.036],[0.08,-0.312],[-0.275,0]],"o":[[0,0],[0,-0.256],[0,0]],"v":[[-383.165,-126.689],[-383.659,-126.333],[-383.161,-126.797]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.6,-49.55],[39.26,-0.87],[0,0]],"o":[[-1,-40.01],[-2.5,-7],[53.46,0]],"v":[[-168.2,-11.32],[-266.5,-79.82],[-265.67,-100.86]],"c":true}]},{"t":35,"s":[{"i":[[-0.018,-1.483],[1.175,-0.026],[0,0]],"o":[[-0.03,-1.198],[-0.075,-0.21],[1.6,0]],"v":[[-162.091,-102.75],[-165.034,-104.8],[-165.009,-105.43]],"c":true}]}]},"nm":"Path 3","hd":false},{"ty":"fl","c":{"a":0,"k":[0.898039275525,0.898039275525,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 3","hd":false},{"ty":"tr","p":{"a":0,"k":[-265.67,-10.27]},"a":{"a":0,"k":[-265.67,-10.27]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-31.67,-34.87],[0,50.03],[0,0]],"o":[[-53.84,0],[15.65,-61.05],[-19.93,11.56]],"v":[[-265.67,80.32],[-363.15,-10.27],[-266.5,-79.82]],"c":true}]},{"t":35,"s":[{"i":[[-0.099,-0.109],[0,0.156],[0,0]],"o":[[-0.168,0],[0.049,-0.191],[-0.062,0.036]],"v":[[-415.258,90.5],[-415.562,90.217],[-415.26,90]],"c":true}]}]},"nm":"Path 4","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843197093,0.643137254902,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 3","hd":false},{"ty":"tr","p":{"a":0,"k":[-314.41,0.25]},"a":{"a":0,"k":[-314.41,0.25]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"st","c":{"a":0,"k":[0.647058823529,0.305882352941,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":24},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-265.67,-10.27]},"a":{"a":0,"k":[-265.67,-10.27]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":30,"op":35,"st":2,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"picies","sr":1,"ks":{"p":{"a":0,"k":[255,191.406,0]},"a":{"a":0,"k":[0,45.406,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[-14.81,-13.46],[11.95,21.92],[1.11,16.11],[-22.44,-7.84],[-11.16,1.15]],"o":[[-22.91,4.11],[-6.67,-12.23],[5.79,24.31],[9.97,3.48],[-0.13,0.68]],"v":[[10.978,133.929],[-49.332,107.219],[-76.002,35.549],[-29.982,86.199],[2.028,89.909]],"c":true}]},{"t":36,"s":[{"i":[[0.042,0.009],[-0.025,-0.045],[-0.002,-0.033],[0.03,0.046],[0.013,0.008]],"o":[[0.048,-0.009],[0.014,0.025],[-0.012,-0.05],[-0.009,-0.013],[-0.02,-0.013]],"v":[[-71.68,195.17],[-71.555,195.225],[-71.5,195.374],[-71.582,195.231],[-71.615,195.198]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[-20.7,35.34],[6.66,-12.23],[15.67,-2.82],[0,0],[-0.13,0.68]],"o":[[-1.11,16.11],[-8.18,15],[0,0],[-14.81,-13.46],[25.91,-2.68]],"v":[[75.998,35.549],[49.338,107.219],[10.988,133.929],[10.978,133.929],[2.028,89.909]],"c":true}]},{"t":36,"s":[{"i":[[-0.149,0.254],[0.048,-0.088],[0.112,-0.02],[0,0],[-0.001,0.005]],"o":[[-0.008,0.116],[-0.059,0.108],[0,0],[0.129,-0.1],[0.093,-0.157]],"v":[[73.967,180.898],[73.775,181.412],[73.5,181.604],[73.5,181.604],[73.665,181.395]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[4.69,-50.46],[25.91,-2.68],[9.97,3.48],[-5.65,29.96],[19.36,23.12]],"o":[[-20.7,35.34],[-11.16,1.15],[25.15,-11.39],[3.71,-19.59],[38.33,2.51]],"v":[[75.998,35.549],[2.028,89.909],[-29.982,86.199],[26.648,21.049],[6.078,-43.891]],"c":true}]},{"t":36,"s":[{"i":[[0.019,-0.202],[0.104,-0.011],[0.04,0.014],[-0.023,0.12],[0.077,0.092]],"o":[[-0.083,0.141],[-0.045,0.005],[0.101,-0.046],[0.015,-0.078],[0.153,0.01]],"v":[[154.424,34.876],[154.128,35.094],[154,35.079],[154.226,34.818],[154.144,34.559]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[3.71,-19.59],[21,-18.31],[-37.83,-2.49]],"o":[[-10.36,-22.61],[4.41,-44.16],[19.36,23.12]],"v":[[26.648,21.049],[-76.002,18.639],[6.078,-43.891]],"c":true}]},{"t":36,"s":[{"i":[[0.012,-0.061],[0.066,-0.057],[-0.118,-0.008]],"o":[[-0.032,-0.071],[0.014,-0.138],[0.06,0.072]],"v":[[1.192,-97.167],[0.872,-97.174],[1.128,-97.369]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[-10.36,-22.61],[25.15,-11.39],[5.79,24.31],[-0.53,5.3]],"o":[[-5.65,29.96],[-22.44,-7.84],[-0.55,-5.97],[21,-18.31]],"v":[[26.648,21.049],[-29.982,86.199],[-76.002,35.549],[-76.002,18.639]],"c":true}]},{"t":36,"s":[{"i":[[-0.156,-0.229],[0.005,-0.198],[0.125,0.524],[-0.011,0.114]],"o":[[0.175,0.257],[-0.483,-0.169],[-0.012,-0.129],[0.113,0.148]],"v":[[-149.053,15.247],[-148.42,15.955],[-149.412,14.864],[-149.412,14.5]],"c":true}]}]},"nm":"Path 5","hd":false},{"ty":"fl","c":{"a":0,"k":[0.898039275525,0.898039275525,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 5","hd":false},{"ty":"st","c":{"a":0,"k":[0.647058823529,0.305882352941,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":24},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,45.406]},"a":{"a":0,"k":[0,45.406]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":31,"op":36,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 3","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[256]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":21,"s":[257.445]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[251.735]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[262.162]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[256.384]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[256.888]},{"t":29,"s":[254.404]}]},"y":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[198]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[190.425]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[197.133]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[186.523]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[194.791]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26,"s":[186.058]},{"t":29,"s":[194.761]}]}},"a":{"a":0,"k":[50,50,0]}},"ao":0,"ip":20,"op":30,"st":-60,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Null 19","parent":3,"sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[50,108,0],"to":[0,0,0],"ti":[0,0,0]},{"t":30,"s":[50,1,0]}]}},"ao":0,"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"blik","parent":6,"sr":1,"ks":{"p":{"a":0,"k":[20.897,-88.771,0]},"a":{"a":0,"k":[19.25,-92,0]},"s":{"a":0,"k":[70,70,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.427,-0.298],[-30.5,146.5]],"o":[[0,13.193],[-55,1.5],[-0.5,0]],"v":[[80.444,-33.889],[0,-10],[0,-200]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-61.227],[24.905,-1.3],[-8.202,27.448]],"o":[[0,40.169],[-45,-4.333],[2.417,-2.431]],"v":[[98.282,62.507],[32.976,130.655],[10.893,-24.206]],"c":true}]},{"t":30,"s":[{"i":[[0,-77.778],[23.059,-1.647],[-4.109,3.995]],"o":[[0,50.031],[-47.216,-6.039],[3.294,-3.203]],"v":[[109.918,84.947],[41.535,169.497],[12.437,7.17]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":8},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-131]},{"t":30,"s":[-5]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8.571},"lc":2,"lj":2,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":2}},{"n":"g","nm":"gap","v":{"a":0,"k":12}},{"n":"d","nm":"dash2","v":{"a":0,"k":297}},{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":30,"st":-60,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"rocket_cap","parent":10,"sr":1,"ks":{"p":{"a":0,"k":[0,-33.889,0]},"a":{"a":0,"k":[0,-33.889,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.427,-0.298],[-30.5,146.5]],"o":[[0,13.193],[-55,1.5],[-0.5,0]],"v":[[80.444,-33.889],[0,-10],[0,-200]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-61.227],[24.905,-1.3],[-8.202,27.448]],"o":[[0,40.169],[-45,-4.333],[2.417,-2.431]],"v":[[87.389,21.435],[22.083,89.583],[0,-65.278]],"c":true}]},{"t":30,"s":[{"i":[[0,-77.778],[23.059,-1.647],[-4.109,3.995]],"o":[[0,50.031],[-47.216,-6.039],[3.294,-3.203]],"v":[[97.776,39.412],[29.392,123.961],[0.294,-38.366]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.898039275525,0.898039275525,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.428,0],[0,13.193],[0,0]],"o":[[0,13.193],[-44.428,0],[0,-13.193],[0,0]],"v":[[80.444,-33.889],[0,-10],[-80.444,-33.889],[0,-200]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-61.227],[48.264,0],[0,40.168],[0,0]],"o":[[0,40.168],[-48.264,0],[0,-61.227],[0,0]],"v":[[87.389,21.435],[0,94.167],[-87.389,21.435],[0,-65.278]],"c":true}]},{"t":30,"s":[{"i":[[0,-77.778],[53.838,0],[0,50.031],[0,0]],"o":[[0,50.031],[-53.838,0],[0,-77.778],[0,0]],"v":[[97.776,39.412],[0.294,130],[-97.187,39.412],[0.294,-38.366]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843197093,0.643137254902,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-105]},"a":{"a":0,"k":[0,-105]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.428,0],[0,13.193],[-44.428,0]],"o":[[0,13.193],[-44.428,0],[0,-13.193],[44.428,0]],"v":[[80.444,-33.889],[0,-10],[-80.444,-33.889],[0,-57.778]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-40.168],[48.264,0],[0,40.168],[-48.264,0]],"o":[[0,40.168],[-48.264,0],[0,-40.168],[48.264,0]],"v":[[87.389,21.435],[0,94.167],[-87.389,21.435],[0,-51.296]],"c":true}]},{"t":30,"s":[{"i":[[0,-50.031],[53.838,0],[0,50.031],[-53.838,0]],"o":[[0,50.031],[-53.838,0],[0,-50.031],[53.838,0]],"v":[[97.776,39.412],[0.294,130],[-97.187,39.412],[0.294,-51.176]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[0.898039275525,0.898039275525,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-33.889]},"a":{"a":0,"k":[0,-33.889]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"st","c":{"a":0,"k":[0.647058823529,0.305882352941,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":24},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"rocket body 2 outlines","parent":10,"sr":1,"ks":{"p":{"a":0,"k":[0,71.389,0]},"a":{"a":0,"k":[0,71.389,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[2.222,-10],[7.734,34.804],[0,0]],"o":[[0,0],[-7.676,34.543],[-2.222,-10],[0,0]],"v":[[68.778,-37.222],[59.333,153.333],[-59.333,153.333],[-68.778,-37.222]],"c":true}]},{"t":30,"s":[{"i":[[0,0],[6.667,-12.222],[20.133,36.91],[1.111,16.111]],"o":[[-1.111,16.111],[-20.133,36.911],[-6.667,-12.222],[0,0]],"v":[[76,35.556],[49.333,107.222],[-49.333,107.222],[-76,35.556]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.647058823529,0.305882352941,0,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":12},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"rocket body 2 shadows","parent":10,"sr":1,"ks":{"o":{"a":0,"k":50},"p":{"a":0,"k":[0,71.389,0]},"a":{"a":0,"k":[0,71.389,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0.667,-19.333],[7.333,22.167],[0,0]],"o":[[0,0],[0.167,0.667],[-3.217,-9.726],[0,0]],"v":[[-13.222,-18.222],[-12.667,177.333],[-59.333,153.333],[-68.778,-37.222]],"c":true}]},{"t":30,"s":[{"i":[[0,0],[0.167,-11.222],[18.833,29.278],[1.111,16.111]],"o":[[-1.111,16.111],[-15.833,-1.222],[-7.532,-11.709],[0,0]],"v":[[8,37.056],[7.333,133.222],[-49.333,107.222],[-76,35.556]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725490196,0.38431372549,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"lines2","parent":10,"sr":1,"ks":{"p":{"a":0,"k":[-4.5,135.446,0]},"a":{"a":0,"k":[-4.5,135.446,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[19,23.25],[0,0],[-72.25,-0.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-45,-7]],"v":[[-67.5,-34.25],[-66,-10.75],[65.25,58.5],[66.5,12.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[19,23.25],[0,0],[-70.25,-2.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-45,-7]],"v":[[-67.5,-34.25],[-67,-26.25],[65.75,25],[68.5,-17]],"c":true}]},{"t":10,"s":[{"i":[[19,23.25],[0,0],[-70.25,-2.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-45,-7]],"v":[[-67.5,-34.25],[-67,-26.25],[68.25,-15],[68.5,-17]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[25,44.25],[0,0],[-71.75,9],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-65,33.75],[-62.5,74.75],[60.75,137],[62.5,98]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[25,44.25],[0,0],[-71.75,10.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-66.5,2.75],[-65,41.25],[61.75,99],[65,63]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[26,30.25],[0,0],[-71.75,10.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-68.5,-6.75],[-66,19.75],[62.75,70.5],[68,37]],"c":true}]},{"t":15,"s":[{"i":[[26,30.25],[0,0],[-54.75,16],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-68.5,-6.75],[-65.5,15.25],[65.75,49],[71,14]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[20,16.5],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[13.25,13.75],[-0.25,0],[-50.75,19]],"v":[[-56.25,160.5],[-56,160.5],[49.25,167.75],[49.75,168]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[20,16.5],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[13.25,13.75],[-0.25,0],[-50.75,19]],"v":[[-54.5,154.25],[-54.25,154.25],[43,161.5],[43.5,161.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[20,16.5],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[13.25,13.75],[-0.25,0],[-50.75,19]],"v":[[-53.75,148],[-53.5,148],[42.25,155.5],[42.75,155.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[17.5,26],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-52.5,136.25],[-52.75,136.25],[42.25,147.75],[42.5,147.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-57.25,102.5],[-55,120.25],[43.25,137.5],[44,133.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-59.25,89.5],[-54.25,106.75],[42.5,128],[48,118.75]],"c":true}]},{"t":30,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-58.5,71.75],[-53.5,89],[43.25,110.25],[48.75,101]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[19,27.5],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-58.5,158.5],[-58.25,158.5],[53.5,166],[54,165]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[19,27.5],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-57.25,147],[-57.25,147.5],[50.5,160],[50,161]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[24.25,45.5],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-60.5,106.5],[-58.75,138.5],[51,150],[51.25,149.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[24.25,45.5],[0,0],[-54.5,29.25],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-62.5,78],[-58.25,106.75],[49,141.75],[53.5,124.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-62.5,67.5],[-59.5,89.75],[50,121.5],[54.5,104.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[21.25,35.75],[0,0],[-49.75,27],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-62.5,63.25],[-60.75,81.25],[52.25,106.5],[57.25,81]],"c":true}]},{"t":30,"s":[{"i":[[21.25,35.75],[0,0],[-49.75,27],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-62.5,63.25],[-60.75,70.5],[56.5,84.75],[57.25,81]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[25,44.25],[0,0],[-47,36.25],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.5,40.25]],"v":[[-61.5,114.75],[-59,155.75],[63.25,158],[63.5,157.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[25,44.25],[0,0],[-55,45.75],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-62,77.5],[-60.75,112.25],[60.75,151.25],[59.5,132.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[25,44.25],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-64.25,50.75],[-60.75,80.5],[58.25,128],[60.75,101.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[25,39],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-65.5,33.75],[-63.75,59.5],[59,101.75],[61.5,76.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[25,39],[0,0],[-55.75,26.75],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-69.5,30],[-65,49],[61,82.75],[63.75,62.25]],"c":true}]},{"t":25,"s":[{"i":[[25,39],[0,0],[-55.75,26.75],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-69.5,30],[-65,54.75],[62.25,76.25],[63.75,62.25]],"c":true}]}]},"nm":"Path 4","hd":false},{"ty":"fl","c":{"a":0,"k":[0.109803921569,0.576470588235,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"rocket body","parent":4,"sr":1,"ks":{"p":{"a":0,"k":[0,71.389,0]},"a":{"a":0,"k":[0,71.389,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[2.222,-10],[7.734,34.804],[0,0]],"o":[[0,0],[-7.676,34.543],[-2.222,-10],[0,0]],"v":[[68.778,-37.222],[59.333,153.333],[-59.333,153.333],[-68.778,-37.222]],"c":true}]},{"t":30,"s":[{"i":[[0,0],[6.667,-12.222],[20.133,36.91],[1.111,16.111]],"o":[[-1.111,16.111],[-20.133,36.911],[-6.667,-12.222],[0,0]],"v":[[76,35.556],[49.333,107.222],[-49.333,107.222],[-76,35.556]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.898039215686,0.898039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[283,390,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[257,472,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11,"s":[283,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":17,"s":[262,427.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[280,323,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[279,288,0],"to":[0,0,0],"ti":[0,0,0]},{"t":31,"s":[259,328,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":4,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":10,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":11,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":17,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":18,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":24,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":25,"s":[100,100,100]},{"t":31,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":4,"op":31,"st":4,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[255,394,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[257,472,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":13,"s":[258,356,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":19,"s":[262,427.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[255,327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[261,292,0],"to":[0,0,0],"ti":[0,0,0]},{"t":33,"s":[259,328,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":12,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":13,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":19,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":26,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":27,"s":[100,100,100]},{"t":33,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":6,"op":33,"st":6,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[231,397,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14,"s":[252,494,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[232,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[254,428,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[230,336,0],"to":[0,0,0],"ti":[0,0,0]},{"t":28,"s":[256.5,371.5,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":14,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":15,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":21,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":22,"s":[100,100,100]},{"t":28,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":8,"op":28,"st":8,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[283,390,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[257,472,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":9,"s":[283,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[262,427.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[280,323,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[279,288,0],"to":[0,0,0],"ti":[0,0,0]},{"t":29,"s":[259,328,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":2,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":8,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":9,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":15,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":16,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":22,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":23,"s":[100,100,100]},{"t":29,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":2,"op":29,"st":2,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[231,397,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[252,494,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[232,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":13,"s":[254,428,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14,"s":[230,336,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[234,298,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[259,328,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[239,262,0],"to":[0,0,0],"ti":[0,0,0]},{"t":33,"s":[264,326,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":7,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":13,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":14,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":21,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":27,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":28,"s":[100,100,100]},{"t":33,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":34,"st":0,"bm":0}]},{"id":"comp_6","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"pink","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[100]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[100]},{"t":100,"s":[0]}]},"p":{"a":0,"k":[245.75,225.75,0]},"a":{"a":0,"k":[2.75,-17.25,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.629,0.629,0.629],"y":[0,0,0]},"t":30,"s":[43.235,43.235,100]},{"t":40,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.312,8.525],[3.244,0.766],[0.278,-4.348]],"o":[[-1.312,-8.525],[-5.358,-1.265],[-0.278,4.348]],"v":[[6.13,-24.571],[8.951,-66.768],[5.014,-23.638]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.55,23.061],[3.244,0.766],[0.753,-11.762]],"o":[[-3.55,-23.061],[-5.358,-1.265],[-0.753,11.762]],"v":[[11.788,-37.733],[14.47,-92.154],[8.769,-35.211]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.113,0.736],[3.244,0.766],[0.024,-0.376]],"o":[[-0.113,-0.736],[-5.358,-1.265],[-0.024,0.376]],"v":[[1.251,-14.165],[19.643,-137.56],[1.155,-14.085]],"c":true}]},{"t":100,"s":[{"i":[[0.002,0.014],[3.244,0.766],[0,-0.007]],"o":[[-0.002,-0.014],[-5.358,-1.265],[0,0.007]],"v":[[29.568,-215.114],[32.47,-250.154],[29.566,-215.113]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.775,14.209],[6.351,4.603],[2.652,-16.185]],"o":[[0.775,-14.209],[-4.056,-2.94],[-2.652,16.185]],"v":[[7.231,-23.969],[18.873,-61.186],[6.13,-24.571]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-2.096,38.439],[6.351,4.603],[7.173,-43.783]],"o":[[2.096,-38.439],[-4.056,-2.94],[-7.173,43.783]],"v":[[14.767,-36.105],[37.637,-108.647],[11.788,-37.733]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.067,1.227],[6.351,4.603],[0.229,-1.398]],"o":[[0.067,-1.227],[-4.056,-2.94],[-0.229,1.398]],"v":[[1.932,-14.242],[49.85,-146.868],[1.837,-14.294]],"c":true}]},{"t":100,"s":[{"i":[[-0.001,0.023],[6.351,4.603],[0.004,-0.026]],"o":[[0.001,-0.023],[-4.056,-2.94],[-0.004,0.026]],"v":[[75.069,-225.113],[80.137,-241.647],[75.068,-225.114]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.749,6.829],[6.31,5.609],[1.359,-6.48]],"o":[[-0.749,-6.829],[-3.554,-3.159],[-1.359,6.48]],"v":[[8.596,-23.217],[31.24,-73.607],[7.231,-23.969]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[2.026,18.473],[6.31,5.609],[3.677,-17.531]],"o":[[-2.026,-18.473],[-3.554,-3.159],[-3.677,17.531]],"v":[[18.46,-34.07],[47.481,-104.355],[14.767,-36.105]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.065,0.59],[6.31,5.609],[0.117,-0.56]],"o":[[-0.065,-0.59],[-3.554,-3.159],[-0.117,0.56]],"v":[[2.166,-13.836],[63.43,-142.576],[2.048,-13.901]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0.011],[6.31,5.609],[0.002,-0.011]],"o":[[-0.001,-0.011],[-3.554,-3.159],[-0.002,0.011]],"v":[[84.072,-198.612],[102.981,-237.355],[84.069,-198.613]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.199,15.089],[5.581,6.595],[12.758,-17.563]],"o":[[6.199,-15.089],[-2.72,-3.214],[-12.758,17.563]],"v":[[9.486,-22.203],[48.882,-61.987],[8.281,-23.532]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-16.769,40.819],[5.581,6.595],[34.513,-47.512]],"o":[[16.769,-40.819],[-2.72,-3.214],[-34.513,47.512]],"v":[[21.719,-30.475],[103.282,-109.764],[18.46,-34.07]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.535,1.303],[5.581,6.595],[1.102,-1.517]],"o":[[0.535,-1.303],[-2.72,-3.214],[-1.102,1.517]],"v":[[3.204,-13.341],[122.823,-131.604],[3.099,-13.456]],"c":true}]},{"t":100,"s":[{"i":[[-0.01,0.025],[5.581,6.595],[0.021,-0.029]],"o":[[0.01,-0.025],[-2.72,-3.214],[-0.021,0.029]],"v":[[156.574,-169.11],[171.282,-185.764],[156.572,-169.112]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.223,9.945],[2.258,6.104],[6.726,-10.205]],"o":[[4.223,-9.945],[-1.817,-4.912],[-6.726,10.205]],"v":[[11.177,-20.424],[48.778,-58.677],[9.801,-21.888]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-11.423,26.904],[2.258,6.104],[18.195,-27.606]],"o":[[11.423,-26.904],[-1.817,-4.912],[-18.195,27.606]],"v":[[25.439,-26.516],[74.48,-79.649],[21.719,-30.475]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.365,0.859],[2.258,6.104],[0.581,-0.882]],"o":[[0.365,-0.859],[-1.817,-4.912],[-0.581,0.882]],"v":[[3.264,-12.932],[105.66,-107.524],[3.146,-13.058]],"c":true}]},{"t":100,"s":[{"i":[[-0.007,0.016],[2.258,6.104],[0.011,-0.017]],"o":[[0.007,-0.016],[-1.817,-4.912],[-0.011,0.017]],"v":[[152.076,-147.108],[182.98,-176.649],[152.074,-147.11]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-3.334,6.648],[-1.799,5.518],[9.673,-9.931]],"o":[[3.334,-6.648],[1.878,-5.758],[-9.673,9.931]],"v":[[12.897,-19.193],[48.972,-44.204],[11.177,-20.424]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-9.018,17.983],[-1.799,5.518],[26.167,-26.865]],"o":[[9.018,-17.983],[1.878,-5.758],[-26.167,26.865]],"v":[[30.094,-23.186],[81.927,-62.022],[25.439,-26.516]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.288,0.574],[-1.799,5.518],[0.836,-0.858]],"o":[[0.288,-0.574],[1.878,-5.758],[-0.836,0.858]],"v":[[3.883,-12.491],[120.292,-83.862],[3.734,-12.597]],"c":true}]},{"t":100,"s":[{"i":[[-0.005,0.011],[-1.799,5.518],[0.016,-0.016]],"o":[[0.005,-0.011],[1.878,-5.758],[-0.016,0.016]],"v":[[188.579,-121.106],[215.427,-138.022],[188.576,-121.108]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-9.869,8.704],[-3.432,7.058],[12.009,-7.852]],"o":[[9.869,-8.704],[2.981,-6.13],[-12.009,7.852]],"v":[[12.832,-16.628],[74.374,-42.527],[12.897,-19.193]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-26.696,23.547],[-3.432,7.058],[32.485,-21.24]],"o":[[26.696,-23.547],[2.981,-6.13],[-32.485,21.24]],"v":[[29.916,-16.247],[117.42,-56.876],[30.094,-23.186]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.852,0.752],[-3.432,7.058],[1.037,-0.678]],"o":[[0.852,-0.752],[2.981,-6.13],[-1.037,0.678]],"v":[[3.897,-11.786],[151.618,-69.521],[3.902,-12.008]],"c":true}]},{"t":100,"s":[{"i":[[-0.016,0.014],[-3.432,7.058],[0.02,-0.013]],"o":[[0.016,-0.014],[2.981,-6.13],[-0.02,0.013]],"v":[[190.079,-83.601],[236.42,-100.876],[190.079,-83.606]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-14.199,3.553],[-2.249,4.962],[22.38,-4.873]],"o":[[22.287,-5.577],[4.049,-8.935],[-22.38,4.873]],"v":[[10.861,-15.141],[88.919,-15.854],[12.516,-16.944]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-38.412,9.611],[-2.249,4.962],[60.542,-13.183]],"o":[[60.291,-15.085],[4.049,-8.935],[-60.542,13.183]],"v":[[25.439,-11.372],[154.986,-15.381],[29.916,-16.247]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-1.227,0.307],[-2.249,4.962],[1.933,-0.421]],"o":[[1.925,-0.482],[4.049,-8.935],[-1.933,0.421]],"v":[[3.934,-10.704],[182.862,-13.944],[4.077,-10.859]],"c":true}]},{"t":100,"s":[{"i":[[-0.023,0.006],[-2.249,4.962],[0.036,-0.008]],"o":[[0.036,-0.009],[4.049,-8.935],[-0.036,0.008]],"v":[[204.076,-11.599],[251.986,-10.381],[204.079,-11.601]],"c":true}]}]},"nm":"Path 8","hd":false},{"ind":8,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.711,2.16],[-0.839,0.958],[12.251,-0.88]],"o":[[4.711,-2.16],[1.348,-1.539],[-12.251,0.88]],"v":[[9.079,-12.869],[39.383,-12.388],[10.861,-14.826]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-12.744,5.843],[-0.839,0.958],[33.14,-2.38]],"o":[[12.744,-5.843],[1.348,-1.539],[-33.14,2.38]],"v":[[20.619,-6.076],[78.015,-8.131],[25.439,-11.372]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.407,0.187],[-0.839,0.958],[1.058,-0.076]],"o":[[0.407,-0.187],[1.348,-1.539],[-1.058,0.076]],"v":[[4.224,-10.251],[128.305,-2.239],[4.378,-10.421]],"c":true}]},{"t":100,"s":[{"i":[[-0.008,0.004],[-0.839,0.958],[0.02,-0.001]],"o":[[0.008,-0.004],[1.348,-1.539],[-0.02,0.001]],"v":[[238.573,10.405],[253.015,12.369],[238.576,10.401]],"c":true}]}]},"nm":"Path 9","hd":false},{"ind":9,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-19.702,-2.401],[-2.482,3.154],[14.29,0.398]],"o":[[19.702,2.401],[2.595,-3.297],[-14.29,-0.398]],"v":[[9.698,-12.307],[64.597,6.088],[9.395,-13.184]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-53.298,-6.494],[-2.482,3.154],[38.657,1.077]],"o":[[53.298,6.494],[2.595,-3.297],[-38.657,-1.077]],"v":[[21.439,-3.705],[93.137,16.968],[20.619,-6.076]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-1.702,-0.207],[-2.482,3.154],[1.234,0.034]],"o":[[1.702,0.207],[2.595,-3.297],[-1.234,-0.034]],"v":[[3.71,-9.493],[135.812,35.216],[3.683,-9.569]],"c":true}]},{"t":100,"s":[{"i":[[-0.032,-0.004],[-2.482,3.154],[0.023,0.001]],"o":[[0.032,0.004],[2.595,-3.297],[-0.023,-0.001]],"v":[[196.573,63.406],[241.637,80.468],[196.573,63.405]],"c":true}]}]},"nm":"Path 10","hd":false},{"ind":10,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-9.253,-3.386],[-5.064,4.293],[16.792,8.51]],"o":[[9.253,3.386],[4.126,-3.498],[-16.792,-8.51]],"v":[[11.313,-9.614],[54.159,17.809],[9.698,-11.992]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-25.032,-9.16],[-5.064,4.293],[45.426,23.022]],"o":[[25.032,9.16],[4.126,-3.498],[-45.426,-23.022]],"v":[[25.808,2.729],[103.197,50.764],[21.439,-3.705]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.799,-0.292],[-5.064,4.293],[1.451,0.735]],"o":[[0.799,0.292],[4.126,-3.498],[-1.451,-0.735]],"v":[[3.881,-8.702],[137.251,71.886],[3.742,-8.908]],"c":true}]},{"t":100,"s":[{"i":[[-0.015,-0.006],[-5.064,4.293],[0.027,0.014]],"o":[[0.015,0.006],[4.126,-3.498],[-0.027,-0.014]],"v":[[199.076,108.91],[221.697,124.264],[199.073,108.906]],"c":true}]}]},"nm":"Path 11","hd":false},{"ind":11,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.526,-3.215],[-3.502,3.162],[9.671,6.481]],"o":[[6.526,3.215],[3.502,-3.162],[-9.671,-6.481]],"v":[[10.491,-8.994],[73.057,40.569],[11.313,-9.929]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-17.653,-8.697],[-3.502,3.162],[26.163,17.532]],"o":[[17.653,8.697],[3.502,-3.162],[-26.163,-17.532]],"v":[[23.585,5.258],[96.236,63.117],[25.808,2.729]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.564,-0.278],[-3.502,3.162],[0.835,0.56]],"o":[[0.564,0.278],[3.502,-3.162],[-0.835,-0.56]],"v":[[3.102,-8.57],[126.267,90.274],[3.173,-8.651]],"c":true}]},{"t":100,"s":[{"i":[[-0.011,-0.005],[-3.502,3.162],[0.016,0.011]],"o":[[0.011,0.005],[3.502,-3.162],[-0.016,-0.011]],"v":[[144.075,112.911],[200.736,157.617],[144.076,112.91]],"c":true}]}]},"nm":"Path 12","hd":false},{"ind":12,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-13.475,-10.482],[-4.458,3.463],[10.073,7.849]],"o":[[13.475,10.482],[4.458,-3.463],[-10.073,-7.849]],"v":[[9.502,-7.951],[52.87,45.892],[10.491,-8.678]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-36.453,-28.355],[-4.458,3.463],[27.249,21.233]],"o":[[36.453,28.355],[4.458,-3.463],[-27.249,-21.233]],"v":[[20.909,7.226],[94.655,93.669],[23.585,5.258]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-1.164,-0.905],[-4.458,3.463],[0.87,0.678]],"o":[[1.164,0.905],[4.458,-3.463],[-0.87,-0.678]],"v":[[3.049,-7.986],[116.926,120.825],[3.134,-8.049]],"c":true}]},{"t":100,"s":[{"i":[[-0.022,-0.017],[-4.458,3.463],[0.016,0.013]],"o":[[0.022,0.017],[4.458,-3.463],[-0.016,-0.013]],"v":[[146.573,153.413],[172.155,188.169],[146.575,153.411]],"c":true}]}]},"nm":"Path 13","hd":false},{"ind":13,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.775,-3.087],[-3.488,2.449],[10.229,14.193]],"o":[[4.775,3.087],[3.488,-2.449],[-10.229,-14.193]],"v":[[7.167,-8.135],[48.091,56.332],[9.187,-7.951]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-12.917,-8.352],[-3.488,2.449],[27.671,38.395]],"o":[[12.917,8.352],[3.488,-2.449],[-27.671,-38.395]],"v":[[15.445,6.729],[62.124,74.78],[20.909,7.226]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.412,-0.267],[-3.488,2.449],[0.884,1.226]],"o":[[0.412,0.267],[3.488,-2.449],[-0.884,-1.226]],"v":[[2.321,-8.079],[84.827,113.576],[2.495,-8.063]],"c":true}]},{"t":100,"s":[{"i":[[-0.008,-0.005],[-3.488,2.449],[0.017,0.023]],"o":[[0.008,0.005],[3.488,-2.449],[-0.017,-0.023]],"v":[[103.57,147.412],[141.124,209.78],[103.573,147.413]],"c":true}]}]},"nm":"Path 14","hd":false},{"ind":14,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-5.362,-6.334],[-2.659,0.857],[3.694,6.735]],"o":[[5.361,6.334],[6.495,-2.093],[-3.694,-6.735]],"v":[[5.102,-9.295],[26.477,35.494],[7.167,-8.45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-14.504,-17.136],[-2.659,0.857],[9.994,18.219]],"o":[[14.504,17.136],[6.495,-2.093],[-9.994,-18.219]],"v":[[9.859,4.445],[41.772,64.349],[15.445,6.729]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.463,-0.547],[-2.659,0.857],[0.319,0.582]],"o":[[0.463,0.547],[6.495,-2.093],[-0.319,-0.582]],"v":[[2.027,-7.553],[61.313,111.91],[2.205,-7.48]],"c":true}]},{"t":100,"s":[{"i":[[-0.009,-0.01],[-2.659,0.857],[0.006,0.011]],"o":[[0.009,0.01],[6.495,-2.093],[-0.006,-0.011]],"v":[[94.566,193.911],[109.772,229.849],[94.57,193.912]],"c":true}]}]},"nm":"Path 15","hd":false},{"ind":15,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.43,-4.038],[-5.792,0.94],[0.557,8.126]],"o":[[0.43,4.038],[3.71,-0.602],[-0.557,-8.126]],"v":[[3.69,-10.876],[17.077,55.965],[5.102,-9.295]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-1.164,-10.924],[-5.792,0.94],[1.507,21.981]],"o":[[1.164,10.924],[3.71,-0.602],[-1.507,-21.981]],"v":[[6.039,0.167],[22.28,71.891],[9.859,4.445]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[-0.037,-0.349],[-5.792,0.94],[0.048,0.702]],"o":[[0.037,0.349],[3.71,-0.602],[-0.048,-0.702]],"v":[[1.242,-7.799],[31.62,122.9],[1.364,-7.663]],"c":true}]},{"t":100,"s":[{"i":[[-0.001,-0.007],[-5.792,0.94],[0.001,0.013]],"o":[[0.001,0.007],[3.71,-0.602],[-0.001,-0.013]],"v":[[43.064,185.408],[54.78,249.391],[43.066,185.411]],"c":true}]}]},"nm":"Path 16","hd":false},{"ind":16,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.268,-1.072],[-8.766,2.77],[-1.453,20.282]],"o":[[-0.268,1.072],[8.956,-2.83],[1.453,-20.282]],"v":[[2.258,-8.649],[11.352,48.031],[4.005,-10.876]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[0.726,-2.901],[-8.766,2.77],[-3.93,54.866]],"o":[[-0.726,2.901],[8.956,-2.83],[3.93,-54.866]],"v":[[1.312,6.192],[20.655,115.675],[6.039,0.167]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.023,-0.093],[-8.766,2.77],[-0.125,1.752]],"o":[[-0.023,0.093],[8.956,-2.83],[0.125,-1.752]],"v":[[0.956,-7.06],[24.966,155.333],[1.106,-7.252]],"c":true}]},{"t":100,"s":[{"i":[[0,-0.002],[-8.766,2.77],[-0.002,0.033]],"o":[[0,0.002],[8.956,-2.83],[0.002,-0.033]],"v":[[32.561,227.912],[35.655,253.675],[32.564,227.908]],"c":true}]}]},"nm":"Path 17","hd":false},{"ind":17,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.674,-5.118],[-9.34,-10.304],[-5.656,20.469]],"o":[[-0.674,5.118],[5.335,5.886],[5.656,-20.469]],"v":[[0.319,-7.862],[-7.754,79.004],[2.258,-8.649]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[1.822,-13.845],[-9.34,-10.304],[-15.299,55.372]],"o":[[-1.822,13.845],[5.335,5.886],[15.299,-55.372]],"v":[[-3.933,8.322],[-10.119,142.549],[1.312,6.192]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.058,-0.442],[-9.34,-10.304],[-0.489,1.768]],"o":[[-0.058,0.442],[5.335,5.886],[0.489,-1.768]],"v":[[0.157,-7.436],[-14.573,174.16],[0.325,-7.504]],"c":true}]},{"t":100,"s":[{"i":[[0.001,-0.008],[-9.34,-10.304],[-0.009,0.033]],"o":[[-0.001,0.008],[5.335,5.886],[0.009,-0.033]],"v":[[-16.442,193.413],[-25.619,252.549],[-16.439,193.412]],"c":true}]}]},"nm":"Path 18","hd":false},{"ind":18,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.134,-4.491],[-8.529,-8.273],[-6.696,18.034]],"o":[[-1.134,4.491],[3.166,3.071],[6.696,-18.034]],"v":[[-1.761,-9.64],[-13.598,51.106],[0.003,-7.546]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.068,-12.148],[-8.529,-8.273],[-18.113,48.786]],"o":[[-3.068,12.148],[3.166,3.071],[18.113,-48.786]],"v":[[-8.707,2.658],[-24.162,110.866],[-3.933,8.322]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.098,-0.388],[-8.529,-8.273],[-0.578,1.558]],"o":[[-0.098,0.388],[3.166,3.071],[0.578,-1.558]],"v":[[-0.446,-7.378],[-34.795,149.374],[-0.293,-7.198]],"c":true}]},{"t":100,"s":[{"i":[[0.002,-0.007],[-8.529,-8.273],[-0.011,0.029]],"o":[[-0.002,0.007],[3.166,3.071],[0.011,-0.029]],"v":[[-51.445,211.91],[-61.162,244.866],[-51.442,211.913]],"c":true}]}]},"nm":"Path 19","hd":false},{"ind":19,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[9.217,-13.161],[-2.977,-4.918],[-5.725,7.174]],"o":[[-9.217,13.161],[1.721,2.843],[5.725,-7.174]],"v":[[-1.154,-12.499],[-42.53,56.34],[-1.761,-9.64]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[24.934,-35.602],[-2.977,-4.918],[-15.488,19.406]],"o":[[-24.934,35.602],[1.721,2.843],[15.488,-19.406]],"v":[[-7.064,-5.076],[-47.418,71.95],[-8.707,2.658]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.796,-1.137],[-2.977,-4.918],[-0.495,0.62]],"o":[[-0.796,1.137],[1.721,2.843],[0.495,-0.62]],"v":[[-0.876,-8.404],[-71.414,113.188],[-0.929,-8.157]],"c":true}]},{"t":100,"s":[{"i":[[0.015,-0.021],[-2.977,-4.918],[-0.009,0.012]],"o":[[-0.015,0.021],[1.721,2.843],[0.009,-0.012]],"v":[[-88.944,151.405],[-130.918,215.45],[-88.945,151.41]],"c":true}]}]},"nm":"Path 20","hd":false},{"ind":20,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[7.626,-7.899],[-8.084,-5.313],[-13.235,7.936]],"o":[[-2.855,2.957],[3.306,2.173],[10.979,-6.583]],"v":[[-4.169,-12.723],[-45.434,37.243],[-0.839,-12.499]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[20.63,-21.369],[-8.084,-5.313],[-35.802,21.467]],"o":[[-7.724,8],[3.306,2.173],[29.699,-17.808]],"v":[[-16.072,-5.684],[-88.796,85.335],[-7.064,-5.076]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.659,-0.682],[-8.084,-5.313],[-1.143,0.686]],"o":[[-0.247,0.255],[3.306,2.173],[0.948,-0.569]],"v":[[-1.962,-8.308],[-113.223,114.36],[-1.674,-8.288]],"c":true}]},{"t":100,"s":[{"i":[[0.012,-0.013],[-8.084,-5.313],[-0.022,0.013]],"o":[[-0.005,0.005],[3.306,2.173],[0.018,-0.011]],"v":[[-150.949,160.405],[-173.796,186.335],[-150.944,160.405]],"c":true}]}]},"nm":"Path 21","hd":false},{"ind":21,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[14.117,-10.21],[-5.628,-14.818],[-24.822,15.796]],"o":[[-14.086,10.188],[3.081,4.733],[21.924,-13.951]],"v":[[-3.765,-15.075],[-82.448,63.651],[-4.484,-12.723]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[38.189,-27.621],[0.797,-25.979],[-67.149,42.73]],"o":[[-38.106,27.561],[-0.163,5.311],[59.307,-37.74]],"v":[[-14.127,-12.046],[-117.611,102.283],[-16.072,-5.684]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[1.219,-0.882],[-2.13,-20.894],[-2.144,1.364]],"o":[[-1.217,0.88],[1.315,5.048],[1.894,-1.205]],"v":[[-1.552,-9.148],[-137.871,121.393],[-1.614,-8.945]],"c":true}]},{"t":100,"s":[{"i":[[0.023,-0.017],[-9.389,-8.283],[-0.04,0.026]],"o":[[-0.023,0.017],[4.981,4.394],[0.036,-0.023]],"v":[[-123.948,110.901],[-188.111,168.783],[-123.949,110.905]],"c":true}]}]},"nm":"Path 22","hd":false},{"ind":22,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.794,-1.349],[0.656,-8.742],[-9.144,3.299]],"o":[[-0.388,0.187],[-0.279,3.717],[9.144,-3.298]],"v":[[-4.357,-15.318],[-39.206,7.131],[-3.283,-15.062]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[7.56,-3.648],[1.507,-8.661],[-24.737,8.919]],"o":[[-1.051,0.507],[-0.641,3.682],[24.737,-8.919]],"v":[[-17.032,-12.737],[-65.957,17.111],[-14.127,-12.046]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.241,-0.117],[1.12,-8.698],[-0.79,0.285]],"o":[[-0.034,0.016],[-0.476,3.698],[0.79,-0.285]],"v":[[-2.581,-9.26],[-111.172,47.264],[-2.489,-9.238]],"c":true}]},{"t":100,"s":[{"i":[[0.004,-0.003],[0.158,-8.79],[-0.014,0.008]],"o":[[-0.001,0],[-0.067,3.737],[0.014,-0.008]],"v":[[-196.685,103.922],[-223.294,122.037],[-196.683,103.922]],"c":true}]}]},"nm":"Path 23","hd":false},{"ind":23,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.652,-0.002],[9.621,-23.195],[-30.797,11.034]],"o":[[-0.652,0.002],[1.201,4.343],[30.797,-11.034]],"v":[[-5.938,-16.534],[-91.485,36.162],[-4.524,-15.646]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[1.764,-0.005],[34.94,-49.93],[-83.311,29.848]],"o":[[-1.764,0.005],[-2.448,3.499],[83.311,-29.848]],"v":[[-20.857,-15.139],[-161.337,74.163],[-17.032,-12.737]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.056,0],[23.404,-37.749],[-2.66,0.953]],"o":[[-0.056,0],[-0.786,3.883],[2.66,-0.953]],"v":[[-2.437,-9.678],[-180.879,84.077],[-2.314,-9.602]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0],[-5.203,-7.542],[-0.05,0.018]],"o":[[-0.001,0],[3.337,4.837],[0.05,-0.018]],"v":[[-175.952,77.399],[-229.337,108.663],[-175.95,77.401]],"c":true}]}]},"nm":"Path 24","hd":false},{"ind":24,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[3.079,-0.51],[0.18,-8.491],[-14.645,0.239]],"o":[[-3.079,0.51],[-0.099,4.7],[14.645,-0.239]],"v":[[-8.996,-17.288],[-60.714,-4.682],[-6.253,-16.534]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[8.33,-1.38],[0.18,-8.491],[-39.617,0.647]],"o":[[-8.33,1.38],[-0.099,4.7],[39.617,-0.647]],"v":[[-28.279,-17.178],[-119.213,6.829],[-20.857,-15.139]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.266,-0.044],[0.18,-8.491],[-1.265,0.021]],"o":[[-0.266,0.044],[-0.099,4.7],[1.265,-0.021]],"v":[[-3.288,-10.423],[-157.506,13.654],[-3.051,-10.357]],"c":true}]},{"t":100,"s":[{"i":[[0.005,-0.001],[0.18,-8.491],[-0.024,0]],"o":[[-0.005,0.001],[-0.099,4.7],[0.024,0]],"v":[[-223.707,24.648],[-252.463,30.579],[-223.702,24.649]],"c":true}]}]},"nm":"Path 25","hd":false},{"ind":25,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[5.002,-0.018],[4.376,-11.742],[-21.034,-0.505]],"o":[[-5.002,0.018],[-1,2.682],[21.034,0.505]],"v":[[-8.482,-18.09],[-80.041,-5.651],[-8.996,-16.972]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[13.533,-0.05],[4.376,-11.742],[-56.9,-1.366]],"o":[[-13.533,0.05],[-1,2.682],[56.9,1.366]],"v":[[-26.888,-20.202],[-133.179,-1.393],[-28.279,-17.178]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.432,-0.002],[4.376,-11.742],[-1.817,-0.044]],"o":[[-0.432,0.002],[-1,2.682],[1.817,0.044]],"v":[[-2.957,-10.802],[-167.52,2.271],[-3.002,-10.706]],"c":true}]},{"t":100,"s":[{"i":[[0.008,0],[4.376,-11.742],[-0.034,-0.001]],"o":[[-0.008,0],[-1,2.682],[0.034,0.001]],"v":[[-201.456,2.646],[-252.679,11.357],[-201.457,2.648]],"c":true}]}]},"nm":"Path 26","hd":false},{"ind":26,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.015,0.648],[9.285,-5.938],[-12.687,-1.577]],"o":[[-1.837,-0.296],[-5.457,3.49],[12.687,1.577]],"v":[[-7.193,-19.109],[-55.539,-18.566],[-8.167,-18.09]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[10.862,1.752],[9.285,-5.938],[-34.32,-4.266]],"o":[[-4.969,-0.802],[-5.457,3.49],[34.32,4.266]],"v":[[-24.253,-22.959],[-119.714,-21.877],[-26.888,-20.202]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.347,0.056],[9.285,-5.938],[-1.096,-0.136]],"o":[[-0.159,-0.026],[-5.457,3.49],[1.096,0.136]],"v":[[-3.256,-11.254],[-157.719,-22.883],[-3.34,-11.166]],"c":true}]},{"t":100,"s":[{"i":[[0.007,0.001],[9.285,-5.938],[-0.021,-0.003]],"o":[[-0.003,0],[-5.457,3.49],[0.021,0.003]],"v":[[-231.204,-25.606],[-251.964,-25.377],[-231.206,-25.604]],"c":true}]}]},"nm":"Path 27","hd":false},{"ind":27,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.393,1.499],[5.601,-5.319],[-6.035,-3.074]],"o":[[-4.393,-1.499],[-2.043,1.94],[7.22,3.678]],"v":[[-5.608,-19.397],[-48.68,-27.903],[-7.508,-19.109]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[11.885,4.055],[5.601,-5.319],[-16.326,-8.316]],"o":[[-11.885,-4.055],[-2.043,1.94],[19.531,9.948]],"v":[[-19.113,-23.736],[-77.851,-34.052],[-24.253,-22.959]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.38,0.13],[5.601,-5.319],[-0.521,-0.266]],"o":[[-0.38,-0.129],[-2.043,1.94],[0.624,0.318]],"v":[[-2.828,-11.723],[-125.77,-43.679],[-2.992,-11.698]],"c":true}]},{"t":100,"s":[{"i":[[0.007,0.002],[5.601,-5.319],[-0.01,-0.005]],"o":[[-0.007,-0.002],[-2.043,1.94],[0.012,0.006]],"v":[[-210.701,-60.106],[-244.601,-67.552],[-210.704,-60.106]],"c":true}]}]},"nm":"Path 28","hd":false},{"ind":28,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[5.221,2.81],[2.362,-11.3],[-20.382,-17.294]],"o":[[-5.221,-2.81],[-1.358,6.576],[20.382,17.294]],"v":[[-7.193,-23.937],[-71.1,-48.972],[-5.293,-19.397]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[14.123,7.602],[4.156,-15.533],[-55.138,-46.784]],"o":[[-14.123,-7.602],[-2.317,8.659],[55.138,46.784]],"v":[[-24.253,-36.018],[-168.703,-94.857],[-19.113,-23.736]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.451,0.243],[3.339,-13.604],[-1.761,-1.494]],"o":[[-0.451,-0.243],[-1.88,7.71],[1.761,1.494]],"v":[[-2.925,-12.804],[-184.078,-101.754],[-2.761,-12.412]],"c":true}]},{"t":100,"s":[{"i":[[0.008,0.005],[1.312,-8.822],[-0.033,-0.028]],"o":[[-0.008,-0.005],[-0.797,5.357],[0.033,0.028]],"v":[[-205.454,-113.613],[-222.203,-118.857],[-205.451,-113.606]],"c":true}]}]},"nm":"Path 29","hd":false},{"ind":29,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[7.26,4.445],[7.011,-4.124],[-10.385,-10.766]],"o":[[-0.326,-0.199],[-7.011,4.124],[16.897,17.517]],"v":[[-5.801,-24.959],[-56.419,-56.973],[-7.193,-23.937]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[19.64,12.023],[7.011,-4.124],[-28.094,-29.123]],"o":[[-0.881,-0.54],[-7.011,4.124],[45.711,47.385]],"v":[[-20.488,-38.784],[-98.519,-86.459],[-24.253,-36.018]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.627,0.384],[7.011,-4.124],[-0.897,-0.93]],"o":[[-0.028,-0.017],[-7.011,4.124],[1.46,1.513]],"v":[[-2.277,-13.13],[-127.401,-106.145],[-2.397,-13.042]],"c":true}]},{"t":100,"s":[{"i":[[0.012,0.007],[7.011,-4.124],[-0.017,-0.018]],"o":[[-0.001,0],[-7.011,4.124],[0.028,0.029]],"v":[[-164.452,-132.115],[-199.019,-154.959],[-164.454,-132.113]],"c":true}]}]},"nm":"Path 30","hd":false},{"ind":30,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[23.866,20.95],[-0.775,-4.182],[-8.843,-10.116]],"o":[[-23.866,-20.95],[0.799,4.308],[8.843,10.116]],"v":[[-4.127,-25.389],[-47.359,-57.266],[-6.116,-25.275]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[64.562,56.675],[-0.775,-4.182],[-23.921,-27.366]],"o":[[-64.562,-56.675],[0.799,4.308],[23.921,27.366]],"v":[[-15.108,-39.093],[-105.227,-105.674],[-20.488,-38.784]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[2.062,1.81],[-0.775,-4.182],[-0.764,-0.874]],"o":[[-2.062,-1.81],[0.799,4.308],[0.764,0.874]],"v":[[-2.169,-13.514],[-128.217,-124.928],[-2.341,-13.504]],"c":true}]},{"t":100,"s":[{"i":[[0.039,0.034],[-0.775,-4.182],[-0.014,-0.016]],"o":[[-0.039,-0.034],[0.799,4.308],[0.014,0.016]],"v":[[-169.449,-161.115],[-185.227,-172.674],[-169.452,-161.115]],"c":true}]}]},"nm":"Path 31","hd":false},{"ind":31,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.861,0.835],[6.082,-5.676],[-10.296,-14.684]],"o":[[-0.861,-0.835],[-2.893,2.699],[10.296,14.684]],"v":[[-0.366,-23.104],[-55.133,-75.787],[-4.127,-25.389]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[2.329,2.258],[6.082,-5.676],[-27.852,-39.724]],"o":[[-2.329,-2.258],[-2.893,2.699],[27.852,39.724]],"v":[[-4.933,-32.911],[-79.889,-105.589],[-15.108,-39.093]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.074,0.072],[6.082,-5.676],[-0.889,-1.268]],"o":[[-0.074,-0.072],[-2.893,2.699],[0.889,1.268]],"v":[[-1.214,-13.207],[-103.885,-130.878],[-1.539,-13.404]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0.001],[6.082,-5.676],[-0.017,-0.024]],"o":[[-0.001,-0.001],[-2.893,2.699],[0.017,0.024]],"v":[[-120.443,-152.612],[-163.389,-193.589],[-120.449,-152.615]],"c":true}]}]},"nm":"Path 32","hd":false},{"ind":32,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[3.542,19.181],[3.073,-0.981],[-0.152,-9.722]],"o":[[-3.542,-19.181],[-2.453,0.783],[0.152,9.722]],"v":[[0.539,-22.899],[-14.028,-61.928],[-0.366,-22.788]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[9.581,51.887],[3.073,-0.981],[-0.412,-26.3]],"o":[[-9.581,-51.887],[-2.453,0.783],[0.412,26.3]],"v":[[-2.483,-33.211],[-31.215,-116.327],[-4.933,-32.911]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.306,1.657],[3.073,-0.981],[-0.013,-0.84]],"o":[[-0.306,-1.657],[-2.453,0.783],[0.013,0.84]],"v":[[-0.517,-14.182],[-44.865,-152.105],[-0.596,-14.172]],"c":true}]},{"t":100,"s":[{"i":[[0.006,0.031],[3.073,-0.981],[0,-0.016]],"o":[[-0.006,-0.031],[-2.453,0.783],[0,0.016]],"v":[[-72.441,-227.612],[-78.715,-240.827],[-72.443,-227.612]],"c":true}]}]},"nm":"Path 33","hd":false},{"ind":33,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.512,3.311],[7.408,-2.515],[0.879,-9.578]],"o":[[-0.512,-3.311],[-4.274,1.451],[-0.879,9.578]],"v":[[1.755,-24.526],[-15.758,-89.049],[0.539,-22.899]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[1.386,8.956],[7.408,-2.515],[2.377,-25.91]],"o":[[-1.386,-8.956],[-4.274,1.451],[-2.377,25.91]],"v":[[0.805,-37.612],[-23.169,-137.772],[-2.483,-33.211]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.044,0.286],[7.408,-2.515],[0.076,-0.827]],"o":[[-0.044,-0.286],[-4.274,1.451],[-0.076,0.827]],"v":[[0.051,-14.02],[-30.928,-169.383],[-0.054,-13.879]],"c":true}]},{"t":100,"s":[{"i":[[0.001,0.005],[7.408,-2.515],[0.001,-0.016]],"o":[[-0.001,-0.005],[-4.274,1.451],[-0.001,0.016]],"v":[[-36.439,-204.114],[-50.169,-247.772],[-36.441,-204.112]],"c":true}]}]},"nm":"Path 34","hd":false},{"ind":34,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.275,0.477],[4.481,1.632],[2.961,-6.55],[-1.425,0.478]],"o":[[-2.55,-0.954],[-6.936,-2.526],[-1.48,3.275],[1.425,-0.478]],"v":[[5.014,-23.954],[2.623,-65.932],[2.07,-24.842],[3.754,-13.991]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.449,1.291],[4.481,1.632],[8.009,-17.719],[-3.855,1.292]],"o":[[-6.899,-2.581],[-6.936,-2.526],[-4.004,8.859],[3.855,-1.292]],"v":[[8.769,-35.211],[4.357,-117.808],[0.805,-37.612],[5.361,-8.261]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":43,"s":[{"i":[[0.11,0.041],[4.481,1.632],[0.256,-0.566],[-0.123,0.041]],"o":[[-0.22,-0.082],[-6.936,-2.526],[-0.128,0.283],[0.123,-0.041]],"v":[[0.788,-14.342],[3.064,-156.748],[0.534,-14.419],[0.679,-13.482]],"c":true}]},{"t":100,"s":[{"i":[[0.002,0.001],[4.481,1.632],[0.005,-0.011],[-0.002,0.001]],"o":[[-0.004,-0.002],[-6.936,-2.526],[-0.002,0.005],[0.002,-0.001]],"v":[[1.066,-235.113],[-0.143,-253.308],[1.061,-235.114],[1.064,-235.097]],"c":true}]}]},"nm":"Path 35","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":7,"k":{"a":0,"k":[0.166,1,1,1,0.291,0.919,0.91,1,0.379,0.837,0.82,1,0.522,0.444,0.645,0.994,0.708,0.05,0.47,0.988,0.865,0.262,0.465,0.992,0.999,0.474,0.461,0.996,0.166,1,0.291,1,0.379,1,0.522,0.9,0.708,0.8,0.865,0.8,0.999,0.8]}},"s":{"a":0,"k":[-12,-18]},"e":{"a":0,"k":[-123.596,-129.596]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-6.793,2.506]},"a":{"a":0,"k":[-6.793,2.506]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"white splashes precomp","refId":"comp_2","sr":1,"ks":{"p":{"a":0,"k":[243,243,0]},"a":{"a":0,"k":[243,243,0]}},"ao":0,"w":486,"h":486,"ip":-4,"op":99,"st":-5,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"ogonek5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[1],"y":[0]},"t":30,"s":[100]},{"t":45,"s":[0]}]},"p":{"a":0,"k":[241,232.25,0]},"a":{"a":0,"k":[-51,43,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,15.828]},"t":30,"s":[0,0,100]},{"t":45,"s":[151,151,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[164,164]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":7,"k":{"a":0,"k":[0.166,1,1,1,0.291,0.886,0.886,0.99,0.379,0.773,0.773,0.98,0.522,0.386,0.644,0.967,0.708,0,0.516,0.953,0.865,0.203,0.631,0.969,0.999,0.405,0.746,0.984,0.166,1,0.291,1,0.379,1,0.522,0.9,0.708,0.8,0.865,0.8,0.999,0.8]}},"s":{"a":0,"k":[1,0.5]},"e":{"a":0,"k":[-61.596,-53.096]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 8","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,43]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":30,"op":45,"st":-45,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"rose splashes 80%","sr":1,"ks":{"o":{"a":0,"k":80},"p":{"a":0,"k":[283.287,244.6,0]},"a":{"a":0,"k":[40.287,1.6,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[8.458,2.584],[-1.178,1.927]],"o":[[0,0],[1.178,-1.927]],"v":[[47.396,-2.495],[66.132,8.305]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[17.915,5.472],[-2.495,4.082]],"o":[[0,0],[2.494,-4.082]],"v":[[127.416,14.655],[167.1,37.528]],"c":true}]},{"t":100,"s":[{"i":[[2.922,0.892],[-0.407,0.666]],"o":[[0,0],[0.407,-0.666]],"v":[[231.544,79.714],[238.016,83.445]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-7.371,11.75],[-2.605,-2.954]],"o":[[0,0],[2.605,2.954]],"v":[[-19.417,21.61],[-33.613,50.565]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-16.511,26.322],[-5.835,-6.617]],"o":[[0,0],[5.835,6.617]],"v":[[-54.289,78.774],[-86.09,143.637]],"c":true}]},{"t":100,"s":[{"i":[[-2.019,3.219],[-0.714,-0.809]],"o":[[0,0],[0.714,0.809]],"v":[[-120,208.391],[-123.889,216.322]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.272,18.752],[3.489,0]],"o":[[0,0],[-2.919,0]],"v":[[13.337,36.564],[18.266,63.997]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[9.5,41.701],[7.758,0]],"o":[[0,0],[-6.491,0]],"v":[[34.44,120.578],[45.401,181.583]],"c":true}]},{"t":100,"s":[{"i":[[1.211,5.316],[0.989,0]],"o":[[0,0],[-0.827,0]],"v":[[49.981,239.625],[51.378,247.402]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[5.357,10.649],[2.065,-1.671]],"o":[[0,0],[-2.092,1.693]],"v":[[27.785,38.344],[37.752,60.836]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[12.682,25.21],[4.889,-3.955]],"o":[[0,0],[-4.953,4.007]],"v":[[73.965,124.33],[97.561,177.576]],"c":true}]},{"t":100,"s":[{"i":[[1.068,2.124],[0.412,-0.333]],"o":[[0,0],[-0.417,0.338]],"v":[[112.746,219.25],[114.734,223.735]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[7.125,6.764],[2.818,-3.288]],"o":[[0,0],[-1.952,2.278]],"v":[[39.186,19.394],[56.431,42.171]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[16.508,15.671],[6.529,-7.617]],"o":[[0,0],[-4.523,5.277]],"v":[[103.804,73.868],[143.76,126.64]],"c":true}]},{"t":100,"s":[{"i":[[1.631,1.548],[0.645,-0.753]],"o":[[0,0],[-0.447,0.521]],"v":[[178.352,168.75],[182.3,173.964]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[14.003,-18.483],[-2.995,-2.598]],"o":[[0,0],[2.346,2.035]],"v":[[19.896,-54.149],[37.88,-77.508]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[31.33,-41.355],[-6.701,-5.813]],"o":[[0,0],[5.249,4.553]],"v":[[52.828,-125.71],[93.066,-177.974]],"c":true}]},{"t":100,"s":[{"i":[[3.858,-5.092],[-0.825,-0.716]],"o":[[0,0],[0.646,0.561]],"v":[[119.615,-214],[124.57,-220.435]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.092,-13.223],[3.225,-0.461]],"o":[[0,0],[-3.225,0.461]],"v":[[-11.7,-53.501],[-23.447,-74.576]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-13.694,-29.721],[7.248,-1.036]],"o":[[0,0],[-7.248,1.036]],"v":[[-33.744,-123.528],[-60.149,-170.902]],"c":true}]},{"t":100,"s":[{"i":[[-1.641,-3.562],[0.869,-0.124]],"o":[[0,0],[-0.869,0.124]],"v":[[-92.793,-229.25],[-95.958,-234.928]],"c":true}]}]},"nm":"Path 7","hd":false},{"ty":"fl","c":{"a":0,"k":[0.399169562845,0.676376941157,0.988235294118,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 7","hd":false},{"ty":"tr","p":{"a":0,"k":[40.287,1.6]},"a":{"a":0,"k":[40.287,1.6]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"rose splashes 40%","sr":1,"ks":{"o":{"a":0,"k":40},"p":{"a":0,"k":[255.462,250.532,0]},"a":{"a":0,"k":[12.462,7.532,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.498,-1.717],[-2.802,-3.967]],"o":[[-1.32,0.907],[0,0]],"v":[[-27.909,-69.864],[-23.485,-62.918]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[5.583,-3.836],[-6.261,-8.864]],"o":[[-2.949,2.026],[0,0]],"v":[[-77.365,-168.395],[-67.479,-152.872]],"c":true}]},{"t":100,"s":[{"i":[[0.692,-0.476],[-0.776,-1.099]],"o":[[-0.366,0.251],[0,0]],"v":[[-104.452,-204.425],[-103.226,-202.5]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-4.758,-1.202],[3.089,-1.172]],"o":[[0,0],[-3.089,1.172]],"v":[[-40.584,-41.866],[-51.165,-45.778]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-10.775,-2.722],[6.996,-2.654]],"o":[[0,0],[-6.996,2.654]],"v":[[-111.836,-92.023],[-135.799,-100.881]],"c":true}]},{"t":100,"s":[{"i":[[-1.235,-0.312],[0.802,-0.304]],"o":[[0,0],[-0.802,0.304]],"v":[[-194.867,-116.25],[-197.612,-117.265]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-6.418,17.711],[-4.713,-1.091]],"o":[[0,0],[2.758,0.638]],"v":[[-22.297,35.041],[-30.452,64.628]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-14.835,40.938],[-10.895,-2.522]],"o":[[0,0],[6.375,1.475]],"v":[[-62.088,115.52],[-80.938,183.907]],"c":true}]},{"t":100,"s":[{"i":[[-1.49,4.112],[-1.094,-0.253]],"o":[[0,0],[0.64,0.148]],"v":[[-89,203.172],[-90.893,210.041]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[1.125,2.009],[-1.205,-2.41]],"o":[[-0.644,-1.15],[1.205,2.41]],"v":[[60.208,34.488],[58.28,36.577]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[2.266,4.046],[-2.428,-4.856]],"o":[[-1.297,-2.316],[2.428,4.856]],"v":[[161.46,114.372],[157.576,118.58]],"c":true}]},{"t":100,"s":[{"i":[[0.457,0.815],[-0.489,-0.978]],"o":[[-0.261,-0.467],[0.489,0.978]],"v":[[178.425,143.467],[177.643,144.315]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.378,1.888],[-0.054,-1.294]],"o":[[-0.244,-1.218],[0.054,1.294]],"v":[[57.201,-37.513],[53.318,-36.704]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[0.721,3.602],[-0.103,-2.47]],"o":[[-0.465,-2.323],[0.103,2.47]],"v":[[152.987,-80.688],[145.577,-79.144]],"c":true}]},{"t":100,"s":[{"i":[[0.177,0.884],[-0.025,-0.606]],"o":[[-0.114,-0.57],[0.025,0.606]],"v":[[207.62,-96.485],[205.801,-96.106]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.033,1.484],[-0.082,-1.894]],"o":[[0.031,-1.392],[0.082,1.894]],"v":[[53.595,-30.294],[48.408,-29.669]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-0.074,3.328],[-0.185,-4.247]],"o":[[0.069,-3.121],[0.185,4.247]],"v":[[143.792,-61.221],[132.159,-59.821]],"c":true}]},{"t":100,"s":[{"i":[[-0.009,0.404],[-0.022,-0.516]],"o":[[0.008,-0.379],[0.022,0.516]],"v":[[216.285,-76.436],[214.873,-76.266]],"c":true}]}]},"nm":"Path 6","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.674509803922,0.988235294118,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 6","hd":false},{"ty":"tr","p":{"a":0,"k":[12.462,7.532]},"a":{"a":0,"k":[12.462,7.532]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"violet","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[33.333]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[100]},{"t":70,"s":[0]}]},"p":{"a":0,"k":[250.172,221.61,0]},"a":{"a":0,"k":[7.172,-21.39,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.629,0.629,0.629],"y":[0,0,0]},"t":30,"s":[43.235,43.235,100]},{"t":40,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[8.424,-5.071],[2.653,-10.693],[8.493,-0.25],[-0.281,-21.575],[8.206,2.479],[-9.071,-18.319],[10.163,-0.826],[-8.504,-13.367],[7.925,-6.142],[-9.44,-13.344],[2.854,-8.942],[-21.641,-24.17],[6.753,-1.287],[-6.374,-4.022],[10.533,-2.098],[-13.32,-3.511],[-1.839,-6.483],[-21.075,-3.827],[-5.954,-6.816],[-11.932,-2.571],[3.257,-17.575],[-38.942,9.134],[-0.325,-7.706],[-8.742,1.73],[-8.801,-7.785],[-30.134,14.717],[-9.382,-4.129],[-16.279,6.661],[1.088,-8.515],[-7.687,7.097],[-6.012,-8.868],[-10.651,19.166],[-10.35,0.696],[-9.579,21.376],[-8.09,4.605],[-5.131,22.602],[-3.808,-4.758],[-0.748,8.961],[-7.254,-2.788],[2.909,7.968],[-7.635,-1.047],[9.08,17.754],[-7.507,3.193],[9.997,10.579],[-6.351,4.692],[9.781,8.968],[0.178,6.817],[17.454,12.482],[0.03,6.973],[12.265,1.257],[-5.162,4.439],[17.018,1.365],[-5.218,7.12],[24.328,-1.527],[-3.121,9.666],[14.883,-6.778],[10.864,5.993],[11.338,-8.563],[5.168,9.097],[9.336,-10.339],[5.422,9.555],[17.401,-17.591],[8.409,7.467],[2.648,-7.068],[8.104,5.903],[5.776,-17.779],[7.39,-3.513],[1.572,-7.178],[9.221,3.529],[4.343,-6.683]],"o":[[-7.125,5.075],[-0.627,-20.281],[-8.492,0.25],[-7.605,-11.419],[-6.68,-3.509],[-2.4,-2.857],[-8.37,0.053],[-2.455,-2.437],[-7.318,5.151],[-25.313,-18.266],[-0.866,7.957],[-15.111,-8.219],[-6.731,1.147],[0,0],[-8.775,1.322],[-36.623,-3.316],[2.35,6.725],[-4.284,0.119],[5.62,5.119],[0,0],[-0.325,7.953],[-14.317,6.526],[0.333,6.979],[-5.382,3.713],[7.32,4.607],[-4.424,3.372],[7.613,2.532],[-10.42,10.905],[-1.668,7.104],[-2.075,4.92],[4.11,6.36],[-1.424,4.903],[7.699,-1.121],[0,0],[8.176,-4.621],[-1.052,23.086],[4.14,4.652],[5.615,9.885],[7.25,2.786],[5.203,5.546],[7.441,1.093],[15.761,17.732],[7.507,-3.193],[5.904,3.732],[6.351,-4.692],[9.84,5.408],[-0.622,-6.552],[19.337,4.71],[0.329,-7.351],[6.418,-1.574],[5.14,-4.409],[27.522,-2.876],[5.215,-7.117],[4.426,-6.277],[2.875,-9.309],[1.399,-5.562],[-7.079,-5.34],[4.34,-9.001],[-5.002,-8.493],[13.638,-18.255],[-5.312,-9.225],[0.333,-7.822],[-7.414,-6.268],[3.497,-13.498],[-8.104,-5.903],[-0.973,-5.144],[-8.335,3.293],[-2.994,-1.674],[-9.108,-3.513],[-0.006,-3.457]],"v":[[5.228,-95.512],[7.462,-28.929],[-2.195,-87.293],[6.416,-28.961],[-31.261,-81.602],[2.547,-32.204],[-43.849,-81.375],[0.273,-32.419],[-44.643,-71.2],[-1.483,-31.501],[-68.757,-71.437],[-0.116,-26.024],[-42.303,-41.201],[-2.32,-26.028],[-60.656,-35.365],[-6.28,-25.891],[-67.569,-24.781],[-6.096,-24.791],[-61.84,-18.285],[-3.524,-23.253],[-83.167,12.168],[-3.472,-21.52],[-43.616,2.576],[-3.348,-19.306],[-64.141,31.565],[0.002,-18.262],[-50.895,27.819],[3.739,-17.431],[-29.038,27.111],[2.554,-14.294],[-16.457,46.001],[4.192,-11.614],[-10.643,61.853],[6.528,-12.171],[7.719,52.216],[8.895,-14.393],[13.275,34.112],[10.222,-12.355],[26.825,31.666],[12.417,-11.04],[35.999,36.579],[14.675,-10.481],[49.25,44.887],[15.923,-11.135],[54.41,30.884],[17.017,-12.053],[59.976,24.143],[15.598,-15.029],[60.134,6.365],[15.406,-16.076],[56.972,-8.479],[17.766,-17.986],[90.136,-9.191],[19.955,-19.743],[79.759,-35.208],[20.474,-22.64],[63.92,-44.118],[18.736,-24.334],[63.006,-55.052],[17.431,-26.232],[77.149,-67.057],[16.295,-27.947],[47.537,-73.383],[14.878,-29.037],[41.274,-77.714],[13.733,-29.91],[25.971,-72.901],[12.306,-29.047],[19.524,-85.462],[9.122,-30.564]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[8.849,0.01],[7.184,-28.957],[3.052,-1.273],[-0.761,-58.423],[7.839,-5.353],[-24.563,-49.607],[9.192,-3.975],[-23.029,-36.198],[4.861,-7.946],[-25.563,-36.135],[12.25,-6.125],[-58.602,-65.452],[0.423,-3.502],[-17.26,-10.89],[11.497,-5.096],[-36.071,-9.509],[-2.201,-1.041],[-57.07,-10.364],[-1.998,-10.013],[-32.312,-6.962],[9.36,-30.715],[-105.454,24.735],[0.047,-4.178],[-23.672,4.686],[-7.461,-15.968],[-81.601,39.854],[-8.157,-7.347],[-44.083,18.039],[-3.026,-7.306],[-20.815,19.217],[-8.186,-10.792],[-28.842,51.901],[-12.022,-1.924],[-25.94,57.885],[-10.365,1.613],[-13.894,61.205],[-1.581,0.481],[-2.025,24.266],[-5.413,1.006],[7.876,21.576],[-4.573,1.132],[24.589,48.078],[-6.41,1.365],[27.072,28.648],[-4.858,2.346],[26.485,24.286],[-6.451,3.968],[47.266,33.8],[-2.771,2.906],[33.213,3.403],[-1.167,1.667],[46.083,3.696],[-1.333,9],[65.879,-4.134],[-5.083,7.361],[40.303,-18.354],[15.513,2.68],[30.702,-23.187],[1.943,7.096],[25.282,-27.999],[2.5,7.5],[47.12,-47.637],[6.155,7.421],[7.17,-19.139],[5.169,3.176],[15.641,-48.146],[3.103,0.727],[4.257,-19.437],[6.927,0.949],[11.76,-18.098]],"o":[[-5.323,-0.006],[-1.697,-54.919],[-3.052,1.273],[-20.594,-30.923],[-3.728,2.546],[-6.498,-7.737],[-4.346,1.879],[-6.647,-6.6],[-3.22,5.264],[-68.548,-49.463],[-6.872,3.436],[-40.921,-22.258],[-0.377,3.121],[0,0],[-6.756,2.995],[-99.173,-8.979],[3.585,1.695],[-11.602,0.322],[1.08,5.412],[0,0],[-1.42,4.661],[-38.77,17.671],[-0.025,2.213],[-14.575,10.054],[3.439,7.359],[-11.98,9.131],[3.354,3.021],[-28.218,29.53],[1.451,3.503],[-5.619,13.324],[3.041,4.009],[-3.855,13.277],[4.839,0.774],[0,0],[10.589,-1.648],[-2.849,62.516],[2.488,-0.757],[15.206,26.767],[5.413,-1.006],[14.09,15.019],[4.055,-1.004],[42.68,48.017],[6.41,-1.365],[15.989,10.107],[4.858,-2.346],[26.647,14.644],[5.257,-3.234],[52.364,12.754],[3.745,-3.927],[17.38,-4.263],[1.115,-1.592],[74.53,-7.789],[1.333,-9],[11.985,-16.998],[4.419,-6.4],[3.79,-15.061],[-5.258,-0.908],[11.752,-24.375],[-1.495,-5.458],[36.932,-49.434],[-2.202,-6.607],[0.901,-21.183],[-3.464,-4.176],[9.469,-36.552],[-5.169,-3.176],[-2.634,-13.929],[-5.658,-1.325],[-8.107,-4.533],[-6.628,-0.908],[-0.017,-9.362]],"v":[[3.02,-162.999],[8.243,-41.492],[-9.448,-140.273],[5.428,-41.577],[-66.581,-136.474],[-4.982,-50.303],[-94.937,-141.294],[-11.103,-50.883],[-91.631,-117.558],[-15.827,-48.412],[-150.75,-126.625],[-12.148,-33.673],[-76.677,-54.762],[-18.079,-33.683],[-125.935,-48.926],[-28.737,-33.315],[-144.516,-27.935],[-28.241,-30.355],[-129.957,-16.393],[-21.32,-26.218],[-189.443,52.219],[-21.179,-21.553],[-84.613,22.128],[-20.845,-15.595],[-143.927,91.798],[-11.831,-12.788],[-109.552,77.33],[-1.776,-10.549],[-60.574,69.369],[-4.963,-2.109],[-41.055,117.272],[-0.555,5.102],[-30.196,155.83],[5.729,3.604],[8.665,130.425],[12.099,-2.376],[18.32,81.416],[15.669,3.108],[41.962,76.131],[21.577,6.648],[62.49,91.451],[27.654,8.15],[95.923,118.365],[31.011,6.393],[102.975,84.18],[33.954,3.923],[112.957,71.446],[30.136,-4.087],[107.438,31.594],[29.62,-6.903],[94.5,0.667],[35.97,-12.045],[183.167,5],[41.86,-16.772],[147.562,-47.507],[43.258,-24.567],[106.809,-61.148],[38.582,-29.125],[103.057,-80.596],[35.068,-34.233],[141.167,-109],[32.012,-38.849],[76.865,-113.118],[28.198,-41.782],[66.503,-119.657],[25.119,-44.132],[37.324,-105.068],[21.279,-41.807],[29.931,-135.604],[12.71,-45.889]],"c":true}]},{"t":100,"s":[{"i":[[6.52,-0.499],[0,0],[7.552,-1.773],[0,0],[7.419,-3.974],[0,0],[5.063,-2.794],[0,0],[3.869,-4.058],[0,0],[3.25,-5.125],[0,0],[2.323,-7.262],[0,0],[1.065,-7.426],[0,0],[-0.016,-6.935],[0,0],[0.043,-8.393],[0,0],[-1.943,-5.281],[0,0],[-4.613,-6.872],[0,0],[-4.427,-4.702],[0,0],[-5.552,-5.67],[0,0],[-7.074,-5.131],[0,0],[-7.555,-2.728],[0,0],[-8.696,0.33],[0,0],[-5.835,-0.075],[0,0],[-5.179,-0.584],[0,0],[-7.538,4.131],[0,0],[-4.51,1.951],[0,0],[-7.077,4.865],[0,0],[-5.025,3.68],[0,0],[-5.043,6.946],[0,0],[-2.062,4.094],[0,0],[-1.5,7.666],[0,0],[-1.333,7.5],[0,0],[-0.438,6.493],[0,0],[2.309,7.852],[0,0],[1.057,4.404],[0,0],[1.667,6.5],[0,0],[6.365,6.382],[0,0],[3.503,2.843],[0,0],[5.324,3.432],[0,0],[2.931,0.896],[0,0]],"o":[[-6.521,0.499],[0,0],[-7.552,1.773],[0,0],[-7.419,3.974],[0,0],[-5.063,2.794],[0,0],[-3.869,4.058],[0,0],[-3.25,5.125],[0,0],[-2.323,7.262],[0,0],[-1.065,7.426],[0,0],[0.016,6.935],[0,0],[-0.043,8.393],[0,0],[1.943,5.281],[0,0],[4.613,6.872],[0,0],[4.427,4.702],[0,0],[5.552,5.67],[0,0],[7.074,5.131],[0,0],[7.555,2.728],[0,0],[8.696,-0.33],[0,0],[5.835,0.075],[0,0],[5.18,0.584],[0,0],[7.538,-4.131],[0,0],[4.51,-1.951],[0,0],[7.077,-4.865],[0,0],[5.025,-3.68],[0,0],[5.043,-6.946],[0,0],[2.062,-4.094],[0,0],[1.5,-7.666],[0,0],[1.333,-7.5],[0,0],[0.438,-6.493],[0,0],[-2.309,-7.852],[0,0],[-1.057,-4.404],[0,0],[-1.667,-6.5],[0,0],[-6.365,-6.382],[0,0],[-3.503,-2.843],[0,0],[-5.324,-3.432],[0,0],[-2.931,-0.896],[0,0]],"v":[[0.021,-238.999],[6.522,-22.25],[-22.948,-238.773],[6.486,-22.251],[-111.081,-205.974],[6.353,-22.363],[-136.437,-188.794],[6.274,-22.37],[-151.131,-175.058],[6.214,-22.339],[-176.75,-144.125],[6.261,-22.15],[-199.177,-102.262],[6.185,-22.15],[-209.935,-65.926],[6.048,-22.145],[-215.016,-31.435],[6.055,-22.107],[-215.457,-11.893],[6.143,-22.054],[-201.443,57.219],[6.145,-21.994],[-194.113,74.128],[6.149,-21.918],[-171.427,112.298],[6.265,-21.882],[-163.052,122.83],[6.394,-21.853],[-126.074,156.869],[6.353,-21.745],[-65.055,189.772],[6.41,-21.653],[-38.196,196.33],[6.49,-21.672],[9.165,202.425],[6.572,-21.748],[31.821,199.416],[6.618,-21.678],[80.962,187.631],[6.693,-21.633],[105.49,178.451],[6.771,-21.613],[127.923,167.865],[6.814,-21.636],[157.975,144.18],[6.852,-21.668],[174.957,125.946],[6.803,-21.77],[204.438,84.094],[6.796,-21.806],[223.5,34.166],[6.878,-21.872],[227.667,12],[6.953,-21.933],[227.062,-61.507],[6.971,-22.033],[213.309,-104.148],[6.911,-22.091],[198.057,-137.596],[6.866,-22.157],[195.667,-144],[6.827,-22.216],[142.865,-200.118],[6.778,-22.254],[122.503,-211.157],[6.739,-22.284],[84.324,-230.568],[6.689,-22.254],[51.431,-240.604],[6.58,-22.306]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":80},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.001,0.031,0.138,0.537,0.707,0.106,0.275,0.555,1,0.182,0.412,0.573]}},"s":{"a":0,"k":[4,-20]},"e":{"a":0,"k":[177.39,-20]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 3","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-4,"op":99,"st":-65,"bm":0}]},{"id":"comp_7","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"picies2","sr":1,"ks":{"p":{"a":0,"k":[257.33,185.73,0]},"a":{"a":0,"k":[-265.67,-10.27,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,-0.35],[53.84,0],[-19.93,11.56],[-1,-40.01]],"o":[[0,50.03],[-31.67,-34.87],[39.26,-0.87],[0.01,0.35]],"v":[[-168.19,-10.27],[-265.67,80.32],[-266.5,-79.82],[-168.2,-11.32]],"c":true}]},{"t":35,"s":[{"i":[[0,-0.004],[0.613,0],[-0.227,0.132],[-0.011,-0.455]],"o":[[0,0.569],[-0.36,-0.397],[0.447,-0.01],[0,0.004]],"v":[[-145.03,70.7],[-146.14,71.731],[-146.149,69.909],[-145.03,70.688]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-2.5,-7],[15.65,-61.05],[-53.84,0]],"o":[[0,0],[0,-50.03],[0,0]],"v":[[-266.5,-79.82],[-363.15,-10.27],[-265.67,-100.86]],"c":true}]},{"t":35,"s":[{"i":[[-0.013,-0.036],[0.08,-0.312],[-0.275,0]],"o":[[0,0],[0,-0.256],[0,0]],"v":[[-383.165,-126.689],[-383.659,-126.333],[-383.161,-126.797]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-0.6,-49.55],[39.26,-0.87],[0,0]],"o":[[-1,-40.01],[-2.5,-7],[53.46,0]],"v":[[-168.2,-11.32],[-266.5,-79.82],[-265.67,-100.86]],"c":true}]},{"t":35,"s":[{"i":[[-0.018,-1.483],[1.175,-0.026],[0,0]],"o":[[-0.03,-1.198],[-0.075,-0.21],[1.6,0]],"v":[[-162.091,-102.75],[-165.034,-104.8],[-165.009,-105.43]],"c":true}]}]},"nm":"Path 3","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.360784313725,0.698039215686,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 3","hd":false},{"ty":"tr","p":{"a":0,"k":[-265.67,-10.27]},"a":{"a":0,"k":[-265.67,-10.27]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-31.67,-34.87],[0,50.03],[0,0]],"o":[[-53.84,0],[15.65,-61.05],[-19.93,11.56]],"v":[[-265.67,80.32],[-363.15,-10.27],[-266.5,-79.82]],"c":true}]},{"t":35,"s":[{"i":[[-0.099,-0.109],[0,0.156],[0,0]],"o":[[-0.168,0],[0.049,-0.191],[-0.062,0.036]],"v":[[-415.258,90.5],[-415.562,90.217],[-415.26,90]],"c":true}]}]},"nm":"Path 4","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.286274509804,0.552941176471,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 3","hd":false},{"ty":"tr","p":{"a":0,"k":[-314.41,0.25]},"a":{"a":0,"k":[-314.41,0.25]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"st","c":{"a":0,"k":[0,0.207843152214,0.400000029919,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":24},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-265.67,-10.27]},"a":{"a":0,"k":[-265.67,-10.27]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":30,"op":35,"st":2,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"picies","sr":1,"ks":{"p":{"a":0,"k":[255,191.406,0]},"a":{"a":0,"k":[0,45.406,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[-14.81,-13.46],[11.95,21.92],[1.11,16.11],[-22.44,-7.84],[-11.16,1.15]],"o":[[-22.91,4.11],[-6.67,-12.23],[5.79,24.31],[9.97,3.48],[-0.13,0.68]],"v":[[10.978,133.929],[-49.332,107.219],[-76.002,35.549],[-29.982,86.199],[2.028,89.909]],"c":true}]},{"t":36,"s":[{"i":[[0.042,0.009],[-0.025,-0.045],[-0.002,-0.033],[0.03,0.046],[0.013,0.008]],"o":[[0.048,-0.009],[0.014,0.025],[-0.012,-0.05],[-0.009,-0.013],[-0.02,-0.013]],"v":[[-71.68,195.17],[-71.555,195.225],[-71.5,195.374],[-71.582,195.231],[-71.615,195.198]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[-20.7,35.34],[6.66,-12.23],[15.67,-2.82],[0,0],[-0.13,0.68]],"o":[[-1.11,16.11],[-8.18,15],[0,0],[-14.81,-13.46],[25.91,-2.68]],"v":[[75.998,35.549],[49.338,107.219],[10.988,133.929],[10.978,133.929],[2.028,89.909]],"c":true}]},{"t":36,"s":[{"i":[[-0.149,0.254],[0.048,-0.088],[0.112,-0.02],[0,0],[-0.001,0.005]],"o":[[-0.008,0.116],[-0.059,0.108],[0,0],[0.129,-0.1],[0.093,-0.157]],"v":[[73.967,180.898],[73.775,181.412],[73.5,181.604],[73.5,181.604],[73.665,181.395]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[4.69,-50.46],[25.91,-2.68],[9.97,3.48],[-5.65,29.96],[19.36,23.12]],"o":[[-20.7,35.34],[-11.16,1.15],[25.15,-11.39],[3.71,-19.59],[38.33,2.51]],"v":[[75.998,35.549],[2.028,89.909],[-29.982,86.199],[26.648,21.049],[6.078,-43.891]],"c":true}]},{"t":36,"s":[{"i":[[0.019,-0.202],[0.104,-0.011],[0.04,0.014],[-0.023,0.12],[0.077,0.092]],"o":[[-0.083,0.141],[-0.045,0.005],[0.101,-0.046],[0.015,-0.078],[0.153,0.01]],"v":[[154.424,34.876],[154.128,35.094],[154,35.079],[154.226,34.818],[154.144,34.559]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[3.71,-19.59],[21,-18.31],[-37.83,-2.49]],"o":[[-10.36,-22.61],[4.41,-44.16],[19.36,23.12]],"v":[[26.648,21.049],[-76.002,18.639],[6.078,-43.891]],"c":true}]},{"t":36,"s":[{"i":[[0.012,-0.061],[0.066,-0.057],[-0.118,-0.008]],"o":[[-0.032,-0.071],[0.014,-0.138],[0.06,0.072]],"v":[[1.192,-97.167],[0.872,-97.174],[1.128,-97.369]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[-10.36,-22.61],[25.15,-11.39],[5.79,24.31],[-0.53,5.3]],"o":[[-5.65,29.96],[-22.44,-7.84],[-0.55,-5.97],[21,-18.31]],"v":[[26.648,21.049],[-29.982,86.199],[-76.002,35.549],[-76.002,18.639]],"c":true}]},{"t":36,"s":[{"i":[[-0.156,-0.229],[0.005,-0.198],[0.125,0.524],[-0.011,0.114]],"o":[[0.175,0.257],[-0.483,-0.169],[-0.012,-0.129],[0.113,0.148]],"v":[[-149.053,15.247],[-148.42,15.955],[-149.412,14.864],[-149.412,14.5]],"c":true}]}]},"nm":"Path 5","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.360784313725,0.698039215686,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 5","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.207843137255,0.4,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":24},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,45.406]},"a":{"a":0,"k":[0,45.406]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":31,"op":36,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 3","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[256]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":21,"s":[257.445]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[251.735]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[262.162]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[256.384]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[256.888]},{"t":29,"s":[254.404]}]},"y":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[198]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[190.425]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[197.133]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[186.523]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[194.791]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26,"s":[186.058]},{"t":29,"s":[194.761]}]}},"a":{"a":0,"k":[50,50,0]}},"ao":0,"ip":20,"op":30,"st":-60,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Null 19","parent":3,"sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[50,108,0],"to":[0,0,0],"ti":[0,0,0]},{"t":30,"s":[50,1,0]}]}},"ao":0,"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"blik","parent":6,"sr":1,"ks":{"p":{"a":0,"k":[20.897,-88.771,0]},"a":{"a":0,"k":[19.25,-92,0]},"s":{"a":0,"k":[70,70,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.427,-0.298],[-30.5,146.5]],"o":[[0,13.193],[-55,1.5],[-0.5,0]],"v":[[80.444,-33.889],[0,-10],[0,-200]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-61.227],[24.905,-1.3],[-8.202,27.448]],"o":[[0,40.169],[-45,-4.333],[2.417,-2.431]],"v":[[98.282,62.507],[32.976,130.655],[10.893,-24.206]],"c":true}]},{"t":30,"s":[{"i":[[0,-77.778],[23.059,-1.647],[-4.109,3.995]],"o":[[0,50.031],[-47.216,-6.039],[3.294,-3.203]],"v":[[109.918,84.947],[41.535,169.497],[12.437,7.17]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":8},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-131]},{"t":30,"s":[-5]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8.571},"lc":2,"lj":2,"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":2}},{"n":"g","nm":"gap","v":{"a":0,"k":12}},{"n":"d","nm":"dash2","v":{"a":0,"k":297}},{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":30,"st":-60,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"rocket_cap","parent":10,"sr":1,"ks":{"p":{"a":0,"k":[0,-33.889,0]},"a":{"a":0,"k":[0,-33.889,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.427,-0.298],[-30.5,146.5]],"o":[[0,13.193],[-55,1.5],[-0.5,0]],"v":[[80.444,-33.889],[0,-10],[0,-200]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-61.227],[24.905,-1.3],[-8.202,27.448]],"o":[[0,40.169],[-45,-4.333],[2.417,-2.431]],"v":[[87.389,21.435],[22.083,89.583],[0,-65.278]],"c":true}]},{"t":30,"s":[{"i":[[0,-77.778],[23.059,-1.647],[-4.109,3.995]],"o":[[0,50.031],[-47.216,-6.039],[3.294,-3.203]],"v":[[97.776,39.412],[29.392,123.961],[0.294,-38.366]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.360784313725,0.698039215686,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.428,0],[0,13.193],[0,0]],"o":[[0,13.193],[-44.428,0],[0,-13.193],[0,0]],"v":[[80.444,-33.889],[0,-10],[-80.444,-33.889],[0,-200]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-61.227],[48.264,0],[0,40.168],[0,0]],"o":[[0,40.168],[-48.264,0],[0,-61.227],[0,0]],"v":[[87.389,21.435],[0,94.167],[-87.389,21.435],[0,-65.278]],"c":true}]},{"t":30,"s":[{"i":[[0,-77.778],[53.838,0],[0,50.031],[0,0]],"o":[[0,50.031],[-53.838,0],[0,-77.778],[0,0]],"v":[[97.776,39.412],[0.294,130],[-97.187,39.412],[0.294,-38.366]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.286274509804,0.552941176471,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-105]},"a":{"a":0,"k":[0,-105]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,-13.193],[44.428,0],[0,13.193],[-44.428,0]],"o":[[0,13.193],[-44.428,0],[0,-13.193],[44.428,0]],"v":[[80.444,-33.889],[0,-10],[-80.444,-33.889],[0,-57.778]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[0,-40.168],[48.264,0],[0,40.168],[-48.264,0]],"o":[[0,40.168],[-48.264,0],[0,-40.168],[48.264,0]],"v":[[87.389,21.435],[0,94.167],[-87.389,21.435],[0,-51.296]],"c":true}]},{"t":30,"s":[{"i":[[0,-50.031],[53.838,0],[0,50.031],[-53.838,0]],"o":[[0,50.031],[-53.838,0],[0,-50.031],[53.838,0]],"v":[[97.776,39.412],[0.294,130],[-97.187,39.412],[0.294,-51.176]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.360784313725,0.698039215686,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-33.889]},"a":{"a":0,"k":[0,-33.889]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"st","c":{"a":0,"k":[0,0.207843137255,0.4,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":24},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"rocket body 2 outlines","parent":10,"sr":1,"ks":{"p":{"a":0,"k":[0,71.389,0]},"a":{"a":0,"k":[0,71.389,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[2.222,-10],[7.734,34.804],[0,0]],"o":[[0,0],[-7.676,34.543],[-2.222,-10],[0,0]],"v":[[68.778,-37.222],[59.333,153.333],[-59.333,153.333],[-68.778,-37.222]],"c":true}]},{"t":30,"s":[{"i":[[0,0],[6.667,-12.222],[20.133,36.91],[1.111,16.111]],"o":[[-1.111,16.111],[-20.133,36.911],[-6.667,-12.222],[0,0]],"v":[[76,35.556],[49.333,107.222],[-49.333,107.222],[-76,35.556]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.207843137255,0.4,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":12},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"rocket body 2 shadows","parent":10,"sr":1,"ks":{"o":{"a":0,"k":50},"p":{"a":0,"k":[0,71.389,0]},"a":{"a":0,"k":[0,71.389,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0.667,-19.333],[7.333,22.167],[0,0]],"o":[[0,0],[0.167,0.667],[-3.217,-9.726],[0,0]],"v":[[-13.222,-18.222],[-12.667,177.333],[-59.333,153.333],[-68.778,-37.222]],"c":true}]},{"t":30,"s":[{"i":[[0,0],[0.167,-11.222],[18.833,29.278],[1.111,16.111]],"o":[[-1.111,16.111],[-15.833,-1.222],[-7.532,-11.709],[0,0]],"v":[[8,37.056],[7.333,133.222],[-49.333,107.222],[-76,35.556]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.207843137255,0.4,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"lines2","parent":10,"sr":1,"ks":{"p":{"a":0,"k":[-4.5,135.446,0]},"a":{"a":0,"k":[-4.5,135.446,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[19,23.25],[0,0],[-72.25,-0.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-45,-7]],"v":[[-67.5,-34.25],[-66,-10.75],[65.25,58.5],[66.5,12.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[19,23.25],[0,0],[-70.25,-2.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-45,-7]],"v":[[-67.5,-34.25],[-67,-26.25],[65.75,25],[68.5,-17]],"c":true}]},{"t":10,"s":[{"i":[[19,23.25],[0,0],[-70.25,-2.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-45,-7]],"v":[[-67.5,-34.25],[-67,-26.25],[68.25,-15],[68.5,-17]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[25,44.25],[0,0],[-71.75,9],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-65,33.75],[-62.5,74.75],[60.75,137],[62.5,98]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[25,44.25],[0,0],[-71.75,10.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-66.5,2.75],[-65,41.25],[61.75,99],[65,63]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[26,30.25],[0,0],[-71.75,10.5],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-68.5,-6.75],[-66,19.75],[62.75,70.5],[68,37]],"c":true}]},{"t":15,"s":[{"i":[[26,30.25],[0,0],[-54.75,16],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50,5]],"v":[[-68.5,-6.75],[-65.5,15.25],[65.75,49],[71,14]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[20,16.5],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[13.25,13.75],[-0.25,0],[-50.75,19]],"v":[[-56.25,160.5],[-56,160.5],[49.25,167.75],[49.75,168]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[20,16.5],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[13.25,13.75],[-0.25,0],[-50.75,19]],"v":[[-54.5,154.25],[-54.25,154.25],[43,161.5],[43.5,161.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[20,16.5],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[13.25,13.75],[-0.25,0],[-50.75,19]],"v":[[-53.75,148],[-53.5,148],[42.25,155.5],[42.75,155.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[17.5,26],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-52.5,136.25],[-52.75,136.25],[42.25,147.75],[42.5,147.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-57.25,102.5],[-55,120.25],[43.25,137.5],[44,133.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-59.25,89.5],[-54.25,106.75],[42.5,128],[48,118.75]],"c":true}]},{"t":30,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-58.5,71.75],[-53.5,89],[43.25,110.25],[48.75,101]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[19,27.5],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-58.5,158.5],[-58.25,158.5],[53.5,166],[54,165]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[19,27.5],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-57.25,147],[-57.25,147.5],[50.5,160],[50,161]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[24.25,45.5],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-60.5,106.5],[-58.75,138.5],[51,150],[51.25,149.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[24.25,45.5],[0,0],[-54.5,29.25],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.75,19]],"v":[[-62.5,78],[-58.25,106.75],[49,141.75],[53.5,124.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[21.25,35.75],[0,0],[-53.25,24.25],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-62.5,67.5],[-59.5,89.75],[50,121.5],[54.5,104.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[21.25,35.75],[0,0],[-49.75,27],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-62.5,63.25],[-60.75,81.25],[52.25,106.5],[57.25,81]],"c":true}]},{"t":30,"s":[{"i":[[21.25,35.75],[0,0],[-49.75,27],[0,0]],"o":[[-0.25,0.25],[11.75,20.5],[-0.25,0],[-50.75,19]],"v":[[-62.5,63.25],[-60.75,70.5],[56.5,84.75],[57.25,81]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[25,44.25],[0,0],[-47,36.25],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-50.5,40.25]],"v":[[-61.5,114.75],[-59,155.75],[63.25,158],[63.5,157.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[25,44.25],[0,0],[-55,45.75],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-62,77.5],[-60.75,112.25],[60.75,151.25],[59.5,132.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[25,44.25],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-64.25,50.75],[-60.75,80.5],[58.25,128],[60.75,101.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[25,39],[0,0],[-58.5,22],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-65.5,33.75],[-63.75,59.5],[59,101.75],[61.5,76.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[25,39],[0,0],[-55.75,26.75],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-69.5,30],[-65,49],[61,82.75],[63.75,62.25]],"c":true}]},{"t":25,"s":[{"i":[[25,39],[0,0],[-55.75,26.75],[0,0]],"o":[[-0.25,0.25],[13.75,25.75],[-0.25,0],[-51,12.25]],"v":[[-69.5,30],[-65,54.75],[62.25,76.25],[63.75,62.25]],"c":true}]}]},"nm":"Path 4","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.360784313725,0.698039215686,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"rocket body","parent":4,"sr":1,"ks":{"p":{"a":0,"k":[0,71.389,0]},"a":{"a":0,"k":[0,71.389,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[2.222,-10],[7.734,34.804],[0,0]],"o":[[0,0],[-7.676,34.543],[-2.222,-10],[0,0]],"v":[[68.778,-37.222],[59.333,153.333],[-59.333,153.333],[-68.778,-37.222]],"c":true}]},{"t":30,"s":[{"i":[[0,0],[6.667,-12.222],[20.133,36.91],[1.111,16.111]],"o":[[-1.111,16.111],[-20.133,36.911],[-6.667,-12.222],[0,0]],"v":[[76,35.556],[49.333,107.222],[-49.333,107.222],[-76,35.556]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.913725550034,0.815686334348,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[283,390,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[257,472,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11,"s":[283,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":17,"s":[262,427.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[280,323,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[279,288,0],"to":[0,0,0],"ti":[0,0,0]},{"t":31,"s":[259,328,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":4,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":10,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":11,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":17,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":18,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":24,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":25,"s":[100,100,100]},{"t":31,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":4,"op":31,"st":4,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[255,394,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[257,472,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":13,"s":[258,356,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":19,"s":[262,427.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[255,327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[261,292,0],"to":[0,0,0],"ti":[0,0,0]},{"t":33,"s":[259,328,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":12,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":13,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":19,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":26,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":27,"s":[100,100,100]},{"t":33,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":6,"op":33,"st":6,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[231,397,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14,"s":[252,494,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[232,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[254,428,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[230,336,0],"to":[0,0,0],"ti":[0,0,0]},{"t":28,"s":[256.5,371.5,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":14,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":15,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":21,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":22,"s":[100,100,100]},{"t":28,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":8,"op":28,"st":8,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[283,390,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[257,472,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":9,"s":[283,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[262,427.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[280,323,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[279,288,0],"to":[0,0,0],"ti":[0,0,0]},{"t":29,"s":[259,328,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":2,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":8,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":9,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":15,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":16,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":22,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":23,"s":[100,100,100]},{"t":29,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":2,"op":29,"st":2,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[231,397,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[252,494,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[232,354,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":13,"s":[254,428,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14,"s":[230,336,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[256.5,371.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[234,298,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[259,328,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[239,262,0],"to":[0,0,0],"ti":[0,0,0]},{"t":33,"s":[264,326,0]}]},"a":{"a":0,"k":[-28,116,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":7,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":13,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":14,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":21,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":27,"s":[20,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":28,"s":[100,100,100]},{"t":33,"s":[20,20,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862745098,0.854901960784,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-28,116]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":34,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"1536_fireworks_3 sec","refId":"comp_0","sr":1,"ks":{"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[768,768,0]},"s":{"a":0,"k":[33.333,33.333,100]}},"ao":0,"w":1536,"h":1536,"ip":0,"op":180,"st":0,"bm":0}]} \ No newline at end of file diff --git a/Tests/LottieMesh/Resources/SUPER Fire.json b/Tests/LottieMesh/Resources/SUPER Fire.json deleted file mode 100644 index a98220cd49e..00000000000 --- a/Tests/LottieMesh/Resources/SUPER Fire.json +++ /dev/null @@ -1 +0,0 @@ -{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":180,"w":512,"h":512,"nm":"!!!1536 scaled 512","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 21","sr":1,"ks":{"p":{"a":0,"k":[1066,1130,0]},"a":{"a":0,"k":[298,362,0]},"s":{"a":0,"k":[33,33,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-100,-560]],"o":[[0,0],[0,0]],"v":[[508,168],[-4,696]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-28,-356]],"o":[[0,0],[0,0]],"v":[[508,168],[244,604]],"c":false}},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[44,-216]],"o":[[0,0],[0,0]],"v":[[508,168],[572,612]],"c":false}},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-48,-180]],"o":[[0,0],[0,0]],"v":[[508,168],[728,208]],"c":false}},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[92,-480]],"o":[[0,0],[0,0]],"v":[[508,168],[-192,392]],"c":false}},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[232,-220]],"o":[[0,0],[-194.813,184.737]],"v":[[508,168],[-68,-20]],"c":false}},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[124,-16]],"o":[[0,0],[0,0]],"v":[[508,168],[112,-288]],"c":false}},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[404,-348]],"c":false}},"nm":"Path 8","hd":false},{"ind":8,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[532,-464]],"c":false}},"nm":"Path 9","hd":false},{"ind":9,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[700,-100]],"c":false}},"nm":"Path 10","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[4]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":16,"s":[4]}]},"e":{"a":0,"k":5},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[115]},{"t":16,"s":[341]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.960784375668,0.717647075653,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.996078491211,0.745098054409,0.337254911661,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":32},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":1,"op":16,"st":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 22","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[100]},{"t":32,"s":[0]}]},"p":{"a":0,"k":[1066,1130,0]},"a":{"a":0,"k":[298,362,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[104,-684]],"o":[[0,0],[0,0]],"v":[[508,168],[236,412]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-12,-324]],"o":[[0,0],[0,0]],"v":[[508,168],[356,520]],"c":false}},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[44,-216]],"o":[[0,0],[0,0]],"v":[[508,168],[612,440]],"c":false}},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-16,-144]],"o":[[0,0],[0,0]],"v":[[508,168],[720,156]],"c":false}},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[92,-480]],"o":[[0,0],[0,0]],"v":[[508,168],[132,336]],"c":false}},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[236,-128]],"o":[[0,0],[-236,128]],"v":[[508,168],[40,116]],"c":false}},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[156,-128]],"o":[[0,0],[-156,128]],"v":[[508,168],[-20,-180]],"c":false}},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[244,-192]],"c":false}},"nm":"Path 8","hd":false},{"ind":8,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[708,-332]],"c":false}},"nm":"Path 9","hd":false},{"ind":9,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[724,-56]],"c":false}},"nm":"Path 10","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[168]},{"t":32,"s":[341]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.956862807274,0.68235296011,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.996078491211,0.643137276173,0.200000017881,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":32},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":32,"st":5,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 20","sr":1,"ks":{"p":{"a":0,"k":[1066,1130,0]},"a":{"a":0,"k":[298,362,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[104,-684]],"o":[[0,0],[0,0]],"v":[[508,168],[88,556]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[476,708]],"c":false}},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[44,-216]],"o":[[0,0],[0,0]],"v":[[508,168],[676,580]],"c":false}},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[20,-228]],"o":[[0,0],[0,0]],"v":[[508,168],[712,288]],"c":false}},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[92,-480]],"o":[[0,0],[0,0]],"v":[[508,168],[24,300]],"c":false}},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[236,-128]],"o":[[0,0],[-236,128]],"v":[[508,168],[20,36]],"c":false}},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[108,-220]],"c":false}},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[340,-264]],"c":false}},"nm":"Path 8","hd":false},{"ind":8,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[644,-444]],"c":false}},"nm":"Path 9","hd":false},{"ind":9,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[508,168],[700,-100]],"c":false}},"nm":"Path 10","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[115]},{"t":18,"s":[341]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.698039233685,0.215686291456,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.988235354424,0.341176480055,0.074509806931,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":32},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":3,"op":18,"st":3,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"smoke","refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[90]},{"t":60,"s":[0]}]},"p":{"a":0,"k":[1264,984,0]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[141,141,100]}},"ao":0,"w":512,"h":768,"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"03 fire small wide cycle end 2","refId":"comp_2","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[-0.376]},{"t":87,"s":[4.959]}]},"p":{"a":0,"k":[740,1512.8,0]},"a":{"a":0,"k":[256,512,0]}},"ao":0,"w":512,"h":512,"ip":40,"op":88,"st":40,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"smoke2_36 sek","refId":"comp_5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[90]},{"t":86,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[736,1400,0],"to":[0,0,0],"ti":[0,0,0]},{"t":86,"s":[736,880,0]}]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[-130,130,100]}},"ao":0,"w":512,"h":768,"ip":50,"op":86,"st":50,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[34.527,76.078],[108,-470]],"o":[[-118,-260],[-30.762,133.873]],"v":[[784,-84],[214,-262]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[0]},{"t":27,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[1,1,1,1]},{"t":27,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[32]},{"t":27,"s":[8]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[1,0.905882418156,0.298039227724,1]},{"t":27,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[48]},{"t":27,"s":[24]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":7,"op":27,"st":-73,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[3.598,83.469],[108,-470]],"o":[[-10,-232],[-30.762,133.873]],"v":[[756,84],[338,326]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":25,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[1,1,1,1]},{"t":25,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[32]},{"t":25,"s":[48]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[1,0.905882418156,0.298039227724,1]},{"t":25,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[48]},{"t":25,"s":[64]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":25,"st":-75,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[54.505,63.318],[-124,-722]],"o":[[-334,-388],[23.251,135.38]],"v":[[756,84],[-54,706]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[0]},{"t":22,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[1,1,1,1]},{"t":22,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[32]},{"t":22,"s":[64]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[1,0.905882418156,0.298039227724,1]},{"t":22,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[48]},{"t":22,"s":[80]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":2,"op":22,"st":-78,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 16","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[34.527,76.078],[108,-470]],"o":[[-118,-260],[-30.762,133.873]],"v":[[784,-84],[94,102]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1,1,1,1]},{"t":20,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[32]},{"t":20,"s":[48]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1,0.905882418156,0.298039227724,1]},{"t":20,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[48]},{"t":20,"s":[64]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":1,"op":21,"st":-80,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 18","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-51.827,65.529],[260,-30]],"o":[[174,-220],[-136.456,15.745]],"v":[[716,52],[426,-438]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[0]},{"t":45,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":26,"s":[1,1,1,1]},{"t":46,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[32]},{"t":45,"s":[8]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":26,"s":[1,0.905882418156,0.298039227724,1]},{"t":46,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[48]},{"t":45,"s":[24]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":26,"op":45,"st":-55,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-51.827,65.529],[182,-228]],"o":[[174,-220],[-85.694,107.353]],"v":[[920,-16],[710,-234]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":20,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1,1,1,1]},{"t":20,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[32]},{"t":20,"s":[8]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1,0.905882418156,0.298039227724,1]},{"t":20,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[48]},{"t":20,"s":[24]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":1,"op":20,"st":-80,"bm":0},{"ddd":0,"ind":13,"ty":0,"nm":"03 fire small wide1","refId":"comp_6","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":49,"s":[-4.281]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":58,"s":[0.51]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":62,"s":[3.655]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":89,"s":[-3.133]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":98,"s":[-5.791]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":113,"s":[-0.014]},{"t":142,"s":[0]}]},"p":{"a":0,"k":[1152,1488.8,0]},"a":{"a":0,"k":[256,512,0]}},"ao":0,"w":512,"h":512,"ip":49,"op":173,"st":49,"bm":0},{"ddd":0,"ind":14,"ty":0,"nm":"smoke2_36 sek 2","refId":"comp_9","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":131,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":144,"s":[90]},{"t":157,"s":[0]}]},"p":{"a":0,"k":[1176,1384,0]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[130,130,100]}},"ao":0,"w":512,"h":768,"ip":121,"op":157,"st":121,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Shape Layer 17","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[28.912,78.384],[204,-678]],"o":[[-90,-244],[-39.577,131.537]],"v":[[804,140],[290,690]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"t":40,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[1,1,1,1]},{"t":40,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[32]},{"t":40,"s":[64]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[1,0.905882418156,0.298039227724,1]},{"t":40,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[48]},{"t":40,"s":[80]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":20,"op":40,"st":-60,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"Shape Layer 12","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[28.912,78.384],[-96,-702]],"o":[[-90,-244],[18.611,136.095]],"v":[[900,148],[650,678]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[0]},{"t":49,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[1,1,1,1]},{"t":49,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[32]},{"t":49,"s":[64]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[1,0.905882418156,0.298039227724,1]},{"t":49,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[48]},{"t":49,"s":[80]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":29,"op":49,"st":-51,"bm":0},{"ddd":0,"ind":17,"ty":0,"nm":"02 fire middle","refId":"comp_10","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[5.472]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[1.181]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":52,"s":[3.12]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":86,"s":[-4.096]},{"t":107,"s":[0]}]},"p":{"a":0,"k":[372,1440,0]},"a":{"a":0,"k":[256,768,0]},"s":{"a":0,"k":[120,120,100]}},"ao":0,"w":512,"h":768,"ip":22,"op":142,"st":22,"bm":0},{"ddd":0,"ind":18,"ty":0,"nm":"smoke","refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":110,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":140,"s":[90]},{"t":160,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[416,1348,0],"to":[0,0,0],"ti":[0,0,0]},{"t":160,"s":[416,1060,0]}]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[145,145,100]}},"ao":0,"w":512,"h":768,"ip":100,"op":160,"st":100,"bm":0},{"ddd":0,"ind":19,"ty":0,"nm":"01 fire small 3","refId":"comp_18","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[-0.87]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":101,"s":[9.498]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":111,"s":[5.62]},{"t":131,"s":[0]}]},"p":{"a":0,"k":[744,1316,0]},"a":{"a":0,"k":[256,512,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":70,"op":162,"st":70,"bm":0},{"ddd":0,"ind":20,"ty":0,"nm":"smoke 2","refId":"comp_24","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":114,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":124,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[90]},{"t":174,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114,"s":[732,1248.798,0],"to":[0,0,0],"ti":[0,0,0]},{"t":174,"s":[732,1008.798,0]}]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[-120,120,100]}},"ao":0,"w":512,"h":768,"ip":114,"op":174,"st":114,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"Shape Layer 14","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[28.912,78.384],[64,-690]],"o":[[-90,-244],[-12.686,136.775]],"v":[[900,148],[298,506]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[0]},{"t":70,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[1,1,1,1]},{"t":70,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[32]},{"t":70,"s":[64]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[1,0.905882418156,0.298039227724,1]},{"t":70,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[48]},{"t":70,"s":[80]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":50,"op":70,"st":-30,"bm":0},{"ddd":0,"ind":22,"ty":0,"nm":"smoke 2","refId":"comp_24","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":71,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":81,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":111,"s":[90]},{"t":131,"s":[0]}]},"p":{"a":0,"k":[784,1070.383,0]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[120,120,100]}},"ao":0,"w":512,"h":768,"ip":71,"op":131,"st":71,"bm":0},{"ddd":0,"ind":23,"ty":0,"nm":"02 fire middle 3","refId":"comp_25","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[-0.766]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":42,"s":[5.499]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":55,"s":[3.823]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":71,"s":[5.884]},{"t":106,"s":[1.622]}]},"p":{"a":0,"k":[792,1144,0]},"a":{"a":0,"k":[256,768,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":25,"op":113,"st":25,"bm":0},{"ddd":0,"ind":24,"ty":0,"nm":"smoke2_36 sek 2","refId":"comp_9","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":98,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":121,"s":[90]},{"t":134,"s":[0]}]},"p":{"a":0,"k":[596,764,0]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":98,"op":134,"st":98,"bm":0},{"ddd":0,"ind":25,"ty":0,"nm":"01 fire small wide2","refId":"comp_26","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[5.76]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":86,"s":[-1.041]},{"t":144,"s":[4.242]}]},"p":{"a":0,"k":[593.436,820.784,0]},"a":{"a":0,"k":[256,512,0]},"s":{"a":0,"k":[-80,80,100]}},"ao":0,"w":512,"h":512,"ip":60,"op":152,"st":60,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":"Shape Layer 13","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[34.527,76.078],[108,-470]],"o":[[-118,-260],[-30.762,133.873]],"v":[[784,-84],[94,102]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[0]},{"t":60,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[1,1,1,1]},{"t":60,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[32]},{"t":60,"s":[48]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[1,0.905882418156,0.298039227724,1]},{"t":60,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[48]},{"t":60,"s":[64]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":40,"op":60,"st":-40,"bm":0},{"ddd":0,"ind":27,"ty":0,"nm":"03 fire small wide cycle end 2","refId":"comp_2","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[-3.983]},{"t":82,"s":[0]}]},"p":{"a":0,"k":[244,1056.8,0]},"a":{"a":0,"k":[256,512,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":35,"op":83,"st":35,"bm":0},{"ddd":0,"ind":28,"ty":0,"nm":"smoke2_36 sek 3","refId":"comp_27","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[90]},{"t":83,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":47,"s":[236,1004,0],"to":[0,0,0],"ti":[0,0,0]},{"t":83,"s":[236,732,0]}]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[110,110,100]}},"ao":0,"w":512,"h":768,"ip":47,"op":83,"st":47,"bm":0},{"ddd":0,"ind":29,"ty":0,"nm":"01 fire small","refId":"comp_28","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[-2.909]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[0.418]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":68,"s":[-6.58]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":80,"s":[-1.563]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":111,"s":[5.03]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":124,"s":[7.741]},{"t":135,"s":[0]}]},"p":{"a":0,"k":[1152.795,555.98,0]},"a":{"a":0,"k":[256,512,0]}},"ao":0,"w":512,"h":512,"ip":20,"op":176,"st":20,"bm":0},{"ddd":0,"ind":30,"ty":0,"nm":"smoke 2","refId":"comp_24","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":119,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":129,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":159,"s":[90]},{"t":179,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":147,"s":[1192,500,0],"to":[0,0,0],"ti":[0,0,0]},{"t":179,"s":[1192,636,0]}]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":119,"op":179,"st":119,"bm":0},{"ddd":0,"ind":31,"ty":0,"nm":"smoke2_36 sek 2","refId":"comp_9","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":144,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":167,"s":[90]},{"t":180,"s":[0]}]},"p":{"a":0,"k":[296,644,0]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":144,"op":180,"st":144,"bm":0},{"ddd":0,"ind":32,"ty":0,"nm":"02 fire middle 2","refId":"comp_29","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":28,"s":[1.238]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[-3.101]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":54,"s":[-8.156]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":66,"s":[-5.302]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":89,"s":[4.013]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":107,"s":[8.136]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":116,"s":[4.606]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":131,"s":[-7.186]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":141,"s":[-11.857]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[-7.113]},{"t":165,"s":[5.607]}]},"p":{"a":0,"k":[292.283,677.379,0]},"a":{"a":0,"k":[256,768,0]},"s":{"a":0,"k":[80,80,100]}},"ao":0,"w":512,"h":768,"ip":28,"op":180,"st":28,"bm":0},{"ddd":0,"ind":33,"ty":0,"nm":"smoke2_36 sek","refId":"comp_5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":63,"s":[90]},{"t":76,"s":[0]}]},"p":{"a":0,"k":[576,848,0]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":40,"op":76,"st":40,"bm":0},{"ddd":0,"ind":34,"ty":0,"nm":"03 fire small wide cycle end 2","refId":"comp_2","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0.759]},{"t":67,"s":[1.63]}]},"p":{"a":0,"k":[564,920.8,0]},"a":{"a":0,"k":[256,512,0]},"s":{"a":0,"k":[70,70,100]}},"ao":0,"w":512,"h":512,"ip":20,"op":68,"st":20,"bm":0},{"ddd":0,"ind":35,"ty":0,"nm":"02 fire middle 3","refId":"comp_25","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":27,"s":[-0.899]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":41,"s":[-5.017]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":85,"s":[0.569]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":97,"s":[4.675]},{"t":108,"s":[0.39]}]},"p":{"a":0,"k":[657.596,487.985,0]},"a":{"a":0,"k":[256,768,0]},"s":{"a":0,"k":[50,50,100]}},"ao":0,"w":512,"h":768,"ip":27,"op":115,"st":27,"bm":0},{"ddd":0,"ind":36,"ty":4,"nm":"Shape Layer 19","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[3.598,83.469],[184,-738]],"o":[[-10,-232],[-33.23,133.282]],"v":[[756,84],[-222,246]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0]},{"t":35,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[1,1,1,1]},{"t":35,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[32]},{"t":35,"s":[48]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[1,0.905882418156,0.298039227724,1]},{"t":35,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[48]},{"t":35,"s":[64]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":15,"op":35,"st":-65,"bm":0},{"ddd":0,"ind":37,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"p":{"a":0,"k":[527.164,1021,0]},"a":{"a":0,"k":[77.164,253,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[3.598,83.469],[496,-534]],"o":[[-10,-232],[-93.483,100.645]],"v":[[756,84],[-206,-122]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[0]},{"t":28,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[1,1,1,1]},{"t":28,"s":[1,0.905882418156,0.298039227724,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[32]},{"t":28,"s":[48]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[1,0.905882418156,0.298039227724,1]},{"t":28,"s":[0.992156922817,0.321568638086,0.078431375325,1]}]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[48]},{"t":28,"s":[64]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":8,"op":28,"st":-72,"bm":0},{"ddd":0,"ind":38,"ty":0,"nm":"03 fire small wide cycle end 2","refId":"comp_2","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[-1.396]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":51,"s":[0.803]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":56,"s":[2.307]},{"t":92,"s":[0.387]}]},"p":{"a":0,"k":[900,364.8,0]},"a":{"a":0,"k":[256,512,0]},"s":{"a":0,"k":[70,70,100]}},"ao":0,"w":512,"h":512,"ip":45,"op":93,"st":45,"bm":0},{"ddd":0,"ind":39,"ty":0,"nm":"smoke 3","refId":"comp_30","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":80,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":90,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":127,"s":[90]},{"t":140,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[662,458,0],"to":[0,0,0],"ti":[0,0,0]},{"t":140,"s":[662,554,0]}]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[-90,90,100]}},"ao":0,"w":512,"h":768,"ip":80,"op":140,"st":80,"bm":0},{"ddd":0,"ind":40,"ty":0,"nm":"smoke2_36 sek 2","refId":"comp_9","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":52,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":62,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":75,"s":[90]},{"t":88,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":52,"s":[916,324,0],"to":[0,0,0],"ti":[0,0,0]},{"t":88,"s":[916,576,0]}]},"a":{"a":0,"k":[276,668,0]},"s":{"a":0,"k":[90,90,100]}},"ao":0,"w":512,"h":768,"ip":52,"op":88,"st":52,"bm":0},{"ddd":0,"ind":41,"ty":0,"nm":"smoke verkh","refId":"comp_31","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":79,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":139,"s":[90]},{"t":180,"s":[0]}]},"p":{"a":0,"k":[768,768,0]},"a":{"a":0,"k":[768,768,0]}},"ao":0,"w":1536,"h":1536,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":42,"ty":0,"nm":"nakal4","refId":"comp_32","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":90,"s":[100]},{"t":166,"s":[0]}]},"p":{"a":0,"k":[768,768,0]},"a":{"a":0,"k":[768,768,0]}},"ao":0,"w":1536,"h":1536,"ip":0,"op":180,"st":0,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","parent":2,"sr":1,"ks":{"p":{"a":0,"k":[-77.334,-175.69,0]},"a":{"a":0,"k":[-77.334,-175.69,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.988,3.181],[-19.8,-5.052]],"o":[[-4.889,-7.822],[26.133,6.668]],"v":[[19.329,271.399],[24.3,294.213]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[4.186,6.698],[-41.687,-10.636]],"o":[[-10.293,-16.469],[55.022,14.038]],"v":[[7.834,173.427],[18.299,221.462]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[14.361,7.522],[-30.261,-6.497]],"o":[[-24.248,-12.701],[34.72,7.454]],"v":[[35.139,128.628],[12.401,185.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[32.578,61.344],[28.072,72.523]],"c":true}]},{"t":31,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[20.578,-416.656],[16.072,-405.477]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.795,-2.463],[-22.463,0.136]],"o":[[-6.751,9.267],[21.909,-0.132]],"v":[[28.78,287.216],[26.463,308]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[6.778,-9.304],[-84.843,0.513]],"o":[[-25.5,35],[82.75,-0.5]],"v":[[35,213],[26.25,291.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[41.584,-3.199],[-34.387,-1.279]],"o":[[-37.621,2.894],[34.387,1.279]],"v":[[41.616,212.199],[44.975,259.221]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[9.857,-0.643],[-6.643,-0.429]],"o":[[-9.65,0.629],[6.643,0.429]],"v":[[38.138,192.643],[39.103,203.571]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.286,-0.214],[-2.214,-0.143]],"o":[[-3.217,0.21],[2.214,0.143]],"v":[[21.707,90.214],[22.029,93.857]],"c":true}]},{"t":41,"s":[{"i":[[3.286,-0.214],[-2.214,-0.143]],"o":[[-3.217,0.21],[2.214,0.143]],"v":[[19.707,-427.786],[20.029,-424.143]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[33.144,5.233],[-24.721,-0.221]],"o":[[-35.022,-5.53],[27.911,0.249]],"v":[[19.555,225.935],[15.319,283.751]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[29.02,-23.268],[-29.148,15.285]],"o":[[-36.827,29.528],[27.206,-14.266]],"v":[[18.827,100.658],[25.794,178.294]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[36.864,-1.646],[-34.889,28.965]],"o":[[-35.938,1.604],[46.08,13.495]],"v":[[1.709,52.646],[-0.595,87.535]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.216,-6.659],[-24.954,25.786]],"o":[[-28.004,9.704],[30.5,0]],"v":[[16.732,-8.349],[24.773,17.714]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[12.002,-2.4],[-12.348,2.646]],"o":[[-16.253,3.251],[13.442,-2.88]],"v":[[25.798,-28.588],[29.158,-15.146]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":51,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[47.81,-67.072],[-25.632,9.313]],"o":[[-93.246,48.698],[25.632,-9.313]],"v":[[-74.754,121.302],[-13.132,182.313]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[16.302,-91.514],[22.297,-55.155]],"o":[[-51.846,41.486],[63.795,-25.293]],"v":[[-77.654,-43.486],[-14.297,37.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[65.508,-110.844],[-62.172,37.016]],"o":[[-46.719,4.672],[111.508,-8.984]],"v":[[-77.508,-81.156],[-31.508,-13.016]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[44.392,-34.904],[-43.656,10.635]],"o":[[-57.534,8.63],[43.656,-10.635]],"v":[[-64.892,-151.596],[-43.156,-90.865]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[45.056,-54.944],[-36.611,29.426]],"o":[[-75.944,-2.944],[74.944,33.426]],"v":[[-55.556,-184.556],[-45.389,-150.426]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[14.5,-3],[-14.25,5.5]],"o":[[-14.5,3],[14.25,-5.5]],"v":[[-44.875,-250.5],[-40.375,-234]],"c":true}]},{"t":60,"s":[{"i":[[1.16,-0.24],[-1.14,0.44]],"o":[[-1.16,0.24],[1.14,-0.44]],"v":[[-45.92,-278.26],[-45.56,-276.94]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[38.071,2.586],[-51.674,3.841],[-14.328,5.136]],"o":[[-71.639,-4.866],[8.07,11.41],[22.966,-8.233]],"v":[[122.074,242.366],[89.943,278.535],[130.995,288.864]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[20.869,-12.454],[-146.79,82.022],[-12.062,30.104]],"o":[[-112.454,-96.052],[15.663,4.937],[27.265,-8.079]],"v":[[157.954,39.052],[123.79,100.978],[169.735,69.137]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[74.55,-67.464],[-92.612,89.3],[0.78,32.797]],"o":[[-175.95,-43.988],[-28.612,-55.2],[57.28,16.797]],"v":[[109.45,-30.036],[99.612,30.2],[150.72,14.703]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[79.321,-50.136],[-20.934,-21.241],[-26.364,9.073]],"o":[[-58.179,-42.136],[17.914,18.177],[76.169,29.621]],"v":[[102.679,-76.364],[46.086,-27.677],[111.331,-23.621]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[62.617,-21.138],[-8.337,-17.814],[-11.971,0.122]],"o":[[-29.444,-9.815],[3.681,7.865],[67.802,31.907]],"v":[[103.66,-125.362],[70.352,-89.569],[93.689,-75.907]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[19.334,0],[-4.841,-5.763],[-4.486,0.179]],"o":[[-13.412,0],[2.137,2.544],[14.647,-0.586]],"v":[[106.839,-168.5],[96.133,-153.01],[106.253,-148.873]],"c":true}]},{"t":60,"s":[{"i":[[5.5,0],[-1.377,-1.639],[-1.276,0.051]],"o":[[-3.815,0],[0.608,0.724],[4.167,-0.167]],"v":[[103.5,-198.125],[100.454,-193.718],[103.333,-192.542]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[29.237,-0.424],[18.659,-25.84],[-78.306,-8.932],[-2.4,-26.987],[0.564,-42.682],[30.491,12.722]],"o":[[-3.4,-14.426],[-41.473,-22.64],[-12.806,-41.432],[1.048,11.781],[27.835,19.62],[5.813,-25.223]],"v":[[106.649,132.4],[49.973,125.64],[49.306,238.932],[79.9,226.987],[116.436,225.182],[131.993,185.252]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[18.356,-65.631],[1.004,-68.279],[-92.58,7.072],[-16.188,1.28],[-17.016,-0.736],[29.057,17.998]],"o":[[12.356,-49.631],[-116.996,-73.279],[18.803,-1.436],[15.763,-1.246],[26.568,1.15],[25.666,-30.324]],"v":[[114.644,-62.369],[48.996,-92.721],[50.08,17.428],[99.158,-10.713],[145.932,9.85],[160.334,-37.676]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[40.408,-8.621],[8.905,-22.866],[-63.43,19.052],[-2.553,-27.854],[-0.664,-27.112],[37.995,7.577]],"o":[[-15.642,-10.077],[-92.499,-21.673],[-3,-37.948],[6.447,-39.854],[25.263,14.209],[-3.038,-25.183]],"v":[[102.472,-151.481],[53.507,-145.134],[65,-51.052],[106.553,-41.146],[153.664,-66.888],[163.996,-115.133]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[25.979,-38.681],[-12.816,-22.414],[-71.606,9.11],[-7.652,-23.023],[-8.541,18],[3.417,17.237]],"o":[[6.479,-39.181],[-74.816,-41.414],[-4.106,-29.39],[7.848,-31.023],[7.401,-15.598],[-5.628,-28.395]],"v":[[80.521,-174.319],[39.316,-172.086],[38.606,-82.61],[103.652,-89.977],[145.259,-114.137],[152.031,-167.148]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[35.852,-24.204],[6.121,-6.787],[-30.147,-5.617],[-12.364,11.19],[-4.863,14.486],[5.544,11.648]],"o":[[-15.19,-0.044],[-18.957,21.021],[11.029,2.055],[17.765,0.658],[4.214,-12.553],[-9.133,-19.188]],"v":[[66.426,-225.746],[34.942,-214.601],[66.05,-144.055],[101.841,-156.26],[135.809,-181.382],[133.83,-220.533]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[11.188,-2.48],[2.145,-2.133],[-8.537,-4.421],[-3.25,0.288],[-2.088,4.234],[1.472,3.752]],"o":[[-5.164,1.145],[-6.643,6.608],[3.123,1.618],[6.515,-0.577],[1.81,-3.669],[-2.424,-6.18]],"v":[[99.368,-273.52],[88.579,-268.455],[96.629,-247.5],[106.444,-245.289],[119.333,-253.457],[119.83,-265.206]],"c":true}]},{"t":60,"s":[{"i":[[2.469,0.044],[0.542,-0.415],[-1.676,-1.418],[-0.709,0.023],[-0.595,0.897],[0.16,0.894]],"o":[[-1.158,-0.021],[-1.68,1.285],[0.613,0.519],[1.347,-0.043],[0.516,-0.777],[-0.264,-1.473]],"v":[[96.518,-299.069],[93.999,-298.426],[94.96,-292.841],[96.99,-292.023],[99.944,-293.62],[100.503,-296.249]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-3.499,-24.848],[-6.884,-16.478],[-0.301,7.46],[-4.644,0.228],[-25.003,12.782],[33.341,-4.053]],"o":[[-34.078,22.449],[3.276,7.842],[1.294,-32.082],[19.012,20.499],[85.358,33.282],[-6.489,-19.362]],"v":[[-102.261,200.848],[-133.404,262.516],[-118.294,281.082],[-102.012,287.001],[-31.997,296.218],[-11.544,205.996]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[-0.747,-48.874],[-2.455,-35.731],[-40.679,19.746],[-10.189,-28.494],[-11.643,4.734],[85.039,22.458]],"o":[[-21.211,-29.874],[-35.532,22.641],[-1.215,-25.539],[22.811,19.506],[80.357,20.734],[29.075,-42.399]],"v":[[-100.253,19.874],[-169.932,40.216],[-144.785,98.539],[-93.811,115.494],[-40.357,117.266],[-14.075,40.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[4.596,-2.157],[-2.838,-33.101],[-23.392,21.782],[64.52,-34.091],[-7.412,7.454],[7.166,18.65]],"o":[[-43.115,-41.195],[-25.698,17.837],[-20.978,-41.699],[20.24,22.409],[13.576,-13.654],[-5.083,-13.23]],"v":[[-78.36,-37.305],[-165.347,-14.561],[-142.022,35.199],[-94.52,47.591],[-39.74,43.428],[-28.273,-12.423]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.586,24.077],[1.481,-16.058],[-4.9,-5.507],[-10.179,0.727],[8.719,-39.971],[89.45,-41.348]],"o":[[-19.447,-7.56],[-0.705,7.642],[4.542,5.105],[-3.153,-23.324],[83.219,35.029],[-14.55,-40.348]],"v":[[-113.586,-84.077],[-147.276,-60.135],[-141.231,-39.204],[-119.347,-31.676],[-66.219,-26.529],[-64.45,-76.152]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[13.764,-16.52],[-0.797,-14.42],[-5.435,-4.397],[-9.222,1.812],[-4.425,5.611],[7.725,10.732]],"o":[[-22.868,-5.87],[0.379,6.863],[5.039,4.077],[14.57,16.763],[8.105,-10.278],[-5.48,-7.613]],"v":[[-80.463,-124.48],[-113.116,-101.094],[-104.345,-83.066],[-82.915,-78.763],[-39.977,-80.996],[-37.614,-117.588]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[9.081,-4.12],[1.941,-4.856],[-0.074,-2.217],[-1.433,-1.373],[-2.412,2.764],[3.877,5.235]],"o":[[-4.629,2.1],[-0.924,2.311],[0.069,2.056],[11.907,11.408],[4.417,-5.063],[-2.75,-3.714]],"v":[[-75.274,-166.527],[-85.585,-154.758],[-86.926,-147.859],[-85.155,-142.261],[-54.718,-143.901],[-53.648,-161.404]],"c":true}]},{"t":60,"s":[{"i":[[0.601,-0.289],[0.013,-0.847],[-0.221,-0.37],[-0.364,-0.269],[-0.704,0.652],[0.934,1.027]],"o":[[-1.307,0.628],[-0.006,0.403],[0.205,0.343],[0.87,0.643],[1.289,-1.194],[-0.662,-0.729]],"v":[[-79.613,-181.42],[-81.494,-179.083],[-81.161,-177.91],[-80.299,-176.982],[-73.89,-177.455],[-73.114,-181.387]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392216701,0.972549079446,0.949019667682,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[280.666,670.31,0]},"a":{"a":0,"k":[24.666,286.31,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[20,20,100]},{"t":20,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.988,3.181],[-19.8,-5.052]],"o":[[-4.889,-7.822],[26.133,6.668]],"v":[[19.329,271.399],[24.3,294.213]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[6.101,9.761],[-60.75,-15.5]],"o":[[-15,-24],[80.183,20.458]],"v":[[0,163],[15.25,233]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[21,11],[-44.25,-9.5]],"o":[[-35.458,-18.573],[50.771,10.9]],"v":[[35,125],[1.75,207.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[32.578,61.344],[28.072,72.523]],"c":true}]},{"t":31,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[20.578,-416.656],[16.072,-405.477]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.795,-2.463],[-22.463,0.136]],"o":[[-6.751,9.267],[21.909,-0.132]],"v":[[28.78,287.216],[26.463,308]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[6.778,-9.304],[-84.843,0.513]],"o":[[-25.5,35],[82.75,-0.5]],"v":[[35,213],[26.25,291.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[65,-5],[-53.75,-2]],"o":[[-58.805,4.523],[53.75,2]],"v":[[34,209.5],[39.25,283]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[23,-1.5],[-15.5,-1]],"o":[[-22.516,1.468],[15.5,1]],"v":[[35.5,190.5],[37.75,216]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.286,-0.214],[-2.214,-0.143]],"o":[[-3.217,0.21],[2.214,0.143]],"v":[[21.707,90.214],[22.029,93.857]],"c":true}]},{"t":41,"s":[{"i":[[3.286,-0.214],[-2.214,-0.143]],"o":[[-3.217,0.21],[2.214,0.143]],"v":[[19.707,-427.786],[20.029,-424.143]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[66.5,10.5],[-49.599,-0.443]],"o":[[-70.267,-11.095],[56,0.5]],"v":[[20.5,199.5],[12,315.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[43.734,-35.066],[-43.928,23.035]],"o":[[-55.5,44.5],[41,-21.5]],"v":[[15.5,72.5],[26,189.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[56,-2.5],[-53,44]],"o":[[-54.592,2.437],[70,20.5]],"v":[[-4.5,51.5],[-8,104.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[34.653,-12.008],[-45,46.5]],"o":[[-50.5,17.5],[55,0]],"v":[[9.5,-11.5],[24,35.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[25,-5],[-25.722,5.512]],"o":[[-33.855,6.771],[28,-6]],"v":[[23,-29],[30,-1]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":51,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[69.5,-97.5],[-113.5,45]],"o":[[-66.5,8.5],[86.092,-34.134]],"v":[[-90.5,124.5],[-22,227]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[22,-123.5],[-113.5,45]],"o":[[-41.5,7],[86.092,-34.134]],"v":[[-91.5,-56.5],[-6,53]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[29.5,-125],[-86.5,51.5]],"o":[[-65,6.5],[164.5,11.5]],"v":[[-87.5,-87],[-23.5,-7.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[71,-76.5],[-34.5,28]],"o":[[-90,13.5],[106.5,47.5]],"v":[[-68.5,-157.5],[-34.5,-62.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[48.5,-66],[-34.5,28]],"o":[[-81,3],[106.5,47.5]],"v":[[-57.5,-194],[-53,-132]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[29,-6],[-28.5,11]],"o":[[-29,6],[28.5,-11]],"v":[[-46,-251.5],[-37,-218.5]],"c":true}]},{"t":60,"s":[{"i":[[1.16,-0.24],[-1.14,0.44]],"o":[[-1.16,0.24],[1.14,-0.44]],"v":[[-45.92,-278.26],[-45.56,-276.94]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[70.414,4.783],[-95.574,7.104],[-26.5,9.5]],"o":[[-132.5,-9],[14.926,21.104],[42.478,-15.228]],"v":[[116,239],[56.574,305.896],[132.5,325]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[31,-18.5],[-168.751,36.203],[-17.918,44.718]],"o":[[-248,-61.5],[23.267,7.334],[40.5,-12]],"v":[[167,24],[116.251,135.297],[184.5,88]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[101,-20.5],[-133.016,57.816],[-23.678,31.225]],"o":[[-222,-55.5],[17.83,12.614],[53,-13]],"v":[[114.5,-57.5],[94.516,65.184],[159,45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[108,-21],[-25.045,-34.694],[-37.826,13.017]],"o":[[-46.826,-19.424],[11.058,15.318],[78.5,26]],"v":[[96,-99.5],[40.628,-7.406],[112,2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[78.5,-26.5],[-10.452,-22.332],[-15.008,0.153]],"o":[[-36.913,-12.304],[4.615,9.86],[85,40]],"v":[[101.5,-126.5],[59.744,-81.628],[89,-64.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[33,0],[-8.262,-9.837],[-7.657,0.306]],"o":[[-22.893,0],[3.648,4.343],[25,-1]],"v":[[103,-170],[84.726,-143.561],[102,-136.5]],"c":true}]},{"t":60,"s":[{"i":[[5.5,0],[-1.377,-1.639],[-1.276,0.051]],"o":[[-3.815,0],[0.608,0.724],[4.167,-0.167]],"v":[[103.5,-198.125],[100.454,-193.718],[103.333,-192.542]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[40.224,-0.584],[25.671,-35.551],[-117.344,4.076],[-13.845,-2.09],[-15.219,20.222],[41.95,17.503]],"o":[[-4.678,-19.848],[-63.829,-76.551],[7.749,18.334],[16.09,2.429],[38.296,26.994],[7.997,-34.703]],"v":[[108.178,121.348],[34.329,117.551],[25.844,278.924],[70,279],[118.204,260.006],[148.55,186.497]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[42.564,-6.226],[40.813,-50.225],[-85.297,31.262],[-32.063,3.791],[-15.43,23.796],[36.231,22.442]],"o":[[-6.076,-18.138],[-120.187,-27.225],[8.47,17.96],[23.983,5.161],[35.183,9.711],[3.616,-40.885]],"v":[[125.779,-104.633],[37.687,-123.775],[35.297,47.238],[94,72],[154.817,37.789],[175.269,-33.942]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[50.807,-10.84],[11.197,-28.751],[-79.754,23.956],[-21.881,6.283],[-12.991,22.817],[47.774,9.527]],"o":[[-19.668,-12.67],[-116.303,-27.251],[12.896,12.677],[24.971,6.064],[31.765,17.866],[-3.819,-31.664]],"v":[[94.869,-161.73],[33.303,-153.749],[47.754,-35.456],[100,-23],[159.235,-55.366],[172.226,-116.027]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[61.33,-27.94],[13.035,-11.794],[-88,34.498],[-24.613,14.871],[-11.285,23.783],[4.514,22.775]],"o":[[-22.734,-8.189],[-81.69,0.79],[16.437,2.441],[24.287,7.59],[9.779,-20.609],[-7.436,-37.518]],"v":[[69.474,-198.525],[15.69,-190.29],[42.5,-42.998],[104,-60],[158.974,-91.922],[167.923,-161.964]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[49.628,-33.504],[8.473,-9.395],[-41.73,-7.776],[-17.115,15.49],[-6.732,20.052],[7.675,16.124]],"o":[[-21.027,-0.06],[-26.241,29.098],[15.267,2.845],[24.591,0.911],[5.833,-17.376],[-12.642,-26.561]],"v":[[48.978,-222.185],[5.396,-206.758],[48.457,-109.106],[98,-126],[145.019,-160.775],[142.281,-214.968]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[20.595,-4.565],[3.948,-3.927],[-15.714,-8.139],[-5.982,0.53],[-3.844,7.794],[2.709,6.906]],"o":[[-9.506,2.107],[-12.229,12.163],[5.749,2.978],[11.992,-1.063],[3.331,-6.754],[-4.463,-11.376]],"v":[[87.975,-274.467],[68.116,-265.142],[82.934,-226.569],[101,-222.5],[124.726,-237.535],[125.642,-259.162]],"c":true}]},{"t":60,"s":[{"i":[[4.312,0.077],[0.947,-0.725],[-2.927,-2.476],[-1.238,0.04],[-1.039,1.566],[0.28,1.562]],"o":[[-2.023,-0.036],[-2.933,2.244],[1.071,0.906],[2.353,-0.076],[0.9,-1.357],[-0.461,-2.572]],"v":[[95.675,-299.303],[91.277,-298.18],[92.954,-288.429],[96.5,-287],[101.657,-289.789],[102.633,-294.38]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-4.046,-28.733],[-7.961,-19.055],[-7.058,-4.972],[-5.37,0.263],[-10.383,14.21],[38.553,-4.686]],"o":[[-39.406,25.959],[3.789,9.068],[6.543,4.609],[20.166,40.644],[98.704,38.486],[-7.504,-22.39]],"v":[[-106.454,196.733],[-142.466,268.042],[-124.994,289.511],[-106.166,296.356],[-25.204,307.014],[-1.553,202.686]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[7.754,-10.103],[-2.864,-41.686],[-47.459,23.038],[-17.819,6.695],[-11.001,13.349],[99.213,26.201]],"o":[[-24.746,-34.853],[-41.454,26.415],[10.089,11.703],[9.571,21.591],[45.708,87.19],[-6.546,-22.901]],"v":[[-105.754,6.853],[-187.046,30.585],[-146.041,114.963],[-104.071,125.409],[-34.708,136.81],[-5.213,30.799]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[5.648,-2.651],[-3.488,-40.68],[-28.748,26.769],[-22.563,7.933],[-9.109,9.161],[8.807,22.921]],"o":[[-52.986,-50.627],[-31.582,21.921],[15.404,15.153],[24.874,27.539],[16.685,-16.781],[-6.247,-16.259]],"v":[[-65.014,-36.373],[-171.918,-8.421],[-143.252,52.731],[-84.874,67.961],[-17.551,62.845],[-3.459,-5.795]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.861,-24.879],[2.389,-25.903],[-7.904,-8.883],[-16.419,1.173],[-8.619,13.394],[50.602,-9.162]],"o":[[-31.37,-12.195],[-1.137,12.327],[7.327,8.235],[20.153,51.095],[59.455,-7.707],[-9.228,-15.356]],"v":[[-114.361,-87.621],[-168.705,-49.001],[-158.954,-15.239],[-123.653,-3.095],[-37.955,5.207],[-35.102,-74.838]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[20.907,-25.093],[-1.21,-21.904],[-8.256,-6.68],[-14.008,2.753],[-6.721,8.523],[11.735,16.301]],"o":[[-34.736,-8.916],[0.576,10.424],[7.654,6.192],[22.131,25.463],[12.311,-15.612],[-8.324,-11.564]],"v":[[-85.407,-125.907],[-135.006,-90.384],[-121.683,-62.999],[-89.131,-56.463],[-23.909,-59.855],[-20.32,-115.438]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[16.089,-7.299],[3.439,-8.602],[-0.131,-3.928],[-2.539,-2.432],[-4.273,4.897],[6.868,9.275]],"o":[[-8.201,3.721],[-1.637,4.094],[0.122,3.642],[21.095,20.211],[7.826,-8.97],[-4.872,-6.579]],"v":[[-77.089,-167.201],[-95.356,-146.351],[-97.732,-134.128],[-94.595,-124.211],[-40.671,-127.117],[-38.776,-158.124]],"c":true}]},{"t":60,"s":[{"i":[[1.463,-0.702],[0.031,-2.062],[-0.538,-0.9],[-0.887,-0.655],[-1.713,1.587],[2.273,2.5]],"o":[[-3.182,1.528],[-0.015,0.981],[0.498,0.834],[2.118,1.565],[3.138,-2.907],[-1.612,-1.773]],"v":[[-78.589,-181.314],[-83.167,-175.626],[-82.357,-172.772],[-80.258,-170.512],[-64.659,-171.663],[-62.771,-181.234]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.945098099054,0.90588241278,0.792156922583,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.917647118662,0.847058883368,0.721568627451,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":60,"st":0,"bm":0}]},{"id":"comp_2","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 12","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[256,495,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[40,40,100]},{"t":8,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":28,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"core","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[100]},{"t":20,"s":[0]}]},"p":{"a":0,"k":[4.85,-0.729,0]},"a":{"a":0,"k":[-51.438,222.044,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[18.197,16.546],[-41.985,0.511],[69.85,68.547]],"o":[[-53.725,57.166],[41.194,-0.501],[-14.905,76.809]],"v":[[-60.485,-15.773],[2.147,99.863],[27.617,-48.036]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[0.068,-0.429],[-35.745,0.435],[65.918,79.024]],"o":[[-55.611,56.567],[35.071,-0.427],[-0.687,-0.216]],"v":[[-9.918,-30.798],[-3.59,87.929],[-8.638,-29.958]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0.069,-0.44],[-31.286,-32.594],[40.368,45.715]],"o":[[-28.033,10.909],[28.162,-20.704],[-0.705,-0.222]],"v":[[18.029,13.22],[1.286,91.311],[19.344,14.083]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0.086,-0.545],[-13.305,-8.903],[-4.886,23.805]],"o":[[-34.696,13.502],[52.246,-21.612],[-0.872,-0.275]],"v":[[0.536,-65.183],[9.914,2.038],[2.164,-64.114]],"c":true}]},{"t":20,"s":[{"i":[[0.128,-0.814],[-10.891,-11.311],[27.696,1.589]],"o":[[20.128,26.186],[43.109,-11.311],[-1.304,-0.411]],"v":[[11.872,-133.186],[21.891,-73.689],[14.304,-131.589]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.121,0.996,0.996,0.898,0.54,0.998,0.951,0.598,0.808,1,0.906,0.298]}},"s":{"a":0,"k":[2.235,29.653]},"e":{"a":0,"k":[6.469,-84.419]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 4","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":20,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"second","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[100]},{"t":20,"s":[0]}]},"p":{"a":0,"k":[2,-97,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[112.946,110.839]],"o":[[-86.872,92.436],[66.609,-0.811],[-1.054,155.839]],"v":[[-75.128,-84.436],[1.891,99.311],[22.054,-139.839]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[0.128,-0.814],[-67.889,0.826],[125.196,150.089]],"o":[[-105.622,107.436],[66.609,-0.811],[-1.304,-0.411]],"v":[[-13.128,-140.186],[-1.109,85.311],[-10.696,-138.589]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0.128,-0.814],[-57.891,-60.311],[74.696,84.589]],"o":[[-51.872,20.186],[52.109,-38.311],[-1.304,-0.411]],"v":[[33.872,-53.186],[2.891,91.311],[36.304,-51.589]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0.128,-0.814],[-19.891,-13.311],[-7.304,35.589]],"o":[[-51.872,20.186],[78.109,-32.311],[-1.304,-0.411]],"v":[[-2.128,-99.186],[11.891,1.311],[0.304,-97.589]],"c":true}]},{"t":20,"s":[{"i":[[0.128,-0.814],[-10.891,-11.311],[27.696,1.589]],"o":[[20.128,26.186],[43.109,-11.311],[-1.304,-0.411]],"v":[[11.872,-133.186],[21.891,-73.689],[14.304,-131.589]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.914,0.239,0.269,1,0.739,0.22,1,1,0.565,0.2]}},"s":{"a":0,"k":[1.76,83.087]},"e":{"a":0,"k":[-8.18,-233.581]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":20,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"main 2","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[0,-111,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-49.509,44.388],[123.533,-38.986]],"o":[[-17.509,54.388],[-101.467,-137.986]],"v":[[138.509,-106.388],[72.467,102.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-54.652,117.814],[96.514,-88.952]],"o":[[39.848,36.814],[-55.486,-82.702]],"v":[[122.902,-122.064],[135.986,59.452]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-69.795,121.239],[25.494,-72.919]],"o":[[-6.795,25.239],[-9.506,-27.419]],"v":[[118.295,-144.739],[157.506,-8.081]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-47.393,78.823],[5.657,-37.203]],"o":[[-20.938,53.232],[-20.343,-15.203]],"v":[[125.938,-167.732],[170.343,-77.797]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-14.581,12.011],[1.034,-14.274]],"o":[[-8.51,26.743],[-24.109,4.012]],"v":[[132.081,-191.511],[118.466,-138.726]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[-8.189,4.595],[5.817,-6.951]],"o":[[0.811,6.877],[-3.683,-1.451]],"v":[[129.689,-209.345],[131.183,-194.049]],"c":true}]},{"t":20,"s":[{"i":[[-8.189,4.595],[5.817,-6.951]],"o":[[0.811,6.877],[-3.683,-1.451]],"v":[[139.689,-425.345],[141.183,-410.049]],"c":true}]}]},"nm":"Path 10","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[116.786,0],[11,144.667]],"o":[[-317,-127.726],[249,149.667]],"v":[[-1,113.726],[-1,-153.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[120.519,-22.794],[-202.241,204.246]],"o":[[-143.695,27.178],[23.759,129.246]],"v":[[0.481,97.794],[0.241,-202.246]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[168.037,-36.863],[3.518,139.825]],"o":[[-193.963,-76.863],[211.518,139.825]],"v":[[1.963,81.863],[-26.518,-251.825]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[78.037,-55.931],[-63.722,35.404]],"o":[[-42.963,-53.931],[-21.722,38.404]],"v":[[2.963,-132.069],[27.722,-295.404]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0.866,0],[-1.78,1.414]],"o":[[-1.004,0],[1.908,1.516]],"v":[[3.963,-346],[3.963,-347.983]],"c":true}]},{"t":17,"s":[{"i":[[0.866,0],[-1.78,1.414]],"o":[[-1.004,0],[1.908,1.516]],"v":[[25.963,-410],[25.963,-411.983]],"c":true}]}]},"nm":"Path 13","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[150.97,-29.436],[-7.904,50.282],[-29.987,22.93],[31.343,22.513]],"o":[[-171.03,-96.436],[20.614,30.124],[20.013,43.93],[96.97,73.457]],"v":[[0.03,113.436],[-84.471,-110.907],[-1.013,-82.93],[77.03,-95.457]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[263.97,-70.436],[41.784,65.595],[-24.463,-11.448],[-17.438,27.049]],"o":[[-288.03,-110.436],[28.054,20.833],[28.268,13.229],[-17.53,86.457]],"v":[[0.03,113.436],[-88.471,-144.407],[-8.648,-91.164],[61.53,-104.457]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[150.97,-29.436],[-32.029,33.907],[-26.789,24.397],[-10.737,32.946]],"o":[[-259.03,-106.436],[23.879,14.802],[25.211,25.397],[-2.03,120.457]],"v":[[0.03,113.436],[-60.471,-110.407],[27.789,-112.897],[118.03,-147.457]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[182.97,-93.436],[-9.029,35.657],[0,0],[-0.28,-0.293]],"o":[[-109.03,-114.436],[-0.029,0.657],[0,0],[30.72,63.707]],"v":[[0.03,113.436],[31.029,-60.657],[31.387,-61.673],[31.28,-60.707]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[122.605,-62.61],[-52.544,10.197],[0,0],[-0.187,-0.196]],"o":[[-64.272,-70.959],[-0.019,0.44],[0,0],[-33.712,34.231]],"v":[[10.772,31.459],[11.544,-98.197],[11.784,-98.878],[11.712,-98.231]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[62.24,-31.783],[36.94,34.738],[0,0],[-0.095,-0.1]],"o":[[-19.515,-27.482],[-0.01,0.223],[0,0],[53.855,13.755]],"v":[[21.515,-50.518],[-7.94,-135.738],[-7.818,-136.083],[-7.855,-135.755]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[16.966,-8.664],[-0.837,3.306],[0,0],[-0.026,-0.027]],"o":[[-10.11,-10.611],[-0.003,0.061],[0,0],[2.849,5.907]],"v":[[-8.428,-142],[-5.554,-158.143],[-5.52,-158.237],[-5.53,-158.148]],"c":true}]},{"t":24,"s":[{"i":[[16.966,-8.664],[-0.837,3.306],[0,0],[-0.026,-0.027]],"o":[[-10.11,-10.611],[-0.003,0.061],[0,0],[2.849,5.907]],"v":[[51.572,-404],[54.446,-420.143],[54.48,-420.237],[54.47,-420.148]],"c":true}]}]},"nm":"Path 12","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-264.439,24.845],[-27.183,-36.451]],"o":[[-162.439,62.845],[-14.183,-2.451]],"v":[[74.439,-222.845],[77.183,-95.549]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[{"i":[[-14.552,17.164],[-84.524,-59.247]],"o":[[4.448,44.164],[-177.524,-58.247]],"v":[[76.552,-245.164],[28.524,-132.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-46.666,12.482],[4.635,-22.542]],"o":[[58.334,48.482],[-2.365,-44.042]],"v":[[60.666,-261.482],[-6.135,-173.958]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[15.828,22.8],[39.365,-19.981]],"o":[[49.828,24.8],[12.365,-29.981]],"v":[[40.172,-270.8],[41.635,-201.019]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-12.679,33.618],[0.739,-12.776]],"o":[[16.321,12.118],[-23.904,-12.919]],"v":[[19.679,-280.118],[73.404,-244.081]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11,"s":[{"i":[[-10.439,3.845],[-2.183,-5.451]],"o":[[2.561,7.845],[-14.183,-2.451]],"v":[[-0.561,-309.845],[6.183,-290.549]],"c":true}]},{"t":12,"s":[{"i":[[-10.439,3.845],[-2.183,-5.451]],"o":[[2.561,7.845],[-14.183,-2.451]],"v":[[-2.561,-427.845],[4.183,-408.549]],"c":true}]}]},"nm":"Path 9","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-22.717,11.53],[16.682,-16.21]],"o":[[4.283,8.53],[5.682,-20.71]],"v":[[101.967,-233.03],[88.818,-188.29]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[-6.346,4.147],[-0.366,-3.08]],"o":[[5.543,2.722],[-7.337,1.322]],"v":[[88.467,-258.53],[94.818,-248.54]],"c":true}]},{"t":4,"s":[{"i":[[-6.346,4.147],[-0.366,-3.08]],"o":[[5.543,2.722],[-7.337,1.322]],"v":[[84.467,-412.53],[90.818,-402.54]],"c":true}]}]},"nm":"Path 8","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-46.009,12.388],[74.033,-0.611]],"o":[[-4.509,11.388],[-46.967,-107.486]],"v":[[125.509,71.112],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-9.509,76.388],[74.033,-0.611]],"o":[[26.991,165.888],[-12.967,-113.486]],"v":[[146.509,-65.388],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-3.591,66.715],[93.702,-33.599]],"o":[[45.409,68.715],[-23.798,-99.599]],"v":[[124.591,-101.715],[86.798,98.599]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-28.673,100.041],[18.871,-121.211]],"o":[[23.327,45.041],[-11.272,-74.766]],"v":[[102.673,-138.041],[146.129,39.211]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-86.521,83.201],[0.04,-46.057]],"o":[[-27.521,44.701],[-27.46,-39.557]],"v":[[104.521,-178.201],[147.46,-53.443]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-78.37,43.361],[-9.836,-38.494]],"o":[[-9.37,31.361],[-29.009,-15.779]],"v":[[124.37,-218.361],[102.836,-129.506]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-12.932,20.02],[8.931,-18.574]],"o":[[5.568,15.02],[-11.069,-5.574]],"v":[[124.432,-260.02],[122.069,-212.426]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[1.395,4.24],[0.439,-5.367]],"o":[[4.195,4.04],[-8.094,-2.167]],"v":[[109.605,-288.64],[115.494,-276.366]],"c":true}]},{"t":28,"s":[{"i":[[1.395,4.24],[0.439,-5.367]],"o":[[4.195,4.04],[-8.094,-2.167]],"v":[[121.605,-432.64],[127.494,-420.366]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-8.509,21.888],[109.533,-35.486]],"o":[[30.991,64.388],[-33.467,-95.486]],"v":[[-84.491,-110.888],[-138.033,56.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[22.118,49.473],[122.558,9.2]],"o":[[68.618,31.473],[6.558,-41.3]],"v":[[-88.618,-143.473],[-141.058,-21.7]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[1.746,26.057],[53.583,-9.114]],"o":[[69.246,2.557],[28.083,-45.614]],"v":[[-106.746,-172.057],[-96.083,-90.386]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-2.806,9.641],[17.287,-6.213]],"o":[[11.944,10.391],[-8.963,-20.463]],"v":[[-100.944,-206.641],[-102.787,-160.537]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[-0.532,1.368],[6.846,-2.218]],"o":[[1.937,4.024],[-2.092,-5.968]],"v":[[-95.281,-232.141],[-96.877,-218.399]],"c":true}]},{"t":16,"s":[{"i":[[-0.532,1.368],[6.846,-2.218]],"o":[[1.937,4.024],[-2.092,-5.968]],"v":[[-97.281,-426.141],[-98.877,-412.399]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":7,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[3.874,33.283],[9.817,-3.451]],"o":[[36.874,61.283],[-1.183,-15.951]],"v":[[-146.374,-134.783],[-180.817,-70.549]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-0.126,16.712],[9.817,-3.451]],"o":[[22.088,11.14],[-1.755,-10.237]],"v":[[-156.088,-168.64],[-145.102,-130.049]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[-3.126,4.283],[9.817,-3.451]],"o":[[4.874,3.783],[-2.183,-5.951]],"v":[[-152.874,-195.783],[-156.817,-182.549]],"c":true}]},{"t":8,"s":[{"i":[[-3.126,4.283],[9.817,-3.451]],"o":[[4.874,3.783],[-2.183,-5.951]],"v":[[-156.874,-419.783],[-160.817,-406.549]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":8,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-14.009,113.888],[9.033,-147.486]],"o":[[54.491,45.388],[-75.467,-0.611]],"v":[[-160.991,-29.888],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-32.96,22.468],[39.256,-132.515]],"o":[[9.54,49.968],[-101.244,-47.015]],"v":[[-141.04,-64.468],[-79.756,102.515]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-6.912,19.047],[-5.522,-58.543]],"o":[[45.088,50.047],[-57.022,-89.543]],"v":[[-121.088,-99.047],[-141.978,53.043]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0.17,68.493],[6.251,-19.805]],"o":[[67.67,54.493],[-2.749,-44.305]],"v":[[-115.67,-138.993],[-160.251,-25.195]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[2.752,16.439],[33.524,-10.567]],"o":[[46.252,26.939],[13.524,-31.067]],"v":[[-121.252,-178.939],[-123.524,-94.933]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-7.345,7.385],[14.19,-7.615]],"o":[[22.798,13.885],[-2.31,-14.115]],"v":[[-118.655,-218.885],[-120.19,-175.885]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[-2.042,3.844],[4.939,-3.65]],"o":[[5.208,4.094],[-3.144,-0.025]],"v":[[-116.708,-248.844],[-117.689,-236.6]],"c":true}]},{"t":24,"s":[{"i":[[-2.042,3.844],[4.939,-3.65]],"o":[[5.208,4.094],[-3.144,-0.025]],"v":[[-120.708,-416.844],[-121.689,-404.6]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[0,76.641]},"e":{"a":0,"k":[0,-306.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":28,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"01 fire small left bottom flame (f24) 2","refId":"comp_3","sr":1,"ks":{"p":{"a":0,"k":[246,253.2,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":8,"op":40,"st":8,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"01 fire small left flame twirl","refId":"comp_4","sr":1,"ks":{"p":{"a":0,"k":[246,253.2,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":8,"op":40,"st":8,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[261.799,285,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-139.238,137.226],[-214.402,227]],"o":[[110.598,-109],[69.514,-73.599]],"v":[[7,157],[22,-231]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[179.736,76.896],[409.598,33]],"o":[[-231.402,-99],[-100.91,-8.13]],"v":[[-11,213],[-186,-187]],"c":false}},"nm":"Path 2","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[0]},{"t":48,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":38,"s":[8]},{"t":48,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":38,"s":[24]},{"t":48,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":9,"op":48,"st":-1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"p":{"a":0,"k":[261.799,285,0]},"a":{"a":0,"k":[3.799,5,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[179.736,76.896],[302,241]],"o":[[-231.402,-99],[-79.13,-63.147]],"v":[[-11,213],[-186,-187]],"c":false}},"nm":"Path 2","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[0]},{"t":47,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[8]},{"t":47,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[24]},{"t":47,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":9,"op":47,"st":9,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"p":{"a":0,"k":[261.799,285,0]},"a":{"a":0,"k":[3.799,5,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-189.52,47.962],[-214.402,227]],"o":[[407,-103],[69.514,-73.599]],"v":[[7,157],[22,-231]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[0]},{"t":42,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":32,"s":[8]},{"t":42,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":32,"s":[24]},{"t":42,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":2,"op":42,"st":2,"bm":0}]},{"id":"comp_3","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"main 4","sr":1,"ks":{"p":{"a":0,"k":[256,390,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[65.688,1.123],[19.556,69.083]],"o":[[-41.977,-0.718],[0.056,0.083]],"v":[[0,105],[-106.556,17.417]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[56.878,-11.304],[-71.136,145.476]],"o":[[-40.253,8],[-9.136,65.476]],"v":[[20.467,102.986],[-125.864,-38.476]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[24.289,-77.906],[35.914,15.981]],"o":[[-62.711,-56.906],[43.914,-19.019]],"v":[[-127.289,-23.094],[-176.914,-135.981]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-7.43,-47.184],[26.355,-15.403]],"o":[[-46.274,-7.369],[16.386,-25.431]],"v":[[-126.57,-102.816],[-189.726,-126.053]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-39.149,-16.463],[16.796,-46.787]],"o":[[-29.838,42.168],[-11.142,-31.843]],"v":[[-151.851,-136.537],[-228.539,-70.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-20.866,7.057],[-9.552,-23.948]],"o":[[2.898,26.635],[-15.271,-8.542]],"v":[[-209.791,-103.936],[-216.762,-51.783]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-3.95,10.396],[-12.397,-3.967]],"o":[[11.308,7.423],[-8.231,3.207]],"v":[[-222.283,-63.444],[-204.202,-43.982]],"c":true}]},{"t":32,"s":[{"i":[[-0.203,0.069],[-0.093,-0.233]],"o":[[0.028,0.259],[-0.148,-0.083]],"v":[[-180.901,-36],[-180.969,-35.493]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[-0.803,78.641]},"e":{"a":0,"k":[5.941,-306.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":36,"st":-24,"bm":0}]},{"id":"comp_4","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"main 5","sr":1,"ks":{"p":{"a":0,"k":[281.589,353.586,0]},"a":{"a":0,"k":[-25.411,84.586,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[35.726,95.092],[9.957,-101.911],[-13.539,42.608]],"o":[[76.726,173.092],[-45.603,-22.126],[34,-107]],"v":[[20.274,-202.092],[-45.957,94.911],[-102,-24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[28.539,79.666],[15.271,-30.792],[63.294,-24.571]],"o":[[89.467,109.928],[-7.636,15.397],[-160.84,62.44]],"v":[[-4.803,-241.627],[21.701,-28.613],[-27.163,-45.068]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[27.035,22.744],[-7.411,-52.025],[34.341,-66.85]],"o":[[98.144,67.203],[4.153,29.149],[-14.835,28.88]],"v":[[-66.918,-295.641],[41.698,-130.341],[-50.963,-169.565]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[38.174,25.145],[-12.055,-52.934],[22.231,-50.177]],"o":[[93.649,22.392],[6.204,24.263],[-19.381,32.447]],"v":[[-114.032,-311.296],[26.995,-175.344],[-61.336,-205.289]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[51.836,22.143],[-19.073,-53.622],[17.852,-32.908]],"o":[[74.884,-18.277],[7.052,19.825],[-21.585,39.789]],"v":[[-157.371,-308.755],[11.042,-212.473],[-74.322,-234.122]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14,"s":[{"i":[[47.388,-0.852],[-20.633,-42.246],[-6.679,-39.947]],"o":[[79.829,-60.96],[7.911,20.621],[3.631,36.916]],"v":[[-192.262,-284.999],[-12.601,-245.703],[-93.247,-262.886]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[30.066,-23.944],[-27.45,-33.052],[-38.7,-24.364]],"o":[[34.202,-118.986],[16.55,19.928],[26.108,16.437]],"v":[[-220.042,-233.02],[-52.086,-285.918],[-140.713,-280.698]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[21.559,-53.406],[-42.556,-12.395],[-39.454,-8.254]],"o":[[-21.52,-112.915],[26.583,7.597],[22.752,4.36]],"v":[[-217.817,-188.862],[-94.492,-311.817],[-150.4,-267.689]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-8.081,-76.532],[-44.505,14.451],[-34.813,5.036]],"o":[[-66.788,-82.682],[28.609,-9.29],[15.471,-2.238]],"v":[[-197.892,-143.194],[-148.445,-308.584],[-158.488,-252.15]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[-42.015,-57.434],[-26.817,31.143],[-39.824,16.539]],"o":[[-92.051,-33.255],[19.332,-22.828],[22.33,-8.449]],"v":[[-156.547,-114.04],[-197.313,-273.38],[-168.13,-223.209]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-61.367,-22.786],[-3.433,35.391],[-37.643,36.993]],"o":[[-86.784,22.985],[2.883,-29.724],[23.519,-23.113]],"v":[[-107.757,-110.853],[-225.28,-214.573],[-166.864,-196.295]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[{"i":[[-48.255,8.824],[11.318,24.14],[-10.048,38.28]],"o":[[-47.05,48.04],[-9.546,-20.238],[6.238,-23.914]],"v":[[-81.839,-126.076],[-197.225,-147.861],[-152.608,-158.445]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-22.599,23.564],[15.508,8.665],[9.557,24.582]],"o":[[-6.616,44.216],[-13.049,-7.172],[-6.032,-15.307]],"v":[[-64.671,-149.022],[-135.368,-115.824],[-115.418,-138.897]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-2.335,16.115],[8.871,-0.948],[11.06,7.121]],"o":[[10.116,19.764],[-7.341,0.908],[-6.895,-4.334]],"v":[[-43.565,-175.386],[-62.601,-141.49],[-61.167,-156.623]],"c":true}]},{"t":32,"s":[{"i":[[-0.068,-0.18],[-0.019,0.193],[0.026,-0.081]],"o":[[-0.145,-0.328],[0.086,0.042],[-0.064,0.203]],"v":[[-36.242,-198.937],[-36.116,-199.5],[-36.01,-199.275]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[-0.803,78.641]},"e":{"a":0,"k":[5.941,-306.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":-40,"bm":0}]},{"id":"comp_5","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","parent":2,"sr":1,"ks":{"p":{"a":0,"k":[-77.334,-175.69,0]},"a":{"a":0,"k":[-77.334,-175.69,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.988,3.181],[-19.8,-5.052]],"o":[[-4.889,-7.822],[26.133,6.668]],"v":[[19.329,271.399],[24.3,294.213]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[4.186,6.698],[-41.687,-10.636]],"o":[[-10.293,-16.469],[55.022,14.038]],"v":[[7.834,173.427],[18.299,221.462]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[14.361,7.522],[-30.261,-6.497]],"o":[[-24.248,-12.701],[34.72,7.454]],"v":[[35.139,128.628],[12.401,185.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[32.578,61.344],[28.072,72.523]],"c":true}]},{"t":19,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[20.578,-416.656],[16.072,-405.477]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[33.144,5.233],[-24.721,-0.221]],"o":[[-35.022,-5.53],[27.911,0.249]],"v":[[19.555,225.935],[15.319,283.751]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[29.02,-23.268],[-29.148,15.285]],"o":[[-36.827,29.528],[27.206,-14.266]],"v":[[18.827,100.658],[25.794,178.294]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[36.864,-1.646],[-34.889,28.965]],"o":[[-35.938,1.604],[46.08,13.495]],"v":[[1.709,52.646],[-0.595,87.535]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[19.216,-6.659],[-24.954,25.786]],"o":[[-28.004,9.704],[30.5,0]],"v":[[16.732,-8.349],[24.773,17.714]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[12.002,-2.4],[-12.348,2.646]],"o":[[-16.253,3.251],[13.442,-2.88]],"v":[[25.798,-28.588],[29.158,-15.146]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":31,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[47.81,-67.072],[-25.632,9.313]],"o":[[-93.246,48.698],[25.632,-9.313]],"v":[[-74.754,121.302],[-13.132,182.313]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[16.302,-91.514],[22.297,-55.155]],"o":[[-51.846,41.486],[63.795,-25.293]],"v":[[-77.654,-43.486],[-14.297,37.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[65.508,-110.844],[-62.172,37.016]],"o":[[-46.719,4.672],[111.508,-8.984]],"v":[[-77.508,-81.156],[-31.508,-13.016]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[44.392,-34.904],[-43.656,10.635]],"o":[[-57.534,8.63],[43.656,-10.635]],"v":[[-64.892,-151.596],[-43.156,-90.865]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[45.056,-54.944],[-36.611,29.426]],"o":[[-75.944,-2.944],[74.944,33.426]],"v":[[-55.556,-184.556],[-45.389,-150.426]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[14.5,-3],[-14.25,5.5]],"o":[[-14.5,3],[14.25,-5.5]],"v":[[-44.875,-250.5],[-40.375,-234]],"c":true}]},{"t":36,"s":[{"i":[[1.16,-0.24],[-1.14,0.44]],"o":[[-1.16,0.24],[1.14,-0.44]],"v":[[-45.92,-278.26],[-45.56,-276.94]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[38.071,2.586],[-51.674,3.841],[-14.328,5.136]],"o":[[-71.639,-4.866],[8.07,11.41],[22.966,-8.233]],"v":[[122.074,242.366],[89.943,278.535],[130.995,288.864]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[20.869,-12.454],[-146.79,82.022],[-12.062,30.104]],"o":[[-112.454,-96.052],[15.663,4.937],[27.265,-8.079]],"v":[[157.954,39.052],[123.79,100.978],[169.735,69.137]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[74.55,-67.464],[-92.612,89.3],[0.78,32.797]],"o":[[-175.95,-43.988],[-28.612,-55.2],[57.28,16.797]],"v":[[109.45,-30.036],[99.612,30.2],[150.72,14.703]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[79.321,-50.136],[-20.934,-21.241],[-26.364,9.073]],"o":[[-58.179,-42.136],[17.914,18.177],[76.169,29.621]],"v":[[102.679,-76.364],[46.086,-27.677],[111.331,-23.621]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[62.617,-21.138],[-8.337,-17.814],[-11.971,0.122]],"o":[[-29.444,-9.815],[3.681,7.865],[67.802,31.907]],"v":[[103.66,-125.362],[70.352,-89.569],[93.689,-75.907]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.334,0],[-4.841,-5.763],[-4.486,0.179]],"o":[[-13.412,0],[2.137,2.544],[14.647,-0.586]],"v":[[106.839,-168.5],[96.133,-153.01],[106.253,-148.873]],"c":true}]},{"t":36,"s":[{"i":[[5.5,0],[-1.377,-1.639],[-1.276,0.051]],"o":[[-3.815,0],[0.608,0.724],[4.167,-0.167]],"v":[[103.5,-198.125],[100.454,-193.718],[103.333,-192.542]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-3.499,-24.848],[-6.884,-16.478],[-0.301,7.46],[-4.644,0.228],[-25.003,12.782],[33.341,-4.053]],"o":[[-34.078,22.449],[3.276,7.842],[1.294,-32.082],[19.012,20.499],[85.358,33.282],[-6.489,-19.362]],"v":[[-102.261,200.848],[-133.404,262.516],[-118.294,281.082],[-102.012,287.001],[-31.997,296.218],[-11.544,205.996]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[-0.747,-48.874],[-2.455,-35.731],[-40.679,19.746],[-10.189,-28.494],[-11.643,4.734],[85.039,22.458]],"o":[[-21.211,-29.874],[-35.532,22.641],[-1.215,-25.539],[22.811,19.506],[80.357,20.734],[29.075,-42.399]],"v":[[-100.253,19.874],[-169.932,40.216],[-144.785,98.539],[-93.811,115.494],[-40.357,117.266],[-14.075,40.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[4.596,-2.157],[-2.838,-33.101],[-23.392,21.782],[64.52,-34.091],[-7.412,7.454],[7.166,18.65]],"o":[[-43.115,-41.195],[-25.698,17.837],[-20.978,-41.699],[20.24,22.409],[13.576,-13.654],[-5.083,-13.23]],"v":[[-78.36,-37.305],[-165.347,-14.561],[-142.022,35.199],[-94.52,47.591],[-39.74,43.428],[-28.273,-12.423]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[19.586,24.077],[1.481,-16.058],[-4.9,-5.507],[-10.179,0.727],[8.719,-39.971],[89.45,-41.348]],"o":[[-19.447,-7.56],[-0.705,7.642],[4.542,5.105],[-3.153,-23.324],[83.219,35.029],[-14.55,-40.348]],"v":[[-113.586,-84.077],[-147.276,-60.135],[-141.231,-39.204],[-119.347,-31.676],[-66.219,-26.529],[-64.45,-76.152]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[13.764,-16.52],[-0.797,-14.42],[-5.435,-4.397],[-9.222,1.812],[-4.425,5.611],[7.725,10.732]],"o":[[-22.868,-5.87],[0.379,6.863],[5.039,4.077],[14.57,16.763],[8.105,-10.278],[-5.48,-7.613]],"v":[[-80.463,-124.48],[-113.116,-101.094],[-104.345,-83.066],[-82.915,-78.763],[-39.977,-80.996],[-37.614,-117.588]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[9.081,-4.12],[1.941,-4.856],[-0.074,-2.217],[-1.433,-1.373],[-2.412,2.764],[3.877,5.235]],"o":[[-4.629,2.1],[-0.924,2.311],[0.069,2.056],[11.907,11.408],[4.417,-5.063],[-2.75,-3.714]],"v":[[-75.274,-166.527],[-85.585,-154.758],[-86.926,-147.859],[-85.155,-142.261],[-54.718,-143.901],[-53.648,-161.404]],"c":true}]},{"t":36,"s":[{"i":[[0.601,-0.289],[0.013,-0.847],[-0.221,-0.37],[-0.364,-0.269],[-0.704,0.652],[0.934,1.027]],"o":[[-1.307,0.628],[-0.006,0.403],[0.205,0.343],[0.87,0.643],[1.289,-1.194],[-0.662,-0.729]],"v":[[-79.613,-181.42],[-81.494,-179.083],[-81.161,-177.91],[-80.299,-176.982],[-73.89,-177.455],[-73.114,-181.387]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392216701,0.972549079446,0.949019667682,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[280.666,670.31,0]},"a":{"a":0,"k":[24.666,286.31,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[20,20,100]},{"t":12,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.988,3.181],[-19.8,-5.052]],"o":[[-4.889,-7.822],[26.133,6.668]],"v":[[19.329,271.399],[24.3,294.213]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[6.101,9.761],[-60.75,-15.5]],"o":[[-15,-24],[80.183,20.458]],"v":[[0,163],[15.25,233]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[21,11],[-44.25,-9.5]],"o":[[-35.458,-18.573],[50.771,10.9]],"v":[[35,125],[1.75,207.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[32.578,61.344],[28.072,72.523]],"c":true}]},{"t":19,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[20.578,-416.656],[16.072,-405.477]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[66.5,10.5],[-49.599,-0.443]],"o":[[-70.267,-11.095],[56,0.5]],"v":[[20.5,199.5],[12,315.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[43.734,-35.066],[-43.928,23.035]],"o":[[-55.5,44.5],[41,-21.5]],"v":[[15.5,72.5],[26,189.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[56,-2.5],[-53,44]],"o":[[-54.592,2.437],[70,20.5]],"v":[[-4.5,51.5],[-8,104.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[34.653,-12.008],[-45,46.5]],"o":[[-50.5,17.5],[55,0]],"v":[[9.5,-11.5],[24,35.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[25,-5],[-25.722,5.512]],"o":[[-33.855,6.771],[28,-6]],"v":[[23,-29],[30,-1]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":31,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[69.5,-97.5],[-113.5,45]],"o":[[-66.5,8.5],[86.092,-34.134]],"v":[[-90.5,124.5],[-22,227]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[22,-123.5],[-113.5,45]],"o":[[-41.5,7],[86.092,-34.134]],"v":[[-91.5,-56.5],[-6,53]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[29.5,-125],[-86.5,51.5]],"o":[[-65,6.5],[164.5,11.5]],"v":[[-87.5,-87],[-23.5,-7.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[71,-76.5],[-34.5,28]],"o":[[-90,13.5],[106.5,47.5]],"v":[[-68.5,-157.5],[-34.5,-62.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[48.5,-66],[-34.5,28]],"o":[[-81,3],[106.5,47.5]],"v":[[-57.5,-194],[-53,-132]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[29,-6],[-28.5,11]],"o":[[-29,6],[28.5,-11]],"v":[[-46,-251.5],[-37,-218.5]],"c":true}]},{"t":36,"s":[{"i":[[1.16,-0.24],[-1.14,0.44]],"o":[[-1.16,0.24],[1.14,-0.44]],"v":[[-45.92,-278.26],[-45.56,-276.94]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[70.414,4.783],[-95.574,7.104],[-26.5,9.5]],"o":[[-132.5,-9],[14.926,21.104],[42.478,-15.228]],"v":[[116,239],[56.574,305.896],[132.5,325]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[31,-18.5],[-168.751,36.203],[-17.918,44.718]],"o":[[-248,-61.5],[23.267,7.334],[40.5,-12]],"v":[[167,24],[116.251,135.297],[184.5,88]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[101,-20.5],[-133.016,57.816],[-23.678,31.225]],"o":[[-222,-55.5],[17.83,12.614],[53,-13]],"v":[[114.5,-57.5],[94.516,65.184],[159,45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[108,-21],[-25.045,-34.694],[-37.826,13.017]],"o":[[-46.826,-19.424],[11.058,15.318],[78.5,26]],"v":[[96,-99.5],[40.628,-7.406],[112,2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[78.5,-26.5],[-10.452,-22.332],[-15.008,0.153]],"o":[[-36.913,-12.304],[4.615,9.86],[85,40]],"v":[[101.5,-126.5],[59.744,-81.628],[89,-64.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[33,0],[-8.262,-9.837],[-7.657,0.306]],"o":[[-22.893,0],[3.648,4.343],[25,-1]],"v":[[103,-170],[84.726,-143.561],[102,-136.5]],"c":true}]},{"t":36,"s":[{"i":[[5.5,0],[-1.377,-1.639],[-1.276,0.051]],"o":[[-3.815,0],[0.608,0.724],[4.167,-0.167]],"v":[[103.5,-198.125],[100.454,-193.718],[103.333,-192.542]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-4.046,-28.733],[-7.961,-19.055],[-7.058,-4.972],[-5.37,0.263],[-10.383,14.21],[38.553,-4.686]],"o":[[-39.406,25.959],[3.789,9.068],[6.543,4.609],[20.166,40.644],[98.704,38.486],[-7.504,-22.39]],"v":[[-106.454,196.733],[-142.466,268.042],[-124.994,289.511],[-106.166,296.356],[-25.204,307.014],[-1.553,202.686]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[7.754,-10.103],[-2.864,-41.686],[-47.459,23.038],[-17.819,6.695],[-11.001,13.349],[99.213,26.201]],"o":[[-24.746,-34.853],[-41.454,26.415],[10.089,11.703],[9.571,21.591],[45.708,87.19],[-6.546,-22.901]],"v":[[-105.754,6.853],[-187.046,30.585],[-146.041,114.963],[-104.071,125.409],[-34.708,136.81],[-5.213,30.799]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[5.648,-2.651],[-3.488,-40.68],[-28.748,26.769],[-22.563,7.933],[-9.109,9.161],[8.807,22.921]],"o":[[-52.986,-50.627],[-31.582,21.921],[15.404,15.153],[24.874,27.539],[16.685,-16.781],[-6.247,-16.259]],"v":[[-65.014,-36.373],[-171.918,-8.421],[-143.252,52.731],[-84.874,67.961],[-17.551,62.845],[-3.459,-5.795]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[19.861,-24.879],[2.389,-25.903],[-7.904,-8.883],[-16.419,1.173],[-8.619,13.394],[50.602,-9.162]],"o":[[-31.37,-12.195],[-1.137,12.327],[7.327,8.235],[20.153,51.095],[59.455,-7.707],[-9.228,-15.356]],"v":[[-114.361,-87.621],[-168.705,-49.001],[-158.954,-15.239],[-123.653,-3.095],[-37.955,5.207],[-35.102,-74.838]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[20.907,-25.093],[-1.21,-21.904],[-8.256,-6.68],[-14.008,2.753],[-6.721,8.523],[11.735,16.301]],"o":[[-34.736,-8.916],[0.576,10.424],[7.654,6.192],[22.131,25.463],[12.311,-15.612],[-8.324,-11.564]],"v":[[-85.407,-125.907],[-135.006,-90.384],[-121.683,-62.999],[-89.131,-56.463],[-23.909,-59.855],[-20.32,-115.438]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[16.089,-7.299],[3.439,-8.602],[-0.131,-3.928],[-2.539,-2.432],[-4.273,4.897],[6.868,9.275]],"o":[[-8.201,3.721],[-1.637,4.094],[0.122,3.642],[21.095,20.211],[7.826,-8.97],[-4.872,-6.579]],"v":[[-77.089,-167.201],[-95.356,-146.351],[-97.732,-134.128],[-94.595,-124.211],[-40.671,-127.117],[-38.776,-158.124]],"c":true}]},{"t":36,"s":[{"i":[[1.463,-0.702],[0.031,-2.062],[-0.538,-0.9],[-0.887,-0.655],[-1.713,1.587],[2.273,2.5]],"o":[[-3.182,1.528],[-0.015,0.981],[0.498,0.834],[2.118,1.565],[3.138,-2.907],[-1.612,-1.773]],"v":[[-78.589,-181.314],[-83.167,-175.626],[-82.357,-172.772],[-80.258,-170.512],[-64.659,-171.663],[-62.771,-181.234]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.945098099054,0.90588241278,0.792156922583,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.917647118662,0.847058883368,0.721568627451,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":36,"st":0,"bm":0}]},{"id":"comp_6","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 12","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[256,495,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[40,40,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":37,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":64,"s":[60,60,100]},{"t":76,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":124,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"03 fire small wide cycle end","parent":1,"refId":"comp_7","sr":1,"ks":{"p":{"a":0,"k":[0,-239,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":64,"op":92,"st":64,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"03 fire small wide cycle","parent":1,"refId":"comp_8","sr":1,"ks":{"p":{"a":0,"k":[0,-239,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"03 fire small wide cycle","parent":1,"refId":"comp_8","sr":1,"ks":{"p":{"a":0,"k":[0,-239,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[261.799,285,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-139.238,137.226],[-214.402,227]],"o":[[110.598,-109],[69.514,-73.599]],"v":[[7,157],[22,-231]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[179.736,76.896],[409.598,33]],"o":[[-231.402,-99],[-100.91,-8.13]],"v":[[-11,213],[-186,-187]],"c":false}},"nm":"Path 2","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":74,"s":[0]},{"t":124,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":74,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":84,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":114,"s":[8]},{"t":124,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":74,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":84,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":114,"s":[24]},{"t":124,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":64,"op":124,"st":64,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"p":{"a":0,"k":[261.799,285,0]},"a":{"a":0,"k":[3.799,5,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[179.736,76.896],[302,241]],"o":[[-231.402,-99],[-79.13,-63.147]],"v":[[-11,213],[-186,-187]],"c":false}},"nm":"Path 2","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":64,"s":[0]},{"t":114,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":64,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":74,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":104,"s":[8]},{"t":114,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":64,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":74,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":104,"s":[24]},{"t":114,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":64,"op":114,"st":64,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"p":{"a":0,"k":[261.799,285,0]},"a":{"a":0,"k":[3.799,5,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-189.52,47.962],[-214.402,227]],"o":[[407,-103],[69.514,-73.599]],"v":[[7,157],[22,-231]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":69,"s":[0]},{"t":109,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":69,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":79,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":99,"s":[8]},{"t":109,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":69,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":79,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":99,"s":[24]},{"t":109,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":69,"op":109,"st":69,"bm":0}]},{"id":"comp_7","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"core","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[100]},{"t":20,"s":[0]}]},"p":{"a":0,"k":[260.85,494.271,0]},"a":{"a":0,"k":[-51.438,222.044,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[18.197,16.546],[-41.985,0.511],[69.85,68.547]],"o":[[-53.725,57.166],[41.194,-0.501],[-14.905,76.809]],"v":[[-60.485,-15.773],[2.147,99.863],[27.617,-48.036]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[0.068,-0.429],[-35.745,0.435],[65.918,79.024]],"o":[[-55.611,56.567],[35.071,-0.427],[-0.687,-0.216]],"v":[[-9.918,-30.798],[-3.59,87.929],[-8.638,-29.958]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0.069,-0.44],[-31.286,-32.594],[40.368,45.715]],"o":[[-28.033,10.909],[28.162,-20.704],[-0.705,-0.222]],"v":[[18.029,13.22],[1.286,91.311],[19.344,14.083]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0.086,-0.545],[-13.305,-8.903],[-4.886,23.805]],"o":[[-34.696,13.502],[52.246,-21.612],[-0.872,-0.275]],"v":[[0.536,-65.183],[9.914,2.038],[2.164,-64.114]],"c":true}]},{"t":20,"s":[{"i":[[0.128,-0.814],[-10.891,-11.311],[27.696,1.589]],"o":[[20.128,26.186],[43.109,-11.311],[-1.304,-0.411]],"v":[[11.872,-133.186],[21.891,-73.689],[14.304,-131.589]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.121,0.996,0.996,0.898,0.54,0.998,0.951,0.598,0.808,1,0.906,0.298]}},"s":{"a":0,"k":[2.235,29.653]},"e":{"a":0,"k":[6.469,-84.419]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 4","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":20,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"second","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[100]},{"t":20,"s":[0]}]},"p":{"a":0,"k":[258,398,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[112.946,110.839]],"o":[[-86.872,92.436],[66.609,-0.811],[-1.054,155.839]],"v":[[-75.128,-84.436],[1.891,99.311],[22.054,-139.839]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[0.128,-0.814],[-67.889,0.826],[125.196,150.089]],"o":[[-105.622,107.436],[66.609,-0.811],[-1.304,-0.411]],"v":[[-13.128,-140.186],[-1.109,85.311],[-10.696,-138.589]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0.128,-0.814],[-57.891,-60.311],[74.696,84.589]],"o":[[-51.872,20.186],[52.109,-38.311],[-1.304,-0.411]],"v":[[33.872,-53.186],[2.891,91.311],[36.304,-51.589]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0.128,-0.814],[-19.891,-13.311],[-7.304,35.589]],"o":[[-51.872,20.186],[78.109,-32.311],[-1.304,-0.411]],"v":[[-2.128,-99.186],[11.891,1.311],[0.304,-97.589]],"c":true}]},{"t":20,"s":[{"i":[[0.128,-0.814],[-10.891,-11.311],[27.696,1.589]],"o":[[20.128,26.186],[43.109,-11.311],[-1.304,-0.411]],"v":[[11.872,-133.186],[21.891,-73.689],[14.304,-131.589]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.914,0.239,0.269,1,0.739,0.22,1,1,0.565,0.2]}},"s":{"a":0,"k":[1.76,83.087]},"e":{"a":0,"k":[-8.18,-233.581]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":20,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"main 2","sr":1,"ks":{"p":{"a":0,"k":[256,384,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-49.509,44.388],[123.533,-38.986]],"o":[[-17.509,54.388],[-101.467,-137.986]],"v":[[138.509,-106.388],[72.467,102.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-54.652,117.814],[96.514,-88.952]],"o":[[39.848,36.814],[-55.486,-82.702]],"v":[[122.902,-122.064],[135.986,59.452]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-69.795,121.239],[25.494,-72.919]],"o":[[-6.795,25.239],[-9.506,-27.419]],"v":[[118.295,-144.739],[157.506,-8.081]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-47.393,78.823],[5.657,-37.203]],"o":[[-20.938,53.232],[-20.343,-15.203]],"v":[[125.938,-167.732],[170.343,-77.797]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-14.581,12.011],[1.034,-14.274]],"o":[[-8.51,26.743],[-24.109,4.012]],"v":[[132.081,-191.511],[118.466,-138.726]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[-8.189,4.595],[5.817,-6.951]],"o":[[0.811,6.877],[-3.683,-1.451]],"v":[[129.689,-209.345],[131.183,-194.049]],"c":true}]},{"t":20,"s":[{"i":[[-8.189,4.595],[5.817,-6.951]],"o":[[0.811,6.877],[-3.683,-1.451]],"v":[[139.689,-425.345],[141.183,-410.049]],"c":true}]}]},"nm":"Path 10","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[116.786,0],[11,144.667]],"o":[[-317,-127.726],[249,149.667]],"v":[[-1,113.726],[-1,-153.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[120.519,-22.794],[-202.241,204.246]],"o":[[-143.695,27.178],[23.759,129.246]],"v":[[0.481,97.794],[0.241,-202.246]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[168.037,-36.863],[3.518,139.825]],"o":[[-193.963,-76.863],[211.518,139.825]],"v":[[1.963,81.863],[-26.518,-251.825]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[78.037,-55.931],[-63.722,35.404]],"o":[[-42.963,-53.931],[-21.722,38.404]],"v":[[2.963,-132.069],[27.722,-295.404]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0.866,0],[-1.78,1.414]],"o":[[-1.004,0],[1.908,1.516]],"v":[[3.963,-346],[3.963,-347.983]],"c":true}]},{"t":17,"s":[{"i":[[0.866,0],[-1.78,1.414]],"o":[[-1.004,0],[1.908,1.516]],"v":[[25.963,-410],[25.963,-411.983]],"c":true}]}]},"nm":"Path 13","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[150.97,-29.436],[-7.904,50.282],[-29.987,22.93],[31.343,22.513]],"o":[[-171.03,-96.436],[20.614,30.124],[20.013,43.93],[96.97,73.457]],"v":[[0.03,113.436],[-84.471,-110.907],[-1.013,-82.93],[77.03,-95.457]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[263.97,-70.436],[41.784,65.595],[-24.463,-11.448],[-17.438,27.049]],"o":[[-288.03,-110.436],[28.054,20.833],[28.268,13.229],[-17.53,86.457]],"v":[[0.03,113.436],[-88.471,-144.407],[-8.648,-91.164],[61.53,-104.457]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[150.97,-29.436],[-32.029,33.907],[-26.789,24.397],[-10.737,32.946]],"o":[[-259.03,-106.436],[23.879,14.802],[25.211,25.397],[-2.03,120.457]],"v":[[0.03,113.436],[-60.471,-110.407],[27.789,-112.897],[118.03,-147.457]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[182.97,-93.436],[-9.029,35.657],[0,0],[-0.28,-0.293]],"o":[[-109.03,-114.436],[-0.029,0.657],[0,0],[30.72,63.707]],"v":[[0.03,113.436],[31.029,-60.657],[31.387,-61.673],[31.28,-60.707]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[122.605,-62.61],[-52.544,10.197],[0,0],[-0.187,-0.196]],"o":[[-64.272,-70.959],[-0.019,0.44],[0,0],[-33.712,34.231]],"v":[[10.772,31.459],[11.544,-98.197],[11.784,-98.878],[11.712,-98.231]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[62.24,-31.783],[36.94,34.738],[0,0],[-0.095,-0.1]],"o":[[-19.515,-27.482],[-0.01,0.223],[0,0],[53.855,13.755]],"v":[[21.515,-50.518],[-7.94,-135.738],[-7.818,-136.083],[-7.855,-135.755]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[16.966,-8.664],[-0.837,3.306],[0,0],[-0.026,-0.027]],"o":[[-10.11,-10.611],[-0.003,0.061],[0,0],[2.849,5.907]],"v":[[-8.428,-142],[-5.554,-158.143],[-5.52,-158.237],[-5.53,-158.148]],"c":true}]},{"t":24,"s":[{"i":[[16.966,-8.664],[-0.837,3.306],[0,0],[-0.026,-0.027]],"o":[[-10.11,-10.611],[-0.003,0.061],[0,0],[2.849,5.907]],"v":[[51.572,-404],[54.446,-420.143],[54.48,-420.237],[54.47,-420.148]],"c":true}]}]},"nm":"Path 12","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-264.439,24.845],[-27.183,-36.451]],"o":[[-162.439,62.845],[-14.183,-2.451]],"v":[[74.439,-222.845],[77.183,-95.549]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[{"i":[[-14.552,17.164],[-84.524,-59.247]],"o":[[4.448,44.164],[-177.524,-58.247]],"v":[[76.552,-245.164],[28.524,-132.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-46.666,12.482],[4.635,-22.542]],"o":[[58.334,48.482],[-2.365,-44.042]],"v":[[60.666,-261.482],[-6.135,-173.958]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[15.828,22.8],[39.365,-19.981]],"o":[[49.828,24.8],[12.365,-29.981]],"v":[[40.172,-270.8],[41.635,-201.019]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-12.679,33.618],[0.739,-12.776]],"o":[[16.321,12.118],[-23.904,-12.919]],"v":[[19.679,-280.118],[73.404,-244.081]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11,"s":[{"i":[[-10.439,3.845],[-2.183,-5.451]],"o":[[2.561,7.845],[-14.183,-2.451]],"v":[[-0.561,-309.845],[6.183,-290.549]],"c":true}]},{"t":12,"s":[{"i":[[-10.439,3.845],[-2.183,-5.451]],"o":[[2.561,7.845],[-14.183,-2.451]],"v":[[-2.561,-427.845],[4.183,-408.549]],"c":true}]}]},"nm":"Path 9","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-22.717,11.53],[16.682,-16.21]],"o":[[4.283,8.53],[5.682,-20.71]],"v":[[101.967,-233.03],[88.818,-188.29]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[-6.346,4.147],[-0.366,-3.08]],"o":[[5.543,2.722],[-7.337,1.322]],"v":[[88.467,-258.53],[94.818,-248.54]],"c":true}]},{"t":4,"s":[{"i":[[-6.346,4.147],[-0.366,-3.08]],"o":[[5.543,2.722],[-7.337,1.322]],"v":[[84.467,-412.53],[90.818,-402.54]],"c":true}]}]},"nm":"Path 8","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-46.009,12.388],[74.033,-0.611]],"o":[[-4.509,11.388],[-46.967,-107.486]],"v":[[125.509,71.112],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-9.509,76.388],[74.033,-0.611]],"o":[[26.991,165.888],[-12.967,-113.486]],"v":[[146.509,-65.388],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-3.591,66.715],[93.702,-33.599]],"o":[[45.409,68.715],[-23.798,-99.599]],"v":[[124.591,-101.715],[86.798,98.599]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-28.673,100.041],[18.871,-121.211]],"o":[[23.327,45.041],[-11.272,-74.766]],"v":[[102.673,-138.041],[146.129,39.211]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-86.521,83.201],[0.04,-46.057]],"o":[[-27.521,44.701],[-27.46,-39.557]],"v":[[104.521,-178.201],[147.46,-53.443]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-78.37,43.361],[-9.836,-38.494]],"o":[[-9.37,31.361],[-29.009,-15.779]],"v":[[124.37,-218.361],[102.836,-129.506]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-12.932,20.02],[8.931,-18.574]],"o":[[5.568,15.02],[-11.069,-5.574]],"v":[[124.432,-260.02],[122.069,-212.426]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[1.395,4.24],[0.439,-5.367]],"o":[[4.195,4.04],[-8.094,-2.167]],"v":[[109.605,-288.64],[115.494,-276.366]],"c":true}]},{"t":28,"s":[{"i":[[1.395,4.24],[0.439,-5.367]],"o":[[4.195,4.04],[-8.094,-2.167]],"v":[[121.605,-432.64],[127.494,-420.366]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-8.509,21.888],[109.533,-35.486]],"o":[[30.991,64.388],[-33.467,-95.486]],"v":[[-84.491,-110.888],[-138.033,56.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[22.118,49.473],[122.558,9.2]],"o":[[68.618,31.473],[6.558,-41.3]],"v":[[-88.618,-143.473],[-141.058,-21.7]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[1.746,26.057],[53.583,-9.114]],"o":[[69.246,2.557],[28.083,-45.614]],"v":[[-106.746,-172.057],[-96.083,-90.386]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-2.806,9.641],[17.287,-6.213]],"o":[[11.944,10.391],[-8.963,-20.463]],"v":[[-100.944,-206.641],[-102.787,-160.537]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[-0.532,1.368],[6.846,-2.218]],"o":[[1.937,4.024],[-2.092,-5.968]],"v":[[-95.281,-232.141],[-96.877,-218.399]],"c":true}]},{"t":16,"s":[{"i":[[-0.532,1.368],[6.846,-2.218]],"o":[[1.937,4.024],[-2.092,-5.968]],"v":[[-97.281,-426.141],[-98.877,-412.399]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":7,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[3.874,33.283],[9.817,-3.451]],"o":[[36.874,61.283],[-1.183,-15.951]],"v":[[-146.374,-134.783],[-180.817,-70.549]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-0.126,16.712],[9.817,-3.451]],"o":[[22.088,11.14],[-1.755,-10.237]],"v":[[-156.088,-168.64],[-145.102,-130.049]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[-3.126,4.283],[9.817,-3.451]],"o":[[4.874,3.783],[-2.183,-5.951]],"v":[[-152.874,-195.783],[-156.817,-182.549]],"c":true}]},{"t":8,"s":[{"i":[[-3.126,4.283],[9.817,-3.451]],"o":[[4.874,3.783],[-2.183,-5.951]],"v":[[-156.874,-419.783],[-160.817,-406.549]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":8,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-14.009,113.888],[9.033,-147.486]],"o":[[54.491,45.388],[-75.467,-0.611]],"v":[[-160.991,-29.888],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-32.96,22.468],[39.256,-132.515]],"o":[[9.54,49.968],[-101.244,-47.015]],"v":[[-141.04,-64.468],[-79.756,102.515]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-6.912,19.047],[-5.522,-58.543]],"o":[[45.088,50.047],[-57.022,-89.543]],"v":[[-121.088,-99.047],[-141.978,53.043]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0.17,68.493],[6.251,-19.805]],"o":[[67.67,54.493],[-2.749,-44.305]],"v":[[-115.67,-138.993],[-160.251,-25.195]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[2.752,16.439],[33.524,-10.567]],"o":[[46.252,26.939],[13.524,-31.067]],"v":[[-121.252,-178.939],[-123.524,-94.933]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-7.345,7.385],[14.19,-7.615]],"o":[[22.798,13.885],[-2.31,-14.115]],"v":[[-118.655,-218.885],[-120.19,-175.885]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[-2.042,3.844],[4.939,-3.65]],"o":[[5.208,4.094],[-3.144,-0.025]],"v":[[-116.708,-248.844],[-117.689,-236.6]],"c":true}]},{"t":24,"s":[{"i":[[-2.042,3.844],[4.939,-3.65]],"o":[[5.208,4.094],[-3.144,-0.025]],"v":[[-120.708,-416.844],[-121.689,-404.6]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[0,76.641]},"e":{"a":0,"k":[0,-306.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":28,"st":0,"bm":0}]},{"id":"comp_8","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"core","sr":1,"ks":{"p":{"a":0,"k":[260.85,494.271,0]},"a":{"a":0,"k":[-51.438,222.044,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[18.197,16.546],[-41.985,0.511],[69.85,68.547]],"o":[[-53.725,57.166],[41.194,-0.501],[-14.905,76.809]],"v":[[-60.485,-15.773],[2.147,99.863],[27.617,-48.036]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-0.979,9.447],[-38.363,0.467],[70.747,84.813]],"o":[[-59.685,60.711],[37.64,-0.458],[-1.585,72.664]],"v":[[-57.538,-18.917],[1.813,98.207],[7.7,-34.227]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-1.042,10.05],[-40.812,0.497],[123.505,80.759]],"o":[[-74.767,73.603],[40.043,-0.487],[-2.738,60.921]],"v":[[-53.637,-37.717],[1.022,99.894],[10.207,-27.711]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-1.085,10.471],[-42.522,0.518],[97.518,74.59]],"o":[[-66.155,67.291],[41.72,-0.508],[-24.524,76.665]],"v":[[-54.263,-34.227],[0.703,99.483],[31.236,-22.892]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-1.154,11.128],[-37.389,-2.202],[40.213,84]],"o":[[-57.827,61.531],[44.266,2.607],[-37.787,100]],"v":[[-49.168,-20.338],[2.1,101.975],[50.498,-47.227]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-1.21,11.676],[-47.417,0.577],[79.061,75.843]],"o":[[-87.565,74.165],[46.523,-0.566],[-12.086,67.113]],"v":[[-25.098,-59.555],[1.837,97.942],[45.651,-47.629]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-1.123,10.831],[-43.984,0.535],[60.542,59.825]],"o":[[-61.466,81.592],[43.155,-0.525],[-21.739,23.543]],"v":[[-17.822,-70.092],[1.626,101.757],[35.746,-6.212]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-1.169,11.276],[-45.792,0.557],[69.607,68.523]],"o":[[-61.294,73.648],[44.929,-0.547],[-11.672,64.813]],"v":[[-35.095,-15.72],[-2.741,104.632],[32.82,-31.227]],"c":true}]},{"t":32,"s":[{"i":[[18.197,16.546],[-41.985,0.511],[69.85,68.547]],"o":[[-53.725,57.166],[41.194,-0.501],[-14.905,76.809]],"v":[[-60.485,-15.773],[2.147,99.863],[27.617,-48.036]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.121,0.996,0.996,0.898,0.54,0.998,0.951,0.598,0.808,1,0.906,0.298]}},"s":{"a":0,"k":[2.235,29.653]},"e":{"a":0,"k":[6.469,-84.419]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 4","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"second","sr":1,"ks":{"p":{"a":0,"k":[258,398,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[112.946,110.839]],"o":[[-86.872,92.436],[66.609,-0.811],[-1.054,155.839]],"v":[[-75.128,-84.436],[1.891,99.311],[22.054,-139.839]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[125.196,150.089]],"o":[[-105.622,107.436],[66.609,-0.811],[-2.804,128.589]],"v":[[-57.128,-106.186],[1.891,99.311],[-10.696,-138.589]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[205.446,134.339]],"o":[[-124.372,122.436],[66.609,-0.811],[-4.554,101.339]],"v":[[-39.128,-127.936],[1.891,99.311],[-9.446,-86.339]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[155.696,119.089]],"o":[[-105.622,107.436],[66.609,-0.811],[-2.804,128.589]],"v":[[-57.128,-106.186],[1.891,99.311],[20.304,-88.089]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[132.946,124.839]],"o":[[-86.872,92.436],[66.609,-0.811],[-1.054,155.839]],"v":[[-75.128,-84.436],[1.891,99.311],[46.054,-130.839]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[113.196,108.589]],"o":[[-125.372,106.186],[66.609,-0.811],[-17.304,96.089]],"v":[[-16.628,-126.186],[1.891,99.311],[50.304,-99.089]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[93.446,92.339]],"o":[[-94.872,125.936],[66.609,-0.811],[-33.554,36.339]],"v":[[-28.128,-165.936],[1.891,99.311],[54.554,-67.339]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[103.196,101.589]],"o":[[-90.872,109.186],[66.609,-0.811],[-17.304,96.089]],"v":[[-41.628,-73.186],[1.891,99.311],[38.304,-103.589]],"c":true}]},{"t":32,"s":[{"i":[[-1.733,16.717],[-67.889,0.826],[112.946,110.839]],"o":[[-86.872,92.436],[66.609,-0.811],[-1.054,155.839]],"v":[[-75.128,-84.436],[1.891,99.311],[22.054,-139.839]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.914,0.239,0.269,1,0.739,0.22,1,1,0.565,0.2]}},"s":{"a":0,"k":[1.76,83.087]},"e":{"a":0,"k":[-8.18,-233.581]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"main 2","sr":1,"ks":{"p":{"a":0,"k":[256,384,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[22.772,0],[-46.805,37.19]],"o":[[-26.404,0],[50.185,39.876]],"v":[[-0.195,113.443],[-0.195,61.306]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[218.597,-71.585],[-101.403,40.181]],"o":[[-122.702,-55.863],[-27.403,50.181]],"v":[[-0.597,113.585],[-0.597,-46.181]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[199.943,-71.044],[38.799,75.924]],"o":[[-170.851,-83.794],[80.799,31.924]],"v":[[-0.799,113.655],[-0.799,-99.924]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[259,-94.726],[-62,81.667]],"o":[[-219,-111.726],[-8,98.667]],"v":[[-1,113.726],[-1,-153.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[134.519,-83.794],[18.759,153.246]],"o":[[-355.481,-139.794],[72.759,69.246]],"v":[[0.481,97.794],[-24.759,-201.246]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[186.037,-45.863],[-99.482,95.825]],"o":[[-135.963,-78.863],[-3.482,107.825]],"v":[[1.963,81.863],[-26.518,-251.825]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[27.037,-76.931],[27.278,52.404]],"o":[[-69.963,-68.931],[64.278,42.404]],"v":[[2.963,-132.069],[-15.278,-290.404]],"c":true}]},{"t":32,"s":[{"i":[[0.866,0],[-1.78,1.414]],"o":[[-1.004,0],[1.908,1.516]],"v":[[3.963,-346],[3.963,-347.983]],"c":true}]}]},"nm":"Path 11","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[116.786,0],[11,144.667]],"o":[[-317,-127.726],[249,149.667]],"v":[[-1,113.726],[-1,-153.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[120.519,-22.794],[-202.241,204.246]],"o":[[-143.695,27.178],[23.759,129.246]],"v":[[0.481,97.794],[0.241,-202.246]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[168.037,-36.863],[3.518,139.825]],"o":[[-193.963,-76.863],[211.518,139.825]],"v":[[1.963,81.863],[-26.518,-251.825]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[78.037,-55.931],[-63.722,35.404]],"o":[[-42.963,-53.931],[-21.722,38.404]],"v":[[2.963,-132.069],[27.722,-295.404]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0.866,0],[-1.78,1.414]],"o":[[-1.004,0],[1.908,1.516]],"v":[[3.963,-346],[3.963,-347.983]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":17,"s":[{"i":[[22.772,0],[-46.805,37.19]],"o":[[-26.404,0],[50.185,39.876]],"v":[[-0.195,113.443],[-0.195,61.306]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[224.785,-62.65],[-21.215,44.341]],"o":[[-196.215,-40.65],[178.785,45.341]],"v":[[-0.785,113.65],[-35.785,-108.341]],"c":true}]},{"t":32,"s":[{"i":[[116.786,0],[11,144.667]],"o":[[-317,-127.726],[249,149.667]],"v":[[-1,113.726],[-1,-153.667]],"c":true}]}]},"nm":"Path 13","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[150.97,-29.436],[-7.904,50.282],[-29.987,22.93],[31.343,22.513]],"o":[[-171.03,-96.436],[20.614,30.124],[20.013,43.93],[96.97,73.457]],"v":[[0.03,113.436],[-84.471,-110.907],[-1.013,-82.93],[77.03,-95.457]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[263.97,-70.436],[41.784,65.595],[-24.463,-11.448],[-17.438,27.049]],"o":[[-288.03,-110.436],[28.054,20.833],[28.268,13.229],[-17.53,86.457]],"v":[[0.03,113.436],[-88.471,-144.407],[-8.648,-91.164],[61.53,-104.457]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[150.97,-29.436],[-32.029,33.907],[-26.789,24.397],[-10.737,32.946]],"o":[[-259.03,-106.436],[23.879,14.802],[25.211,25.397],[-2.03,120.457]],"v":[[0.03,113.436],[-60.471,-110.407],[27.789,-112.897],[118.03,-147.457]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[218.97,-95.436],[-14.029,92.657],[-25.376,-0.07],[-14.356,48.358]],"o":[[-215.03,-101.436],[18.775,49.248],[29.323,0.081],[-14.779,118.707]],"v":[[0.03,113.436],[-38.971,-132.657],[31.387,-61.673],[103.28,-138.707]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[286.97,-161.436],[36.471,78.407],[-18.903,53.783],[-17.706,24.637]],"o":[[-171.03,-96.436],[27.358,41.478],[30.097,36.783],[-27.529,116.957]],"v":[[0.03,113.436],[-40.471,-166.407],[29.903,-158.783],[103.53,-179.957]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[245.97,-124.769],[28.471,22.365],[-36.809,25.898],[5.725,49.721]],"o":[[-86.03,-19.436],[-9.524,-43.439],[42.535,-29.926],[44.679,76.748]],"v":[[0.03,113.436],[-126.471,65.635],[-66.412,-30.863],[18.321,-138.748]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[204.97,-88.102],[14.284,64.345],[-5.622,33.725],[-11.854,28.702]],"o":[[-178.03,-22.436],[27.474,40.898],[20.378,16.725],[116.887,36.54]],"v":[[0.03,113.436],[-122.471,-54.324],[-77.378,-76.725],[-34.887,-117.54]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[163.97,-51.436],[3.19,57.313],[-43.708,-7.429],[-12.405,-37.168]],"o":[[-189.03,-2.436],[27.532,40.608],[50.507,8.584],[-25.405,42.832]],"v":[[0.03,113.436],[-103.471,-80.782],[8.103,-95.227],[146.405,-9.832]],"c":true}]},{"t":32,"s":[{"i":[[150.97,-29.436],[-7.904,50.282],[-29.987,22.93],[31.343,22.513]],"o":[[-171.03,-96.436],[20.614,30.124],[20.013,43.93],[96.97,73.457]],"v":[[0.03,113.436],[-84.471,-110.907],[-1.013,-82.93],[77.03,-95.457]],"c":true}]}]},"nm":"Path 12","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-49.509,44.388],[123.533,-38.986]],"o":[[-17.509,54.388],[-101.467,-137.986]],"v":[[138.509,-106.388],[72.467,102.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-54.652,117.814],[96.514,-88.952]],"o":[[39.848,36.814],[-55.486,-82.702]],"v":[[122.902,-122.064],[135.986,59.452]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-69.795,121.239],[25.494,-72.919]],"o":[[-6.795,25.239],[-9.506,-27.419]],"v":[[118.295,-144.739],[157.506,-8.081]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-47.393,78.823],[5.657,-37.203]],"o":[[-20.938,53.232],[-20.343,-15.203]],"v":[[125.938,-167.732],[170.343,-77.797]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-14.581,12.011],[1.034,-14.274]],"o":[[-8.51,26.743],[-24.109,4.012]],"v":[[132.081,-191.511],[118.466,-138.726]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[-8.189,4.595],[5.817,-6.951]],"o":[[0.811,6.877],[-3.683,-1.451]],"v":[[129.689,-209.345],[131.183,-194.049]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,113.375],[0,113.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-86.754,-66.493],[91.266,-0.305]],"o":[[3.246,27.507],[-58.234,-74.493]],"v":[[143.754,52.493],[0.734,113.68]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-85.509,72.388],[223.533,-7.986]],"o":[[-30.509,37.388],[-116.467,-148.986]],"v":[[157.509,-90.388],[1.467,113.986]],"c":true}]},{"t":32,"s":[{"i":[[-49.509,44.388],[123.533,-38.986]],"o":[[-17.509,54.388],[-101.467,-137.986]],"v":[[138.509,-106.388],[72.467,102.986]],"c":true}]}]},"nm":"Path 10","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-264.439,24.845],[-27.183,-36.451]],"o":[[-162.439,62.845],[-14.183,-2.451]],"v":[[74.439,-222.845],[77.183,-95.549]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[{"i":[[-14.552,17.164],[-84.524,-59.247]],"o":[[4.448,44.164],[-177.524,-58.247]],"v":[[76.552,-245.164],[28.524,-132.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-46.666,12.482],[4.635,-22.542]],"o":[[58.334,48.482],[-2.365,-44.042]],"v":[[60.666,-261.482],[-6.135,-173.958]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[15.828,22.8],[39.365,-19.981]],"o":[[49.828,24.8],[12.365,-29.981]],"v":[[40.172,-270.8],[41.635,-201.019]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-12.679,33.618],[0.739,-12.776]],"o":[[16.321,12.118],[-23.904,-12.919]],"v":[[19.679,-280.118],[73.404,-244.081]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11,"s":[{"i":[[-10.439,3.845],[-2.183,-5.451]],"o":[[2.561,7.845],[-14.183,-2.451]],"v":[[-0.561,-309.845],[6.183,-290.549]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,113.375],[0,113.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-132.754,-64.493],[63.266,-0.305]],"o":[[-3.754,90.507],[-6.484,-56.743]],"v":[[147.754,12.493],[0.734,113.68]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[21.491,112.388],[126.533,-0.611]],"o":[[224.491,166.388],[-116.467,-148.986]],"v":[[19.509,-136.388],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-135.819,196.207],[121.294,-153.141]],"o":[[1.181,45.207],[-18.706,-90.141]],"v":[[-5.181,-172.207],[113.706,89.141]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-234.629,86.526],[12.555,-77.796]],"o":[[-114.629,48.526],[-16.445,-46.296]],"v":[[13.629,-194.526],[144.445,-6.204]],"c":true}]},{"t":32,"s":[{"i":[[-264.439,24.845],[-27.183,-36.451]],"o":[[-162.439,62.845],[-14.183,-2.451]],"v":[[74.439,-222.845],[77.183,-95.549]],"c":true}]}]},"nm":"Path 9","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-22.717,11.53],[16.682,-16.21]],"o":[[4.283,8.53],[5.682,-20.71]],"v":[[101.967,-233.03],[88.818,-188.29]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[-6.346,4.147],[-0.366,-3.08]],"o":[[5.543,2.722],[-7.337,1.322]],"v":[[88.467,-258.53],[94.818,-248.54]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,113.375],[0,113.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-41.254,-51.493],[37.016,-0.305]],"o":[[-39.254,39.882],[-6.484,-56.743]],"v":[[127.254,73.493],[0.734,113.68]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-9.509,76.388],[74.033,-0.611]],"o":[[74.491,136.388],[-12.967,-113.486]],"v":[[125.509,-54.388],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-14.3,65.117],[72.063,-37.031]],"o":[[78.7,80.617],[-49.687,-74.281]],"v":[[100.3,-82.617],[86.937,99.031]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-19.092,53.845],[64.593,-56.075]],"o":[[34.908,50.845],[-86.407,-35.075]],"v":[[71.092,-114.845],[102.407,15.075]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-74.384,65.24],[3.789,-34.954]],"o":[[-8.384,53.24],[-113.211,7.046]],"v":[[64.384,-155.24],[122.211,-37.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-91.175,30.135],[-24.514,-25.332]],"o":[[-16.175,23.135],[-53.764,-6.832]],"v":[[92.175,-192.135],[75.514,-110.668]],"c":true}]},{"t":32,"s":[{"i":[[-22.717,11.53],[16.682,-16.21]],"o":[[4.283,8.53],[5.682,-20.71]],"v":[[101.967,-233.03],[88.818,-188.29]],"c":true}]}]},"nm":"Path 8","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-46.009,12.388],[74.033,-0.611]],"o":[[-4.509,11.388],[-46.967,-107.486]],"v":[[125.509,71.112],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-9.509,76.388],[74.033,-0.611]],"o":[[26.991,165.888],[-12.967,-113.486]],"v":[[146.509,-65.388],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-3.591,66.715],[93.702,-33.599]],"o":[[45.409,68.715],[-23.798,-99.599]],"v":[[124.591,-101.715],[86.798,98.599]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-28.673,100.041],[18.871,-121.211]],"o":[[23.327,45.041],[-11.272,-74.766]],"v":[[102.673,-138.041],[146.129,39.211]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-86.521,83.201],[0.04,-46.057]],"o":[[-27.521,44.701],[-27.46,-39.557]],"v":[[104.521,-178.201],[147.46,-53.443]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-78.37,43.361],[-9.836,-38.494]],"o":[[-9.37,31.361],[-29.009,-15.779]],"v":[[124.37,-218.361],[102.836,-129.506]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-12.932,20.02],[8.931,-18.574]],"o":[[5.568,15.02],[-11.069,-5.574]],"v":[[124.432,-260.02],[122.069,-212.426]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[1.395,4.24],[0.439,-5.367]],"o":[[4.195,4.04],[-8.094,-2.167]],"v":[[109.605,-288.64],[115.494,-276.366]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,113.375],[0,113.375]],"c":true}]},{"t":32,"s":[{"i":[[-46.009,12.388],[74.033,-0.611]],"o":[[-4.509,11.388],[-46.967,-107.486]],"v":[[125.509,71.112],[1.467,113.986]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,113.375],[0,113.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[9.996,54.507],[18.766,-61.243]],"o":[[81.496,-58.493],[-74.234,-0.305]],"v":[[-144.496,36.493],[0.734,113.68]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-160.009,110.388],[37.533,-122.486]],"o":[[30.991,64.388],[-148.467,-0.611]],"v":[[-60.991,-109.388],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-28.931,37.131],[101.093,-74.23]],"o":[[26.297,53.685],[-138.907,-85.98]],"v":[[-39.069,-135.131],[-92.093,95.98]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[31.646,69.873],[164.653,-25.974]],"o":[[21.603,42.982],[-10.847,-128.474]],"v":[[-41.146,-166.873],[-145.653,27.974]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[51.14,50.249],[196.48,-6.735]],"o":[[31.14,23.249],[83.48,-70.235]],"v":[[-68.14,-193.749],[-119.48,-62.265]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[2.634,39.625],[104.807,-64.995]],"o":[[12.214,21.576],[27.307,-41.495]],"v":[[-85.134,-224.625],[-44.307,-124.505]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-5.015,14.537],[29.527,-18.684]],"o":[[-0.515,20.037],[-20.473,-28.184]],"v":[[-63.485,-252.537],[-76.027,-200.316]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":31,"s":[{"i":[[-2.751,4.345],[10.817,-1.201]],"o":[[3.999,2.845],[1.067,-3.951]],"v":[[-54.249,-276.095],[-57.817,-259.799]],"c":true}]},{"t":32,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,113.375],[0,113.375]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":8,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-8.509,21.888],[109.533,-35.486]],"o":[[30.991,64.388],[-33.467,-95.486]],"v":[[-84.491,-110.888],[-138.033,56.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[22.118,49.473],[122.558,9.2]],"o":[[68.618,31.473],[6.558,-41.3]],"v":[[-88.618,-143.473],[-141.058,-21.7]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[1.746,26.057],[53.583,-9.114]],"o":[[69.246,2.557],[28.083,-45.614]],"v":[[-106.746,-172.057],[-96.083,-90.386]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-2.806,9.641],[17.287,-6.213]],"o":[[11.944,10.391],[-8.963,-20.463]],"v":[[-100.944,-206.641],[-102.787,-160.537]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[-0.532,1.368],[6.846,-2.218]],"o":[[1.937,4.024],[-2.092,-5.968]],"v":[[-95.281,-232.141],[-96.877,-218.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,113.375],[0,113.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[27.996,30.007],[18.766,-61.243]],"o":[[67.996,-36.993],[-98.734,-0.305]],"v":[[-125.996,67.993],[0.734,113.68]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-59.009,91.388],[37.533,-122.486]],"o":[[30.991,64.388],[-130.467,-0.611]],"v":[[-123.991,-53.388],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-33.759,56.638],[73.533,-78.986]],"o":[[30.991,64.388],[-111.217,-27.486]],"v":[[-104.241,-82.138],[-77.783,97.986]],"c":true}]},{"t":32,"s":[{"i":[[-8.509,21.888],[109.533,-35.486]],"o":[[30.991,64.388],[-33.467,-95.486]],"v":[[-84.491,-110.888],[-138.033,56.986]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":9,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[3.874,33.283],[9.817,-3.451]],"o":[[36.874,61.283],[-1.183,-15.951]],"v":[[-146.374,-134.783],[-180.817,-70.549]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-0.126,16.712],[9.817,-3.451]],"o":[[22.088,11.14],[-1.755,-10.237]],"v":[[-156.088,-168.64],[-145.102,-130.049]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[-3.126,4.283],[9.817,-3.451]],"o":[[4.874,3.783],[-2.183,-5.951]],"v":[[-152.874,-195.783],[-156.817,-182.549]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,113.375],[0,113.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[15.496,26.632],[4.516,-73.743]],"o":[[72.746,-25.993],[-37.734,-0.305]],"v":[[-106.496,84.743],[0.734,113.68]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[32.991,88.388],[46.533,-190.986]],"o":[[43.991,27.388],[-75.467,-0.611]],"v":[[-175.991,-1.388],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[7.991,54.888],[48.283,-114.236]],"o":[[34.991,53.388],[-57.467,-13.798]],"v":[[-185.491,-36.888],[-72.283,105.236]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-17.009,21.388],[-35.967,-67.486]],"o":[[64.991,42.388],[-39.467,-26.986]],"v":[[-179.991,-74.388],[-130.033,65.486]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-21.318,2.586],[43.425,-41.969]],"o":[[3.682,27.586],[-35.575,-40.469]],"v":[[-156.182,-99.086],[-170.925,4.469]],"c":true}]},{"t":32,"s":[{"i":[[3.874,33.283],[9.817,-3.451]],"o":[[36.874,61.283],[-1.183,-15.951]],"v":[[-146.374,-134.783],[-180.817,-70.549]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":10,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-14.009,113.888],[9.033,-147.486]],"o":[[54.491,45.388],[-75.467,-0.611]],"v":[[-160.991,-29.888],[1.467,113.986]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-32.96,22.468],[39.256,-132.515]],"o":[[9.54,49.968],[-101.244,-47.015]],"v":[[-141.04,-64.468],[-79.756,102.515]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-6.912,19.047],[-5.522,-58.543]],"o":[[45.088,50.047],[-57.022,-89.543]],"v":[[-121.088,-99.047],[-141.978,53.043]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0.17,68.493],[6.251,-19.805]],"o":[[67.67,54.493],[-2.749,-44.305]],"v":[[-115.67,-138.993],[-160.251,-25.195]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[2.752,16.439],[33.524,-10.567]],"o":[[46.252,26.939],[13.524,-31.067]],"v":[[-121.252,-178.939],[-123.524,-94.933]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-7.345,7.385],[14.19,-7.615]],"o":[[22.798,13.885],[-2.31,-14.115]],"v":[[-118.655,-218.885],[-120.19,-175.885]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[-2.042,3.844],[4.939,-3.65]],"o":[[5.208,4.094],[-3.144,-0.025]],"v":[[-116.708,-248.844],[-117.689,-236.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,113.375],[0,113.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[27.996,23.757],[4.516,-73.743]],"o":[[27.246,22.694],[-37.734,-0.305]],"v":[[-118.996,80.243],[0.734,113.68]],"c":true}]},{"t":32,"s":[{"i":[[-14.009,113.888],[9.033,-147.486]],"o":[[54.491,45.388],[-75.467,-0.611]],"v":[[-160.991,-29.888],[1.467,113.986]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[0,76.641]},"e":{"a":0,"k":[0,-306.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0}]},{"id":"comp_9","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","parent":2,"sr":1,"ks":{"p":{"a":0,"k":[-77.334,-175.69,0]},"a":{"a":0,"k":[-77.334,-175.69,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.988,3.181],[-19.8,-5.052]],"o":[[-4.889,-7.822],[26.133,6.668]],"v":[[19.329,271.399],[24.3,294.213]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[4.186,6.698],[-41.687,-10.636]],"o":[[-10.293,-16.469],[55.022,14.038]],"v":[[7.834,173.427],[18.299,221.462]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[14.361,7.522],[-30.261,-6.497]],"o":[[-24.248,-12.701],[34.72,7.454]],"v":[[35.139,128.628],[12.401,185.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[32.578,61.344],[28.072,72.523]],"c":true}]},{"t":19,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[20.578,-416.656],[16.072,-405.477]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[33.144,5.233],[-24.721,-0.221]],"o":[[-35.022,-5.53],[27.911,0.249]],"v":[[19.555,225.935],[15.319,283.751]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[29.02,-23.268],[-29.148,15.285]],"o":[[-36.827,29.528],[27.206,-14.266]],"v":[[18.827,100.658],[25.794,178.294]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[36.864,-1.646],[-34.889,28.965]],"o":[[-35.938,1.604],[46.08,13.495]],"v":[[1.709,52.646],[-0.595,87.535]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[19.216,-6.659],[-24.954,25.786]],"o":[[-28.004,9.704],[30.5,0]],"v":[[16.732,-8.349],[24.773,17.714]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[12.002,-2.4],[-12.348,2.646]],"o":[[-16.253,3.251],[13.442,-2.88]],"v":[[25.798,-28.588],[29.158,-15.146]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":31,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[47.81,-67.072],[-25.632,9.313]],"o":[[-93.246,48.698],[25.632,-9.313]],"v":[[-74.754,121.302],[-13.132,182.313]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[16.302,-91.514],[22.297,-55.155]],"o":[[-51.846,41.486],[63.795,-25.293]],"v":[[-77.654,-43.486],[-14.297,37.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[65.508,-110.844],[-62.172,37.016]],"o":[[-46.719,4.672],[111.508,-8.984]],"v":[[-77.508,-81.156],[-31.508,-13.016]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[44.392,-34.904],[-43.656,10.635]],"o":[[-57.534,8.63],[43.656,-10.635]],"v":[[-64.892,-151.596],[-43.156,-90.865]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[45.056,-54.944],[-36.611,29.426]],"o":[[-75.944,-2.944],[74.944,33.426]],"v":[[-55.556,-184.556],[-45.389,-150.426]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[14.5,-3],[-14.25,5.5]],"o":[[-14.5,3],[14.25,-5.5]],"v":[[-44.875,-250.5],[-40.375,-234]],"c":true}]},{"t":36,"s":[{"i":[[1.16,-0.24],[-1.14,0.44]],"o":[[-1.16,0.24],[1.14,-0.44]],"v":[[-45.92,-278.26],[-45.56,-276.94]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[38.071,2.586],[-51.674,3.841],[-14.328,5.136]],"o":[[-71.639,-4.866],[8.07,11.41],[22.966,-8.233]],"v":[[122.074,242.366],[89.943,278.535],[130.995,288.864]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[20.869,-12.454],[-146.79,82.022],[-12.062,30.104]],"o":[[-112.454,-96.052],[15.663,4.937],[27.265,-8.079]],"v":[[157.954,39.052],[123.79,100.978],[169.735,69.137]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[74.55,-67.464],[-92.612,89.3],[0.78,32.797]],"o":[[-175.95,-43.988],[-28.612,-55.2],[57.28,16.797]],"v":[[109.45,-30.036],[99.612,30.2],[150.72,14.703]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[79.321,-50.136],[-20.934,-21.241],[-26.364,9.073]],"o":[[-58.179,-42.136],[17.914,18.177],[76.169,29.621]],"v":[[102.679,-76.364],[46.086,-27.677],[111.331,-23.621]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[62.617,-21.138],[-8.337,-17.814],[-11.971,0.122]],"o":[[-29.444,-9.815],[3.681,7.865],[67.802,31.907]],"v":[[103.66,-125.362],[70.352,-89.569],[93.689,-75.907]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.334,0],[-4.841,-5.763],[-4.486,0.179]],"o":[[-13.412,0],[2.137,2.544],[14.647,-0.586]],"v":[[106.839,-168.5],[96.133,-153.01],[106.253,-148.873]],"c":true}]},{"t":36,"s":[{"i":[[5.5,0],[-1.377,-1.639],[-1.276,0.051]],"o":[[-3.815,0],[0.608,0.724],[4.167,-0.167]],"v":[[103.5,-198.125],[100.454,-193.718],[103.333,-192.542]],"c":true}]}]},"nm":"Path 3","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392216701,0.972549079446,0.949019667682,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[280.666,670.31,0]},"a":{"a":0,"k":[24.666,286.31,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[20,20,100]},{"t":12,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.988,3.181],[-19.8,-5.052]],"o":[[-4.889,-7.822],[26.133,6.668]],"v":[[19.329,271.399],[24.3,294.213]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[6.101,9.761],[-60.75,-15.5]],"o":[[-15,-24],[80.183,20.458]],"v":[[0,163],[15.25,233]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[21,11],[-44.25,-9.5]],"o":[[-35.458,-18.573],[50.771,10.9]],"v":[[35,125],[1.75,207.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[32.578,61.344],[28.072,72.523]],"c":true}]},{"t":19,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[20.578,-416.656],[16.072,-405.477]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[66.5,10.5],[-49.599,-0.443]],"o":[[-70.267,-11.095],[56,0.5]],"v":[[20.5,199.5],[12,315.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[43.734,-35.066],[-43.928,23.035]],"o":[[-55.5,44.5],[41,-21.5]],"v":[[15.5,72.5],[26,189.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[56,-2.5],[-53,44]],"o":[[-54.592,2.437],[70,20.5]],"v":[[-4.5,51.5],[-8,104.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[34.653,-12.008],[-45,46.5]],"o":[[-50.5,17.5],[55,0]],"v":[[9.5,-11.5],[24,35.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[25,-5],[-25.722,5.512]],"o":[[-33.855,6.771],[28,-6]],"v":[[23,-29],[30,-1]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":31,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[69.5,-97.5],[-113.5,45]],"o":[[-66.5,8.5],[86.092,-34.134]],"v":[[-90.5,124.5],[-22,227]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[22,-123.5],[-113.5,45]],"o":[[-41.5,7],[86.092,-34.134]],"v":[[-91.5,-56.5],[-6,53]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[29.5,-125],[-86.5,51.5]],"o":[[-65,6.5],[164.5,11.5]],"v":[[-87.5,-87],[-23.5,-7.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[71,-76.5],[-34.5,28]],"o":[[-90,13.5],[106.5,47.5]],"v":[[-68.5,-157.5],[-34.5,-62.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[48.5,-66],[-34.5,28]],"o":[[-81,3],[106.5,47.5]],"v":[[-57.5,-194],[-53,-132]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[29,-6],[-28.5,11]],"o":[[-29,6],[28.5,-11]],"v":[[-46,-251.5],[-37,-218.5]],"c":true}]},{"t":36,"s":[{"i":[[1.16,-0.24],[-1.14,0.44]],"o":[[-1.16,0.24],[1.14,-0.44]],"v":[[-45.92,-278.26],[-45.56,-276.94]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[70.414,4.783],[-95.574,7.104],[-26.5,9.5]],"o":[[-132.5,-9],[14.926,21.104],[42.478,-15.228]],"v":[[116,239],[56.574,305.896],[132.5,325]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[31,-18.5],[-168.751,36.203],[-17.918,44.718]],"o":[[-248,-61.5],[23.267,7.334],[40.5,-12]],"v":[[167,24],[116.251,135.297],[184.5,88]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[101,-20.5],[-133.016,57.816],[-23.678,31.225]],"o":[[-222,-55.5],[17.83,12.614],[53,-13]],"v":[[114.5,-57.5],[94.516,65.184],[159,45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[108,-21],[-25.045,-34.694],[-37.826,13.017]],"o":[[-46.826,-19.424],[11.058,15.318],[78.5,26]],"v":[[96,-99.5],[40.628,-7.406],[112,2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[78.5,-26.5],[-10.452,-22.332],[-15.008,0.153]],"o":[[-36.913,-12.304],[4.615,9.86],[85,40]],"v":[[101.5,-126.5],[59.744,-81.628],[89,-64.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[33,0],[-8.262,-9.837],[-7.657,0.306]],"o":[[-22.893,0],[3.648,4.343],[25,-1]],"v":[[103,-170],[84.726,-143.561],[102,-136.5]],"c":true}]},{"t":36,"s":[{"i":[[5.5,0],[-1.377,-1.639],[-1.276,0.051]],"o":[[-3.815,0],[0.608,0.724],[4.167,-0.167]],"v":[[103.5,-198.125],[100.454,-193.718],[103.333,-192.542]],"c":true}]}]},"nm":"Path 3","hd":false},{"ty":"fl","c":{"a":0,"k":[0.945098099054,0.90588241278,0.792156922583,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.917647118662,0.847058883368,0.721568627451,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":36,"st":0,"bm":0}]},{"id":"comp_10","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 12","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[256,743,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[40,40,100]},{"t":8,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":124,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"02 fire middle end","parent":1,"refId":"comp_11","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":64,"op":120,"st":64,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"02 fire middle cycle","parent":1,"refId":"comp_12","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"02 fire middle cycle","parent":1,"refId":"comp_12","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"02 fire middle flame2","parent":1,"refId":"comp_13","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":56,"op":84,"st":56,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"02 fire middle flame2","parent":1,"refId":"comp_13","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":40,"op":68,"st":40,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"02 fire middle flame2","parent":1,"refId":"comp_13","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":8,"op":36,"st":8,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"02 fire middle center (f0)","parent":1,"refId":"comp_14","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":32,"op":52,"st":32,"bm":0},{"ddd":0,"ind":11,"ty":0,"nm":"02 fire middle center (f0)","parent":1,"refId":"comp_14","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":20,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":48,"op":80,"st":48,"bm":0},{"ddd":0,"ind":13,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":16,"op":48,"st":16,"bm":0},{"ddd":0,"ind":14,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":64,"op":96,"st":64,"bm":0},{"ddd":0,"ind":15,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":16,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":0,"nm":"02 fire middle cycle outlines","parent":1,"refId":"comp_16","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":18,"ty":0,"nm":"02 fire middle cycle outlines","parent":1,"refId":"comp_16","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":19,"ty":0,"nm":"02 fire middle end outl","parent":1,"refId":"comp_17","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":64,"op":120,"st":64,"bm":0}]},{"id":"comp_11","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"core","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":16,"s":[100]},{"t":24,"s":[0]}]},"p":{"a":0,"k":[274.85,745.271,0]},"a":{"a":0,"k":[-51.438,222.044,0]},"s":{"a":0,"k":[105,95,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[39.58,85.812],[-17.902,-31.376],[35.171,-7.543],[-2.194,65.424]],"o":[[43.523,37.586],[39.788,69.735],[-35.171,7.543],[52.37,41.33]],"v":[[-66.479,-127.536],[10.277,-47.479],[1.953,96.976],[-79.778,6.061]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-15.574,60.55],[-15.66,-19.896],[25.722,9.327],[-25.407,35.637]],"o":[[7.827,46.973],[-7.577,42.996],[-28.847,-10.9],[25.303,12.329]],"v":[[43.874,-48.891],[35.16,30.819],[-29.181,91.286],[-49.602,7.388]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-6.961,62.286],[-30.452,4.961],[24.958,0.245],[-12.047,39.13]],"o":[[19.353,55.986],[-8.301,31.7],[-31.851,-0.312],[12.828,-41.664]],"v":[[58.023,-65.56],[49.622,36.723],[-4.6,92.405],[-47.296,23.087]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-17.454,42.526],[-34.556,14.996],[25.974,2.407],[-19.446,40.683]],"o":[[-0.104,53.98],[-4.433,35.663],[-30.206,-2.441],[13.297,-30.334]],"v":[[52.684,-50.822],[51.554,40.778],[-6.488,96.151],[-35.222,17.746]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-26.361,22.765],[-31.719,25.032],[21.302,4.569],[-24.099,42.237]],"o":[[-23.972,51.974],[1.329,39.626],[-21.302,-4.569],[10.843,-19.004]],"v":[[17.471,-41.347],[25.526,39.57],[-23.977,94.634],[-29.019,7.142]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-29.207,38.421],[-3.981,-35.119],[30.141,7.395],[1.651,26.371]],"o":[[-5.586,49.574],[3.738,32.983],[-23.618,-5.795],[27.791,11.116]],"v":[[-22.517,-66.798],[59.641,26.63],[5.786,69.708],[-34.552,13.884]],"c":true}]},{"t":32,"s":[{"i":[[39.58,85.812],[-17.902,-31.376],[35.171,-7.543],[-2.194,65.424]],"o":[[43.523,37.586],[39.788,69.735],[-35.171,7.543],[52.37,41.33]],"v":[[-66.479,-127.536],[10.277,-47.479],[1.953,96.976],[-79.778,6.061]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.121,0.996,0.996,0.898,0.54,0.998,0.951,0.598,0.808,1,0.906,0.298]}},"s":{"a":0,"k":[2.235,29.653]},"e":{"a":0,"k":[6.469,-84.419]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 4","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"second 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":8,"s":[0]}]},"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-26.872,112.436],[85.609,-106.311]],"o":[[-132.872,139.436],[144.609,-112.311]],"v":[[-39.128,-254.436],[2.391,101.311]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[21.128,83.436],[24.609,-91.311]],"o":[[-66.872,120.436],[89.609,-97.311]],"v":[[-38.128,-277.436],[1.391,-14.689]],"c":true}]},{"t":8,"s":[{"i":[[-67.629,106.783],[-100.629,-42.475]],"o":[[-145.629,104.783],[79.371,-115.475]],"v":[[-1.371,-455.783],[40.629,-170.525]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.914,0.239,0.269,1,0.739,0.22,1,1,0.565,0.2]}},"s":{"a":0,"k":[1.76,83.087]},"e":{"a":0,"k":[-8.18,-233.581]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":8,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"second 4","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[100]},{"t":16,"s":[0]}]},"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[118.077,110.131],[-2.665,-39.018],[-39.734,-0.002]],"o":[[23.538,87.153],[2.856,41.822],[41.124,-0.187]],"v":[[-75.077,-142.131],[-73.567,32.169],[2.391,101.311]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[31.424,67.656],[1.546,-80.008],[-41.88,2.007]],"o":[[-31.576,70.656],[-0.913,47.276],[-41.995,-86.699]],"v":[[-3.424,-109.656],[-79.56,13.318],[-15.005,96.699]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[68.553,128.061],[24.049,-18.868],[-7.331,-43.72]],"o":[[1.954,34.086],[-26.589,20.861],[36.8,-35.069]],"v":[[56.681,-178.293],[10.775,-106.916],[-35.362,-20.93]],"c":true}]},{"t":16,"s":[{"i":[[94.686,138.159],[18.186,-23.426],[-5.386,-53.715]],"o":[[5.853,35.136],[-21.233,27.351],[66.001,-69.723]],"v":[[49.314,-279.159],[20.764,-198.478],[-19.001,-88.277]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.914,0.239,0.269,1,0.739,0.22,1,1,0.565,0.2]}},"s":{"a":0,"k":[1.76,83.087]},"e":{"a":0,"k":[-8.18,-233.581]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":16,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"second 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":24,"s":[100]},{"t":32,"s":[0]}]},"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-39.734,-0.002],[160.903,157.868]],"o":[[41.124,-0.187],[-19.097,53.868]],"v":[[2.391,101.311],[-4.903,-122.868]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-67.071,-173.409],[49.406,97]],"o":[[46.247,-0.728],[-30.594,55]],"v":[[-15.929,99.409],[68.594,-56]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-133.939,-164.639],[53.824,142]],"o":[[43.111,-1.157],[-67.176,52]],"v":[[-16.561,96.506],[99.176,-107]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-86.439,-134.572],[55.824,140.5]],"o":[[43.111,-1.157],[-67.176,52]],"v":[[1.939,90.506],[75.676,-97]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-38.939,-104.506],[57.824,139]],"o":[[43.111,-1.157],[-67.176,52]],"v":[[-14.061,95.506],[52.176,-87]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-7.939,-57.506],[22.824,36]],"o":[[165.061,-85.506],[-106.176,65]],"v":[[21.939,89.506],[-24.824,-88]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-30.439,-41.506],[22.824,36]],"o":[[33.561,-64.506],[-87.176,60.5]],"v":[[36.439,-22.494],[-35.824,-155]],"c":true}]},{"t":32,"s":[{"i":[[-52.939,-25.506],[22.824,36]],"o":[[11.061,-51.506],[-68.176,56]],"v":[[16.939,-94.494],[-46.824,-222]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.914,0.239,0.269,1,0.739,0.22,1,1,0.565,0.2]}},"s":{"a":0,"k":[1.76,83.087]},"e":{"a":0,"k":[-8.18,-233.581]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"main","sr":1,"ks":{"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-11,-132.312],[-7.172,51.412],[38.165,58.396]],"o":[[53.567,-0.243],[9.183,-65.826],[111.313,177.997]],"v":[[3,102.312],[103.012,7.782],[15.687,-158.997]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-67,-154.909],[-14.398,53.118],[34.043,59.847]],"o":[[53.821,-0.847],[14.42,-57.831],[57.066,132.408]],"v":[[1,101.909],[112.427,3.393],[63.934,-166.408]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-167.999,-206.506],[-21.624,54.823],[29.921,61.299]],"o":[[54.074,-1.451],[19.657,-49.837],[2.818,86.819]],"v":[[-1.001,101.506],[121.841,-0.997],[112.182,-173.819]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-53.567,-0.243],[-3.669,53.71],[-22.247,82.372]],"o":[[53.5,0.243],[3.016,-44.152],[-202.747,113.872]],"v":[[-1,100.312],[99.011,8.806],[99.747,-160.872]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[40.815,-60.506],[1.22,63.133],[20.655,68.222]],"o":[[47.827,1.284],[-1.871,-96.875],[-162.345,140.111]],"v":[[1.185,101.506],[101.739,0.856],[-42.655,-165.222]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-31.558,-50.003],[16.799,28.099],[2.217,48.282]],"o":[[21.885,-45.314],[-28.06,-46.936],[-150.456,128.631]],"v":[[46.558,7.003],[45.21,-99.803],[-42.044,-227.187]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[-37.931,-26.5],[13.635,23.185],[-8.736,55.472]],"o":[[8.571,-34.721],[-24.653,-41.921],[-138.568,117.152]],"v":[[29.931,-75.5],[16.564,-159.567],[-41.432,-289.152]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-28.177,-18.494],[2.319,19.339],[-10.19,30.389]],"o":[[-1.177,-21.494],[-3.873,-32.303],[-112.29,82.582]],"v":[[-32.823,-251.506],[-43.018,-315.512],[-29.71,-405.582]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-2.92,-10.108],[-2.262,14.82],[-5.034,15.394]],"o":[[-0.505,-10.745],[2.5,-16.382],[-42.542,41.191]],"v":[[-54.2,-359.009],[-45.238,-393.82],[-38.849,-439.797]],"c":true}]},{"t":48,"s":[{"i":[[0.382,-0.567],[0.061,0.36],[0.121,0.4]],"o":[[0.168,0.005],[-0.102,-0.602],[-1.521,1.313]],"v":[[-47.577,-471.512],[-47.458,-472.128],[-47.987,-474.011]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[21.747,64.872],[-3.016,-44.152],[-53.5,0.243]],"o":[[22.247,82.372],[3.669,53.71],[79,-87.312]],"v":[[-97.747,-158.872],[-97.011,10.806],[3,102.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[113.161,97.111],[1.871,-96.875],[-47.828,1.284]],"o":[[14.417,96.532],[-1.22,63.133],[-66.999,-121.506]],"v":[[-20.161,-165.111],[-101.555,0.856],[-1.001,101.506]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[118.424,157.27],[28.288,-30.267],[-18.903,-57.031]],"o":[[9.7,45.245],[-31.276,33.464],[42.001,-54.614]],"v":[[46.576,-222.27],[-0.075,-117.135],[-44.001,7.614]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[94.686,138.159],[18.186,-23.426],[-5.386,-53.715]],"o":[[5.853,35.136],[-21.233,27.351],[66.001,-69.723]],"v":[[49.314,-279.159],[20.764,-198.478],[-19.001,-88.277]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[66.949,92.127],[9.993,-15.964],[-5.063,-48.024]],"o":[[5.051,35.121],[-11.668,18.639],[43.6,-13.156]],"v":[[49.051,-336.127],[35.336,-268.89],[15.4,-183.844]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[42.212,58.095],[5.432,-11.165],[-9.453,-39.448]],"o":[[3.788,26.341],[-6.342,13.036],[32.7,-9.867]],"v":[[48.788,-393.095],[41.523,-344.806],[38.55,-278.766]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[28.141,38.73],[1.328,-7.479],[-6.302,-26.298]],"o":[[2.526,17.561],[-1.551,8.732],[21.8,-6.578]],"v":[[48.525,-450.063],[48.103,-417.802],[51.7,-373.687]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":36,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[48,-564],[48,-564],[48,-564]],"c":true}]},{"t":37,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[47,-692],[47,-692],[47,-692]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-31,"s":[{"i":[[-30.231,72.598],[-53.5,0.243]],"o":[[-74.692,48.796],[53.567,-0.243]],"v":[[3,-341],[3,102.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-104,239],[95,-118.312]],"o":[[-279,185],[275,-162.312]],"v":[[3,-341],[3,102.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-85.814,172.891],[-2.814,-80.393]],"o":[[-167.814,138.391],[153.186,-122.893]],"v":[[0.814,-398.391],[-2.186,-7.107]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-67.629,106.783],[-100.629,-42.475]],"o":[[-145.629,104.783],[79.371,-115.475]],"v":[[-1.371,-455.783],[40.629,-170.525]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-45.595,72.231],[-66.633,-28.88]],"o":[[-92.443,67.174],[-18.443,-69.55]],"v":[[-3.557,-513.174],[-36.557,-332.45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[7.743,54.566],[-32.638,-15.285]],"o":[[-48.214,34.909],[20.243,-33.963]],"v":[[-5.743,-570.566],[-53.243,-478.037]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-1.529,3.129],[1.357,-1.69]],"o":[[-3.986,2.643],[3.929,-2.319]],"v":[[-7.929,-627.957],[-7.929,-621.624]],"c":true}]},{"t":21,"s":[{"i":[[-1.529,3.129],[1.357,-1.69]],"o":[[-3.986,2.643],[3.929,-2.319]],"v":[[-8.857,-693.957],[-8.857,-687.624]],"c":true}]}]},"nm":"Path 5","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[0,78.641]},"e":{"a":0,"k":[0,-396.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":48,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"p":{"a":0,"k":[263.799,525,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-188.981,50.042],[-214.402,227]],"o":[[321,-85],[69.514,-73.599]],"v":[[-33,77],[102,-415]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":45,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[8]},{"t":45,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[24]},{"t":45,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":45,"st":5,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"p":{"a":0,"k":[263.799,525,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[179.736,76.896],[302,241]],"o":[[-231.402,-99.001],[-79.13,-63.147]],"v":[[-23,57],[-182,-463]],"c":false}},"nm":"Path 2","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":56,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":46,"s":[8]},{"t":56,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":46,"s":[24]},{"t":56,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":56,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[263.799,525,0]},"a":{"a":0,"k":[3.799,5,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-139.238,137.226],[-214.402,227]],"o":[[110.598,-109],[69.514,-73.599]],"v":[[7,157],[-82,-267]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[179.736,76.896],[409.598,33]],"o":[[-231.402,-99],[-100.91,-8.13]],"v":[[-11,213],[-178,-415]],"c":false}},"nm":"Path 2","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[0]},{"t":51,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":41,"s":[8]},{"t":51,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":41,"s":[24]},{"t":51,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":56,"st":9,"bm":0}]},{"id":"comp_12","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"core","sr":1,"ks":{"p":{"a":0,"k":[274.85,745.271,0]},"a":{"a":0,"k":[-51.438,222.044,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[105,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":8,"s":[95,105,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":16,"s":[105,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":24,"s":[95,105,100]},{"t":32,"s":[105,95,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[39.58,85.812],[-17.902,-31.376],[35.171,-7.543],[-2.194,65.424]],"o":[[43.523,37.586],[39.788,69.735],[-35.171,7.543],[52.37,41.33]],"v":[[-66.479,-127.536],[10.277,-47.479],[1.953,96.976],[-79.778,6.061]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[8.841,78.454],[-43.981,17.591],[37.377,-9.171],[-5.916,52.199]],"o":[[46.223,60.804],[-2.613,41.734],[-47.701,11.703],[6.299,-55.579]],"v":[[47.252,-119.276],[66.299,8.408],[2.462,96.754],[-82.769,28.58]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-43.523,37.586],[-52.37,41.33],[35.171,7.543],[-39.788,69.735]],"o":[[-39.58,85.812],[2.194,65.424],[-35.171,-7.543],[17.902,-31.376]],"v":[[24.695,-129.694],[37.994,3.903],[-43.738,94.818],[-52.061,-49.637]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-46.223,60.804],[-6.299,-55.579],[47.701,11.703],[2.613,41.734]],"o":[[-8.841,78.454],[5.916,52.199],[-37.377,-9.171],[43.981,17.591]],"v":[[-86.843,-117.324],[43.178,30.532],[-42.053,98.707],[-105.89,10.361]],"c":true}]},{"t":32,"s":[{"i":[[39.58,85.812],[-17.902,-31.376],[35.171,-7.543],[-2.194,65.424]],"o":[[43.523,37.586],[39.788,69.735],[-35.171,7.543],[52.37,41.33]],"v":[[-66.479,-127.536],[10.277,-47.479],[1.953,96.976],[-79.778,6.061]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.121,0.996,0.996,0.898,0.54,0.998,0.951,0.598,0.808,1,0.906,0.298]}},"s":{"a":0,"k":[2.235,29.653]},"e":{"a":0,"k":[6.469,-84.419]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 4","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"second","sr":1,"ks":{"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-4.799,46.292],[-11.907,-44.087],[-2.665,-39.018],[-39.734,-0.002],[-5.575,39.963],[38.678,59.179]],"o":[[-31.898,32.4],[23.538,87.153],[2.856,41.822],[41.124,-0.187],[7.47,-53.546],[-27.683,-42.356]],"v":[[-39.128,-254.436],[-75.077,-142.131],[-73.567,32.169],[2.391,101.311],[78.742,27.466],[-4.903,-122.868]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[44.885,22.093],[-5.643,-37.784],[1.546,-80.008],[-41.88,2.007],[-23.325,59.137],[24.767,50.739]],"o":[[5.683,20.276],[16.655,111.521],[-0.913,47.276],[36.128,-1.732],[19.026,-48.238],[-37.411,-76.64]],"v":[[-11.07,-221.853],[10.576,-146.656],[-70.56,23.318],[6.995,98.699],[106.516,12.197],[97.615,-141.292]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[31.898,32.4],[27.683,-42.356],[-7.47,-53.546],[-41.124,-0.187],[-2.856,41.822],[-23.538,87.153]],"o":[[4.799,46.292],[-38.677,59.18],[5.575,39.963],[39.734,-0.002],[2.665,-39.018],[11.907,-44.087]],"v":[[39.57,-255.936],[5.345,-124.368],[-78.301,25.966],[-1.949,99.811],[74.008,30.669],[75.518,-143.631]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-5.683,20.276],[37.411,-76.64],[-19.026,-48.238],[-36.128,-1.732],[0.913,47.276],[-16.655,111.521]],"o":[[-49.292,56.853],[-24.766,50.74],[23.327,59.137],[41.88,2.007],[-1.546,-80.008],[5.643,-37.784]],"v":[[39.292,-260.853],[-95.393,-140.292],[-104.293,13.197],[-4.773,99.699],[72.782,24.318],[-8.354,-145.656]],"c":true}]},{"t":32,"s":[{"i":[[-4.799,46.292],[-11.907,-44.087],[-2.665,-39.018],[-39.734,-0.002],[-5.575,39.963],[38.678,59.179]],"o":[[-31.898,32.4],[23.538,87.153],[2.856,41.822],[41.124,-0.187],[7.47,-53.546],[-27.683,-42.356]],"v":[[-39.128,-254.436],[-75.077,-142.131],[-73.567,32.169],[2.391,101.311],[78.742,27.466],[-4.903,-122.868]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.914,0.239,0.269,1,0.739,0.22,1,1,0.565,0.2]}},"s":{"a":0,"k":[1.76,83.087]},"e":{"a":0,"k":[-8.18,-233.581]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"main","sr":1,"ks":{"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-30.231,72.598],[-20.387,-75.483],[-3.016,-44.152],[-53.5,0.243],[-7.172,51.412],[38.165,58.396]],"o":[[-74.692,48.796],[22.247,82.372],[3.669,53.71],[53.567,-0.243],[9.183,-65.826],[-35.462,-54.259]],"v":[[3,-341],[-97.747,-158.872],[-97.011,10.806],[3,102.312],[103.012,7.782],[15.687,-158.997]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[55.583,73],[-8.766,-58.692],[1.871,-96.875],[-47.827,1.284],[-21.624,54.823],[29.921,61.299]],"o":[[-3.417,72],[14.417,96.532],[-1.22,63.133],[54.074,-1.451],[19.657,-49.837],[-55.886,-114.495]],"v":[[-46.583,-299],[-20.161,-165.111],[-101.555,0.856],[-1.001,101.506],[121.841,-0.997],[112.182,-173.819]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[74.692,48.796],[35.462,-54.259],[-9.183,-65.826],[-53.567,-0.243],[-3.669,53.71],[-22.247,82.372]],"o":[[30.231,72.598],[-38.165,58.396],[7.172,51.412],[53.5,0.243],[3.016,-44.152],[20.387,-75.483]],"v":[[-1,-343],[-13.687,-160.997],[-101.012,5.782],[-1,100.312],[99.011,8.806],[99.747,-160.872]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[75.232,135],[55.886,-114.495],[-19.657,-49.837],[-54.074,-1.451],[1.22,63.133],[-14.417,96.532]],"o":[[-1.768,121],[-29.921,61.299],[21.624,54.823],[47.828,1.284],[-1.871,-96.875],[8.766,-58.692]],"v":[[48.768,-397],[-111.998,-173.819],[-121.657,-0.997],[1.185,101.506],[101.739,0.856],[20.345,-165.111]],"c":true}]},{"t":32,"s":[{"i":[[-30.231,72.598],[-20.387,-75.483],[-3.016,-44.152],[-53.5,0.243],[-7.172,51.412],[38.165,58.396]],"o":[[-74.692,48.796],[22.247,82.372],[3.669,53.71],[53.567,-0.243],[9.183,-65.826],[-35.462,-54.259]],"v":[[3,-341],[-97.747,-158.872],[-97.011,10.806],[3,102.312],[103.012,7.782],[15.687,-158.997]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[0,78.641]},"e":{"a":0,"k":[0,-396.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0}]},{"id":"comp_13","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"main","sr":1,"ks":{"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[54.161,45.111],[1.871,-96.875]],"o":[[14.417,96.532],[153.555,-0.856]],"v":[[-20.161,-165.111],[-101.555,0.856]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[68.115,181.651],[-13.746,-186.897]],"o":[[10.298,68.951],[24.254,-68.897]],"v":[[-20.115,-283.651],[-98.254,-1.103]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[122.069,81.381],[-53.048,-69.338]],"o":[[56.069,69.381],[14.552,-41.338]],"v":[[-74.069,-401.381],[-62.952,-256.662]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[38.046,52.921],[-35.365,-46.226]],"o":[[8.046,37.921],[9.701,-27.559]],"v":[[-64.046,-481.921],[-48.635,-364.441]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[10.023,27.46],[6.317,-21.779]],"o":[[-5.977,27.46],[24.317,-27.779]],"v":[[-49.023,-535.46],[-34.317,-472.221]],"c":true}]},{"t":28,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-20,-580],[-20,-580]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[0,78.641]},"e":{"a":0,"k":[0,-396.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":28,"st":-8,"bm":0}]},{"id":"comp_14","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"main","sr":1,"ks":{"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-31,"s":[{"i":[[-30.231,72.598],[-53.5,0.243]],"o":[[-74.692,48.796],[53.567,-0.243]],"v":[[3,-341],[3,102.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-104,239],[95,-118.312]],"o":[[-279,185],[275,-162.312]],"v":[[3,-341],[3,102.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-85.814,172.891],[-2.814,-80.393]],"o":[[-167.814,138.391],[153.186,-122.893]],"v":[[0.814,-398.391],[-2.186,-7.107]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-67.629,106.783],[-100.629,-42.475]],"o":[[-145.629,104.783],[79.371,-115.475]],"v":[[-1.371,-455.783],[40.629,-170.525]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-45.595,72.231],[-66.633,-28.88]],"o":[[-92.443,67.174],[-18.443,-69.55]],"v":[[-3.557,-513.174],[-36.557,-332.45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[7.743,54.566],[-32.638,-15.285]],"o":[[-48.214,34.909],[20.243,-33.963]],"v":[[-5.743,-570.566],[-53.243,-478.037]],"c":true}]},{"t":20,"s":[{"i":[[-1.529,3.129],[1.357,-1.69]],"o":[[-3.986,2.643],[3.929,-2.319]],"v":[[-7.929,-627.957],[-7.929,-621.624]],"c":true}]}]},"nm":"Path 5","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[0,78.641]},"e":{"a":0,"k":[0,-396.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":20,"st":0,"bm":0}]},{"id":"comp_15","layers":[{"ddd":0,"ind":3,"ty":4,"nm":"main 2","sr":1,"ks":{"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-27,-142.312],[38.313,64.997]],"o":[[114,-7.312],[-17.687,108.997]],"v":[[2,101.312],[71.687,-78.997]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-27,-142.312],[116.313,86.997]],"o":[[152,4.688],[74.313,100.997]],"v":[[2,101.312],[123.687,-214.997]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[69.667,-86.208],[12.542,44.665]],"o":[[181.667,-126.208],[-53.458,52.665]],"v":[[132.333,-147.792],[87.458,-400.665]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-59.667,-35.104],[56.771,51.332]],"o":[[-33.667,-59.104],[-43.229,37.332]],"v":[[111.667,-428.896],[85.229,-555.332]],"c":true}]},{"t":32,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[89,-623],[89,-623]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[0,78.641]},"e":{"a":0,"k":[0,-396.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0}]},{"id":"comp_16","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"main","sr":1,"ks":{"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-30.231,72.598],[-20.387,-75.483],[-3.016,-44.152],[-53.5,0.243],[-7.172,51.412],[38.165,58.396]],"o":[[-74.692,48.796],[22.247,82.372],[3.669,53.71],[53.567,-0.243],[9.183,-65.826],[-35.462,-54.259]],"v":[[3,-341],[-97.747,-158.872],[-97.011,10.806],[3,102.312],[103.012,7.782],[15.687,-158.997]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[55.583,73],[-8.766,-58.692],[1.871,-96.875],[-47.827,1.284],[-21.624,54.823],[29.921,61.299]],"o":[[-3.417,72],[14.417,96.532],[-1.22,63.133],[54.074,-1.451],[19.657,-49.837],[-55.886,-114.495]],"v":[[-46.583,-299],[-20.161,-165.111],[-101.555,0.856],[-1.001,101.506],[121.841,-0.997],[112.182,-173.819]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[74.692,48.796],[35.462,-54.259],[-9.183,-65.826],[-53.567,-0.243],[-3.669,53.71],[-22.247,82.372]],"o":[[30.231,72.598],[-38.165,58.396],[7.172,51.412],[53.5,0.243],[3.016,-44.152],[20.387,-75.483]],"v":[[-1,-343],[-13.687,-160.997],[-101.012,5.782],[-1,100.312],[99.011,8.806],[99.747,-160.872]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[75.232,135],[55.886,-114.495],[-19.657,-49.837],[-54.074,-1.451],[1.22,63.133],[-14.417,96.532]],"o":[[-1.768,121],[-29.921,61.299],[21.624,54.823],[47.828,1.284],[-1.871,-96.875],[8.766,-58.692]],"v":[[48.768,-397],[-111.998,-173.819],[-121.657,-0.997],[1.185,101.506],[101.739,0.856],[20.345,-165.111]],"c":true}]},{"t":32,"s":[{"i":[[-30.231,72.598],[-20.387,-75.483],[-3.016,-44.152],[-53.5,0.243],[-7.172,51.412],[38.165,58.396]],"o":[[-74.692,48.796],[22.247,82.372],[3.669,53.71],[53.567,-0.243],[9.183,-65.826],[-35.462,-54.259]],"v":[[3,-341],[-97.747,-158.872],[-97.011,10.806],[3,102.312],[103.012,7.782],[15.687,-158.997]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0}]},{"id":"comp_17","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"main","sr":1,"ks":{"p":{"a":0,"k":[256,646,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-11,-132.312],[-7.172,51.412],[38.165,58.396]],"o":[[53.567,-0.243],[9.183,-65.826],[111.313,177.997]],"v":[[3,102.312],[103.012,7.782],[15.687,-158.997]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-67,-154.909],[-14.398,53.118],[34.043,59.847]],"o":[[53.821,-0.847],[14.42,-57.831],[57.066,132.408]],"v":[[1,101.909],[112.427,3.393],[63.934,-166.408]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-167.999,-206.506],[-21.624,54.823],[29.921,61.299]],"o":[[54.074,-1.451],[19.657,-49.837],[2.818,86.819]],"v":[[-1.001,101.506],[121.841,-0.997],[112.182,-173.819]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-53.567,-0.243],[-3.669,53.71],[-22.247,82.372]],"o":[[53.5,0.243],[3.016,-44.152],[-202.747,113.872]],"v":[[-1,100.312],[99.011,8.806],[99.747,-160.872]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[40.815,-60.506],[1.22,63.133],[20.655,68.222]],"o":[[47.827,1.284],[-1.871,-96.875],[-162.345,140.111]],"v":[[1.185,101.506],[101.739,0.856],[-42.655,-165.222]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-31.558,-50.003],[16.799,28.099],[2.217,48.282]],"o":[[21.885,-45.314],[-28.06,-46.936],[-150.456,128.631]],"v":[[46.558,7.003],[45.21,-99.803],[-42.044,-227.187]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[-37.931,-26.5],[13.635,23.185],[-8.736,55.472]],"o":[[8.571,-34.721],[-24.653,-41.921],[-138.568,117.152]],"v":[[29.931,-75.5],[16.564,-159.567],[-41.432,-289.152]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[-28.177,-18.494],[2.319,19.339],[-10.19,30.389]],"o":[[-1.177,-21.494],[-3.873,-32.303],[-112.29,82.582]],"v":[[-32.823,-251.506],[-43.018,-315.512],[-29.71,-405.582]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-2.92,-10.108],[-2.262,14.82],[-5.034,15.394]],"o":[[-0.505,-10.745],[2.5,-16.382],[-42.542,41.191]],"v":[[-54.2,-359.009],[-45.238,-393.82],[-38.849,-439.797]],"c":true}]},{"t":48,"s":[{"i":[[0.382,-0.567],[0.061,0.36],[0.121,0.4]],"o":[[0.168,0.005],[-0.102,-0.602],[-1.521,1.313]],"v":[[-47.577,-471.512],[-47.458,-472.128],[-47.987,-474.011]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[21.747,64.872],[-3.016,-44.152],[-53.5,0.243]],"o":[[22.247,82.372],[3.669,53.71],[79,-87.312]],"v":[[-97.747,-158.872],[-97.011,10.806],[3,102.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[113.161,97.111],[1.871,-96.875],[-47.828,1.284]],"o":[[14.417,96.532],[-1.22,63.133],[-66.999,-121.506]],"v":[[-20.161,-165.111],[-101.555,0.856],[-1.001,101.506]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[118.424,157.27],[28.288,-30.267],[-18.903,-57.031]],"o":[[9.7,45.245],[-31.276,33.464],[42.001,-54.614]],"v":[[46.576,-222.27],[-0.075,-117.135],[-44.001,7.614]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[94.686,138.159],[18.186,-23.426],[-5.386,-53.715]],"o":[[5.853,35.136],[-21.233,27.351],[66.001,-69.723]],"v":[[49.314,-279.159],[20.764,-198.478],[-19.001,-88.277]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[66.949,92.127],[9.993,-15.964],[-5.063,-48.024]],"o":[[5.051,35.121],[-11.668,18.639],[43.6,-13.156]],"v":[[49.051,-336.127],[35.336,-268.89],[15.4,-183.844]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[42.212,58.095],[5.432,-11.165],[-9.453,-39.448]],"o":[[3.788,26.341],[-6.342,13.036],[32.7,-9.867]],"v":[[48.788,-393.095],[41.523,-344.806],[38.55,-278.766]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[28.141,38.73],[1.328,-7.479],[-6.302,-26.298]],"o":[[2.526,17.561],[-1.551,8.732],[21.8,-6.578]],"v":[[48.525,-450.063],[48.103,-417.802],[51.7,-373.687]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":36,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[48,-564],[48,-564],[48,-564]],"c":true}]},{"t":37,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[47,-692],[47,-692],[47,-692]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-31,"s":[{"i":[[-30.231,72.598],[-53.5,0.243]],"o":[[-74.692,48.796],[53.567,-0.243]],"v":[[3,-341],[3,102.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-104,239],[95,-118.312]],"o":[[-279,185],[275,-162.312]],"v":[[3,-341],[3,102.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-85.814,172.891],[-2.814,-80.393]],"o":[[-167.814,138.391],[153.186,-122.893]],"v":[[0.814,-398.391],[-2.186,-7.107]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-67.629,106.783],[-100.629,-42.475]],"o":[[-145.629,104.783],[79.371,-115.475]],"v":[[-1.371,-455.783],[40.629,-170.525]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[-45.595,72.231],[-66.633,-28.88]],"o":[[-92.443,67.174],[-18.443,-69.55]],"v":[[-3.557,-513.174],[-36.557,-332.45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[7.743,54.566],[-32.638,-15.285]],"o":[[-48.214,34.909],[20.243,-33.963]],"v":[[-5.743,-570.566],[-53.243,-478.037]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[-1.529,3.129],[1.357,-1.69]],"o":[[-3.986,2.643],[3.929,-2.319]],"v":[[-7.929,-627.957],[-7.929,-621.624]],"c":true}]},{"t":21,"s":[{"i":[[-1.529,3.129],[1.357,-1.69]],"o":[[-3.986,2.643],[3.929,-2.319]],"v":[[-8.857,-693.957],[-8.857,-687.624]],"c":true}]}]},"nm":"Path 5","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":48,"st":0,"bm":0}]},{"id":"comp_18","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 14","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[256,488,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[40,40,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[60,60,100]},{"t":42,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":92,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"01 fire small cycle","parent":1,"refId":"comp_19","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"01 fire small end","parent":1,"refId":"comp_20","sr":1,"ks":{"p":{"a":0,"k":[0,24,0]},"a":{"a":0,"k":[256,512,0]}},"ao":0,"w":512,"h":512,"ip":32,"op":92,"st":32,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"01 fire small left bottom flame (f24)","parent":1,"refId":"comp_22","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":8,"op":40,"st":8,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"01 fire small left bottom flame (f24) 2","parent":1,"refId":"comp_3","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":24,"op":56,"st":24,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"01 fire small left flame (f0)","parent":1,"refId":"comp_21","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":16,"op":48,"st":16,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"01 fire small left flame (f0)","parent":1,"refId":"comp_21","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":0,"nm":"01 fire small cycle outlines","parent":1,"refId":"comp_23","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"01 fire small left flame twirl","parent":1,"refId":"comp_4","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":24,"op":56,"st":24,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"p":{"a":0,"k":[259.799,261,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-88.418,174.357],[70,-25]],"o":[[107,-211],[-95.339,34.05]],"v":[[-77,179],[-204,-167]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":44,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431378603,0.4392157197,0.058823533356,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[8]},{"t":44,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[24]},{"t":44,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":4,"op":44,"st":4,"bm":0}]},{"id":"comp_19","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"core 2","sr":1,"ks":{"p":{"a":0,"k":[255.562,491.044,0]},"a":{"a":0,"k":[-51.438,222.044,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[105,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":8,"s":[95,105,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":16,"s":[105,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":24,"s":[95,105,100]},{"t":32,"s":[105,95,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-32.312,54.845],[-50.481,50.293],[35.394,-6.415],[6.415,35.394]],"o":[[46.16,46.426],[17.507,77.667],[-35.394,6.415],[-6.415,-35.394]],"v":[[-32.108,-101.639],[64.059,-13.213],[18.468,99.426],[-57.235,46.954]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-9.049,78.431],[-46.813,7.238],[38.482,-0.52],[-11.655,36.679]],"o":[[31.347,69.649],[-11.942,40.075],[-49.111,0.664],[15.601,23.049]],"v":[[45.915,-106],[82.625,29.003],[0.537,100.708],[-67.157,15.096]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[3.588,91.907],[6.435,-34.983],[34.983,6.435],[-15.694,53.155]],"o":[[25.664,58.408],[-6.435,34.983],[-34.983,-6.435],[12.057,48.134]],"v":[[29.902,-105.564],[55.722,46.071],[-19.272,97.762],[-62.997,-18.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-54.662,56.939],[-21.112,21.663],[36.819,0.63],[3.619,48.777]],"o":[[5.338,56.939],[20.993,48.329],[-32.5,-0.556],[57.303,3.063]],"v":[[-7.338,-108.939],[56.925,4.101],[-1.758,100.061],[-76.227,19.844]],"c":true}]},{"t":32,"s":[{"i":[[-32.312,54.845],[-50.481,50.293],[35.394,-6.415],[6.415,35.394]],"o":[[46.16,46.426],[17.507,77.667],[-35.394,6.415],[-6.415,-35.394]],"v":[[-32.108,-101.639],[64.059,-13.213],[18.468,99.426],[-57.235,46.954]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.121,0.996,0.996,0.898,0.54,0.998,0.951,0.598,0.808,1,0.906,0.298]}},"s":{"a":0,"k":[2.235,29.653]},"e":{"a":0,"k":[6.469,-84.419]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 4","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"second 2","sr":1,"ks":{"p":{"a":0,"k":[256,390,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-41.664,72.163],[-8.214,-67.826],[46.519,-9.139],[9.031,45.968]],"o":[[23.707,82.394],[5.632,46.51],[-46.519,9.139],[-9.031,-45.968]],"v":[[-41.859,-162.622],[87.483,-0.511],[19.606,99.271],[-80.977,32.585]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-17.507,82.469],[18.015,-60.597],[47.482,-0.642],[-14.381,45.257]],"o":[[42.493,69.469],[-14.703,49.456],[-60.597,0.819],[27.842,-87.619]],"v":[[41.507,-150.469],[91.801,20.75],[0.906,101],[-82.619,-4.635]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-24.427,64.616],[9.005,-44.596],[44.596,9.005],[-9.492,44.494]],"o":[[41.516,68.877],[-9.005,44.596],[-44.596,-9.005],[13.048,-61.165]],"v":[[43.143,-159.265],[78.884,33.269],[-18.169,97.711],[-82.611,0.658]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-66.821,80.403],[-15.912,-60.669],[49.77,0.851],[10.874,42.57]],"o":[[-1.821,86.403],[12.892,49.156],[-43.931,-0.751],[-13.53,-52.967]],"v":[[0.821,-184.403],[78.597,2.76],[-0.727,99],[-81.47,33.052]],"c":true}]},{"t":32,"s":[{"i":[[-41.664,72.163],[-8.214,-67.826],[46.519,-9.139],[9.031,45.968]],"o":[[23.707,82.394],[5.632,46.51],[-46.519,9.139],[-9.031,-45.968]],"v":[[-41.859,-162.622],[87.483,-0.511],[19.606,99.271],[-80.977,32.585]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.914,0.239,0.269,1,0.739,0.22,1,1,0.565,0.2]}},"s":{"a":0,"k":[1.76,83.087]},"e":{"a":0,"k":[-3.18,-152.581]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"main 2","sr":1,"ks":{"p":{"a":0,"k":[256,390,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-50.591,89.284],[-10.423,-83.922],[56.878,-11.304],[11.304,56.878]],"o":[[29.463,101.948],[7.147,57.548],[-56.878,11.304],[-11.304,-56.878]],"v":[[-43.991,-227.888],[102.986,-20.467],[20.467,102.986],[-102.986,20.467]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[35.726,95.092],[22,-74],[57.985,-0.784],[-17.561,55.267]],"o":[[96.726,88.092],[-17.955,60.395],[-74,1],[34,-107]],"v":[[20.274,-202.092],[111,7],[0,105],[-102,-24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-29.463,101.948],[11.304,-56.878],[56.878,11.304],[-7.147,57.548]],"o":[[50.591,89.284],[-11.304,56.878],[-56.878,-11.304],[10.423,-83.922]],"v":[[43.237,-226.763],[102.232,21.592],[-21.221,104.111],[-103.74,-19.342]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-117.832,89.347],[-21.001,-80.072],[65.687,1.123],[14.352,56.186]],"o":[[-46.372,107.609],[17.016,64.878],[-57.982,-0.992],[-17.858,-69.908]],"v":[[31.079,-257.165],[104.694,-22.02],[0,105],[-106.567,17.96]],"c":true}]},{"t":32,"s":[{"i":[[-50.591,89.284],[-10.423,-83.922],[56.878,-11.304],[11.304,56.878]],"o":[[29.463,101.948],[7.147,57.548],[-56.878,11.304],[-11.304,-56.878]],"v":[[-43.991,-227.888],[102.986,-20.467],[20.467,102.986],[-102.986,20.467]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[0,76.641]},"e":{"a":0,"k":[0,-306.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0}]},{"id":"comp_20","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"core 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":8,"s":[0]}]},"p":{"a":0,"k":[255.562,491.044,0]},"a":{"a":0,"k":[-51.438,222.044,0]},"s":{"a":0,"k":[105,95,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-32.312,54.845],[-50.481,50.293],[35.394,-6.415]],"o":[[46.16,46.426],[17.507,77.667],[-35.394,6.415]],"v":[[-32.108,-101.639],[64.059,-13.213],[18.468,99.426]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-20.681,66.638],[-25.24,25.147],[36.938,-3.468]],"o":[[19.636,60.922],[8.753,38.833],[-42.253,3.539]],"v":[[6.903,-103.82],[25.247,13.158],[-2.402,97.962]],"c":true}]},{"t":8,"s":[{"i":[[-9.049,78.431],[0,0],[38.482,-0.52]],"o":[[-6.888,75.419],[0,0],[-49.111,0.664]],"v":[[45.915,-106],[26.435,33.213],[-23.272,96.498]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.121,0.996,0.996,0.898,0.54,0.998,0.951,0.598,0.808,1,0.906,0.298]}},"s":{"a":0,"k":[2.235,29.653]},"e":{"a":0,"k":[6.469,-84.419]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 4","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":8,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"core 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[100]},{"t":16,"s":[0]}]},"p":{"a":0,"k":[255.562,491.044,0]},"a":{"a":0,"k":[-51.438,222.044,0]},"s":{"a":0,"k":[105,95,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-32.312,54.845],[35.394,-6.415],[6.415,35.394]],"o":[[46.16,46.426],[-35.394,6.415],[-6.415,-35.394]],"v":[[-32.108,-101.639],[18.468,99.426],[-57.235,46.954]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-9.049,78.431],[38.482,-0.52],[-11.655,36.679]],"o":[[14.108,81.288],[-49.111,0.664],[15.601,23.049]],"v":[[36.441,-78.381],[-17.358,97.851],[-58.736,-34.428]],"c":true}]},{"t":16,"s":[{"i":[[25.316,-83.965],[-0.749,0.077],[-76.071,58.32]],"o":[[0.554,1.298],[-34.983,-6.435],[-36.071,64.636]],"v":[[-53.908,77.594],[-54.51,76.71],[-37.283,-93.112]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.121,0.996,0.996,0.898,0.54,0.998,0.951,0.598,0.808,1,0.906,0.298]}},"s":{"a":0,"k":[2.235,29.653]},"e":{"a":0,"k":[6.469,-84.419]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 4","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":16,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"second 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":8,"s":[0]}]},"p":{"a":0,"k":[256,390,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-41.664,72.163],[-8.214,-67.826],[46.519,-9.139]],"o":[[23.707,82.394],[5.632,46.51],[-46.519,9.139]],"v":[[-41.859,-162.622],[87.483,-0.511],[19.606,99.271]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[-20.764,35.76],[-4.177,-33.677],[23.13,-4.558]],"o":[[11.758,40.899],[2.873,23.062],[-23.024,4.548]],"v":[[0.057,-155.184],[26.715,-70.788],[1.037,-22.218]],"c":true}]},{"t":8,"s":[{"i":[[0.137,-0.643],[-0.14,0.473],[-0.258,0.023]],"o":[[-0.191,-0.596],[0.115,-0.386],[0.471,-0.043]],"v":[[41.973,-147.746],[41.947,-149.066],[42.469,-149.707]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.914,0.239,0.269,1,0.739,0.22,1,1,0.565,0.2]}},"s":{"a":0,"k":[1.76,83.087]},"e":{"a":0,"k":[-3.18,-152.581]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":9,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"second 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8,"s":[100]},{"t":16,"s":[0]}]},"p":{"a":0,"k":[256,390,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-41.664,72.163],[46.519,-9.139],[9.031,45.968]],"o":[[23.707,82.394],[-46.519,9.139],[-9.031,-45.968]],"v":[[-41.859,-162.622],[19.606,99.271],[-80.977,32.585]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[-17.507,82.469],[47.482,-0.642],[-14.381,45.257]],"o":[[42.493,69.469],[-60.597,0.819],[52.619,38.635]],"v":[[41.507,-150.469],[-14.094,99],[-74.619,-94.635]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-41.143,-111.735],[0.169,1.289],[-67.389,81.342]],"o":[[-0.143,-0.735],[-65.831,-26.711],[73.611,83.342]],"v":[[-48.857,72.735],[-50.169,74.711],[-15.611,-173.342]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-66.821,80.403],[49.77,0.851],[10.874,42.57]],"o":[[-1.821,86.403],[-43.931,-0.751],[-13.53,-52.967]],"v":[[0.821,-184.403],[-0.727,99],[-81.47,33.052]],"c":true}]},{"t":32,"s":[{"i":[[-41.664,72.163],[46.519,-9.139],[9.031,45.968]],"o":[[23.707,82.394],[-46.519,9.139],[-9.031,-45.968]],"v":[[-41.859,-162.622],[19.606,99.271],[-80.977,32.585]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.914,0.239,0.269,1,0.739,0.22,1,1,0.565,0.2]}},"s":{"a":0,"k":[1.76,83.087]},"e":{"a":0,"k":[-3.18,-152.581]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":16,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"main 3","sr":1,"ks":{"p":{"a":0,"k":[256,390,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-50.971,89.067],[103.972,-17.072],[11.061,56.925]],"o":[[29.028,102.073],[-57.224,9.396],[-11.061,-56.925]],"v":[[-43.019,-228.074],[20.028,103.072],[-103.072,20.028]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[43.726,82.092],[57.988,0.518],[-95,149]],"o":[[96.726,88.092],[-112,-1],[-13,51]],"v":[[20.274,-202.092],[0,105],[-73,-138]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-73.494,-115.442],[-0.965,-0.071],[38.023,130.049]],"o":[[-0.535,-0.415],[-233.934,-85.156],[139.015,154.085]],"v":[[-22.502,103.407],[-20.072,103.064],[-32.976,-260.047]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[2.021,-34.999],[-0.33,-1.295],[-11.432,87.746]],"o":[[1.079,2.35],[22.732,-106.281],[55.537,140.785]],"v":[[-87.963,-63.052],[-89.633,-60.758],[17.544,-277.742]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-0.698,-28.573],[0.066,0.496],[-9.356,59.089]],"o":[[-0.427,-0.075],[-30.434,-80.504],[51.697,91.245]],"v":[[-35.323,-171.675],[-35.566,-171.996],[29.381,-304.076]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[24.936,-19.259],[-0.649,-0.657],[-15.013,31.882]],"o":[[-0.129,0.159],[-25.448,-61.739],[35.94,46.05]],"v":[[-26.053,-248.746],[-26.036,-246.932],[35.021,-337.818]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[-0.582,0.228],[-0.296,-0.089],[0.406,0.513]],"o":[[-0.319,0.299],[-1.172,0.037],[0.462,0.188]],"v":[[21.662,-367.612],[21.457,-366.849],[20.741,-368.45]],"c":true}]},{"t":33,"s":[{"i":[[-0.582,0.228],[-0.296,-0.089],[0.406,0.513]],"o":[[-0.319,0.299],[-1.172,0.037],[0.462,0.188]],"v":[[22.119,-425.612],[21.915,-424.849],[21.199,-426.45]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[35.726,95.092],[9.957,-101.911],[-1.117,44.693]],"o":[[71.726,197.092],[-45.603,-22.126],[3,-120]],"v":[[-42.726,-223.092],[-45.957,94.911],[-102,-24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[35.726,95.092],[9.957,-101.911],[-13.539,42.608]],"o":[[76.726,173.092],[-45.603,-22.126],[34,-107]],"v":[[20.274,-202.092],[-45.957,94.911],[-102,-24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[28.539,79.666],[15.271,-30.792],[63.294,-24.571]],"o":[[89.467,109.928],[-7.636,15.397],[-160.84,62.44]],"v":[[-4.803,-241.627],[21.701,-28.613],[-27.163,-45.068]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[27.035,22.744],[-7.411,-52.025],[34.341,-66.85]],"o":[[98.144,67.203],[4.153,29.149],[-14.835,28.88]],"v":[[-66.918,-295.641],[41.698,-130.341],[-50.963,-169.565]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[38.174,25.145],[-12.055,-52.934],[22.231,-50.177]],"o":[[93.649,22.392],[6.204,24.263],[-19.381,32.447]],"v":[[-114.032,-311.296],[26.995,-175.344],[-61.336,-205.289]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[51.836,22.143],[-19.073,-53.622],[17.852,-32.908]],"o":[[74.884,-18.277],[7.052,19.825],[-21.585,39.789]],"v":[[-157.371,-308.755],[11.042,-212.473],[-74.322,-234.122]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[47.388,-0.852],[-20.633,-42.246],[-6.679,-39.947]],"o":[[79.829,-60.96],[7.911,20.621],[3.631,36.916]],"v":[[-192.262,-284.999],[-12.601,-245.703],[-93.247,-262.886]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[30.066,-23.944],[-27.45,-33.052],[-38.7,-24.364]],"o":[[34.202,-118.986],[16.55,19.928],[26.108,16.437]],"v":[[-220.042,-233.02],[-52.086,-285.918],[-140.713,-280.698]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[{"i":[[21.559,-53.406],[-42.556,-12.395],[-39.454,-8.254]],"o":[[-21.52,-112.915],[26.583,7.597],[22.752,4.36]],"v":[[-217.817,-188.862],[-94.492,-311.817],[-150.4,-267.689]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-8.081,-76.532],[-44.505,14.451],[-34.813,5.036]],"o":[[-66.788,-82.682],[28.609,-9.29],[15.471,-2.238]],"v":[[-197.892,-143.194],[-148.445,-308.584],[-158.488,-252.15]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-42.015,-57.434],[-26.817,31.143],[-39.824,16.539]],"o":[[-92.051,-33.255],[19.332,-22.828],[22.33,-8.449]],"v":[[-156.547,-114.04],[-197.313,-273.38],[-168.13,-223.209]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[-61.367,-22.786],[-3.433,35.391],[-37.643,36.993]],"o":[[-86.784,22.985],[2.883,-29.724],[23.519,-23.113]],"v":[[-107.757,-110.853],[-225.28,-214.573],[-166.864,-196.295]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":34,"s":[{"i":[[-48.255,8.824],[11.318,24.14],[-10.048,38.28]],"o":[[-47.05,48.04],[-9.546,-20.238],[6.238,-23.914]],"v":[[-81.839,-126.076],[-197.225,-147.861],[-152.608,-158.445]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":36,"s":[{"i":[[-22.599,23.564],[15.508,8.665],[9.557,24.582]],"o":[[-6.616,44.216],[-13.049,-7.172],[-6.032,-15.307]],"v":[[-64.671,-149.022],[-135.368,-115.824],[-115.418,-138.897]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[-2.335,16.115],[8.871,-0.948],[11.06,7.121]],"o":[[10.116,19.764],[-7.341,0.908],[-6.895,-4.334]],"v":[[-43.565,-175.386],[-62.601,-141.49],[-61.167,-156.623]],"c":true}]},{"t":40,"s":[{"i":[[-0.068,-0.18],[-0.019,0.193],[0.026,-0.081]],"o":[[-0.145,-0.328],[0.086,0.042],[-0.064,0.203]],"v":[[-36.242,-198.937],[-36.116,-199.5],[-36.01,-199.275]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[-0.803,78.641]},"e":{"a":0,"k":[5.941,-306.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":40,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"01 fire small left flame (f0)","refId":"comp_21","sr":1,"ks":{"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":-16,"op":16,"st":-16,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"main 4","sr":1,"ks":{"p":{"a":0,"k":[256,390,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-50.971,89.067],[103.972,-17.072],[11.061,56.925]],"o":[[29.028,102.073],[-57.224,9.396],[-11.061,-56.925]],"v":[[-43.019,-228.074],[20.028,103.072],[-103.072,20.028]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[43.726,82.092],[57.988,0.518],[-95,149]],"o":[[96.726,88.092],[-112,-1],[-13,51]],"v":[[20.274,-202.092],[0,105],[-73,-138]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-73.494,-115.442],[-0.965,-0.071],[38.023,130.049]],"o":[[-0.535,-0.415],[-233.934,-85.156],[139.015,154.085]],"v":[[-22.502,103.407],[-20.072,103.064],[-32.976,-260.047]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[2.021,-34.999],[-0.33,-1.295],[-11.432,87.746]],"o":[[1.079,2.35],[22.732,-106.281],[55.537,140.785]],"v":[[-87.963,-63.052],[-89.633,-60.758],[17.544,-277.742]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-0.698,-28.573],[0.066,0.496],[-9.356,59.089]],"o":[[-0.427,-0.075],[-30.434,-80.504],[51.697,91.245]],"v":[[-35.323,-171.675],[-35.566,-171.996],[29.381,-304.076]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[24.936,-19.259],[-0.649,-0.657],[-15.013,31.882]],"o":[[-0.129,0.159],[-25.448,-61.739],[35.94,46.05]],"v":[[-26.053,-248.746],[-26.036,-246.932],[35.021,-337.818]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[-0.582,0.228],[-0.296,-0.089],[0.406,0.513]],"o":[[-0.319,0.299],[-1.172,0.037],[0.462,0.188]],"v":[[21.662,-367.612],[21.457,-366.849],[20.741,-368.45]],"c":true}]},{"t":33,"s":[{"i":[[-0.582,0.228],[-0.296,-0.089],[0.406,0.513]],"o":[[-0.319,0.299],[-1.172,0.037],[0.462,0.188]],"v":[[22.119,-425.612],[21.915,-424.849],[21.199,-426.45]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[35.726,95.092],[9.957,-101.911],[-1.117,44.693]],"o":[[71.726,197.092],[-45.603,-22.126],[3,-120]],"v":[[-42.726,-223.092],[-45.957,94.911],[-102,-24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[35.726,95.092],[9.957,-101.911],[-13.539,42.608]],"o":[[76.726,173.092],[-45.603,-22.126],[34,-107]],"v":[[20.274,-202.092],[-45.957,94.911],[-102,-24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[28.539,79.666],[15.271,-30.792],[63.294,-24.571]],"o":[[89.467,109.928],[-7.636,15.397],[-160.84,62.44]],"v":[[-4.803,-241.627],[21.701,-28.613],[-27.163,-45.068]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[27.035,22.744],[-7.411,-52.025],[34.341,-66.85]],"o":[[98.144,67.203],[4.153,29.149],[-14.835,28.88]],"v":[[-66.918,-295.641],[41.698,-130.341],[-50.963,-169.565]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[38.174,25.145],[-12.055,-52.934],[22.231,-50.177]],"o":[[93.649,22.392],[6.204,24.263],[-19.381,32.447]],"v":[[-114.032,-311.296],[26.995,-175.344],[-61.336,-205.289]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[51.836,22.143],[-19.073,-53.622],[17.852,-32.908]],"o":[[74.884,-18.277],[7.052,19.825],[-21.585,39.789]],"v":[[-157.371,-308.755],[11.042,-212.473],[-74.322,-234.122]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[47.388,-0.852],[-20.633,-42.246],[-6.679,-39.947]],"o":[[79.829,-60.96],[7.911,20.621],[3.631,36.916]],"v":[[-192.262,-284.999],[-12.601,-245.703],[-93.247,-262.886]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[30.066,-23.944],[-27.45,-33.052],[-38.7,-24.364]],"o":[[34.202,-118.986],[16.55,19.928],[26.108,16.437]],"v":[[-220.042,-233.02],[-52.086,-285.918],[-140.713,-280.698]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[{"i":[[21.559,-53.406],[-42.556,-12.395],[-39.454,-8.254]],"o":[[-21.52,-112.915],[26.583,7.597],[22.752,4.36]],"v":[[-217.817,-188.862],[-94.492,-311.817],[-150.4,-267.689]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-8.081,-76.532],[-44.505,14.451],[-34.813,5.036]],"o":[[-66.788,-82.682],[28.609,-9.29],[15.471,-2.238]],"v":[[-197.892,-143.194],[-148.445,-308.584],[-158.488,-252.15]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[-42.015,-57.434],[-26.817,31.143],[-39.824,16.539]],"o":[[-92.051,-33.255],[19.332,-22.828],[22.33,-8.449]],"v":[[-156.547,-114.04],[-197.313,-273.38],[-168.13,-223.209]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[-61.367,-22.786],[-3.433,35.391],[-37.643,36.993]],"o":[[-86.784,22.985],[2.883,-29.724],[23.519,-23.113]],"v":[[-107.757,-110.853],[-225.28,-214.573],[-166.864,-196.295]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":34,"s":[{"i":[[-48.255,8.824],[11.318,24.14],[-10.048,38.28]],"o":[[-47.05,48.04],[-9.546,-20.238],[6.238,-23.914]],"v":[[-81.839,-126.076],[-197.225,-147.861],[-152.608,-158.445]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":36,"s":[{"i":[[-22.599,23.564],[15.508,8.665],[9.557,24.582]],"o":[[-6.616,44.216],[-13.049,-7.172],[-6.032,-15.307]],"v":[[-64.671,-149.022],[-135.368,-115.824],[-115.418,-138.897]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[-2.335,16.115],[8.871,-0.948],[11.06,7.121]],"o":[[10.116,19.764],[-7.341,0.908],[-6.895,-4.334]],"v":[[-43.565,-175.386],[-62.601,-141.49],[-61.167,-156.623]],"c":true}]},{"t":40,"s":[{"i":[[-0.068,-0.18],[-0.019,0.193],[0.026,-0.081]],"o":[[-0.145,-0.328],[0.086,0.042],[-0.064,0.203]],"v":[[-36.242,-198.937],[-36.116,-199.5],[-36.01,-199.275]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":40,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"p":{"a":0,"k":[259.799,261,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-189.52,47.962],[-214.402,227]],"o":[[407,-103],[69.514,-73.599]],"v":[[7,157],[22,-231]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":45,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[8]},{"t":45,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[24]},{"t":45,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":45,"st":5,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"p":{"a":0,"k":[259.799,261,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[179.736,76.896],[302,241]],"o":[[-231.402,-99],[-79.13,-63.147]],"v":[[-11,213],[-186,-187]],"c":false}},"nm":"Path 2","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":50,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[8]},{"t":50,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[24]},{"t":50,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[259.799,261,0]},"a":{"a":0,"k":[3.799,5,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-139.238,137.226],[-214.402,227]],"o":[[110.598,-109],[69.514,-73.599]],"v":[[7,157],[22,-231]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[179.736,76.896],[409.598,33]],"o":[[-231.402,-99],[-100.91,-8.13]],"v":[[-11,213],[-186,-187]],"c":false}},"nm":"Path 2","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":60,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[8]},{"t":60,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[24]},{"t":60,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":60,"st":0,"bm":0}]},{"id":"comp_21","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"main 3","sr":1,"ks":{"p":{"a":0,"k":[256,390,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-50.971,89.067],[56.925,-11.061],[11.061,56.925]],"o":[[29.028,102.073],[-56.925,11.061],[-11.061,-56.925]],"v":[[-51.019,-212.091],[20.028,103.072],[-103.072,20.028]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[43.726,82.092],[57.988,0.518],[-95,149]],"o":[[96.726,88.092],[-112,-1],[-13,51]],"v":[[30.274,-165.092],[0,105],[-73,-138]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-73.494,-115.442],[-0.965,-0.071],[38.023,130.049]],"o":[[-0.535,-0.415],[-233.934,-85.156],[139.015,154.085]],"v":[[-22.502,103.407],[-20.072,103.064],[-32.976,-260.047]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[2.021,-34.999],[-0.33,-1.295],[-11.432,87.746]],"o":[[1.079,2.35],[22.732,-106.281],[55.537,140.785]],"v":[[-87.963,-63.052],[-89.633,-60.758],[17.544,-277.742]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-0.698,-28.573],[0.066,0.496],[-9.356,59.089]],"o":[[-0.427,-0.075],[-30.434,-80.504],[51.697,91.245]],"v":[[-35.323,-171.675],[-35.566,-171.996],[29.381,-304.076]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[24.936,-19.259],[-0.649,-0.657],[-15.013,31.882]],"o":[[-0.129,0.159],[-25.448,-61.739],[35.94,46.05]],"v":[[-26.053,-248.746],[-26.036,-246.932],[35.021,-337.818]],"c":true}]},{"t":32,"s":[{"i":[[-0.582,0.228],[-0.296,-0.089],[0.406,0.513]],"o":[[-0.319,0.299],[-1.172,0.037],[0.462,0.188]],"v":[[21.662,-367.612],[21.457,-366.849],[20.741,-368.45]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[-0.803,78.641]},"e":{"a":0,"k":[5.941,-306.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0}]},{"id":"comp_22","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"main 3","sr":1,"ks":{"p":{"a":0,"k":[256,390,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[65.688,1.123],[19.556,69.083]],"o":[[-41.977,-0.718],[0.056,0.083]],"v":[[0,105],[-106.556,17.417]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[56.878,-11.304],[-71.136,145.476]],"o":[[-40.253,8],[-9.136,65.476]],"v":[[20.467,102.986],[-125.864,-38.476]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[57.289,-92.906],[-55.086,48.981]],"o":[[-57.711,-67.906],[-28.086,62.981]],"v":[[-145.289,-16.094],[-126.914,-181.981]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-8.455,-33.826],[3.464,50.987]],"o":[[-46.455,-37.826],[67.464,52.987]],"v":[[-122.545,-106.174],[-129.464,-218.987]],"c":true}]},{"t":32,"s":[{"i":[[-0.557,-0.338],[-1.599,1.221]],"o":[[-0.348,0.005],[-0.55,0.457]],"v":[[-130.801,-267.255],[-131.014,-268.993]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.651,0.024,0.179,0.98,0.476,0.053,1,0.992,0.302,0.082]}},"s":{"a":0,"k":[-0.803,78.641]},"e":{"a":0,"k":[5.941,-306.457]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":-24,"bm":0}]},{"id":"comp_23","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"main outlines","sr":1,"ks":{"p":{"a":0,"k":[256,390,0]},"a":{"a":0,"k":[-51,121,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-50.591,89.284],[-10.423,-83.922],[56.878,-11.304],[11.304,56.878]],"o":[[29.463,101.948],[7.147,57.548],[-56.878,11.304],[-11.304,-56.878]],"v":[[-43.991,-227.888],[102.986,-20.467],[20.467,102.986],[-102.986,20.467]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[35.726,95.092],[22,-74],[57.985,-0.784],[-17.561,55.267]],"o":[[96.726,88.092],[-17.955,60.395],[-74,1],[34,-107]],"v":[[20.274,-202.092],[111,7],[0,105],[-102,-24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[-29.463,101.948],[11.304,-56.878],[56.878,11.304],[-7.147,57.548]],"o":[[50.591,89.284],[-11.304,56.878],[-56.878,-11.304],[10.423,-83.922]],"v":[[43.237,-226.763],[102.232,21.592],[-21.221,104.111],[-103.74,-19.342]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-117.832,89.347],[-21.001,-80.072],[65.687,1.123],[14.352,56.186]],"o":[[-46.372,107.609],[17.016,64.878],[-57.982,-0.992],[-17.858,-69.908]],"v":[[31.079,-257.165],[104.694,-22.02],[0,105],[-106.567,17.96]],"c":true}]},{"t":32,"s":[{"i":[[-50.591,89.284],[-10.423,-83.922],[56.878,-11.304],[11.304,56.878]],"o":[[29.463,101.948],[7.147,57.548],[-56.878,11.304],[-11.304,-56.878]],"v":[[-43.991,-227.888],[102.986,-20.467],[20.467,102.986],[-102.986,20.467]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[-51,121]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":0,"op":32,"st":0,"bm":0}]},{"id":"comp_24","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","parent":2,"sr":1,"ks":{"p":{"a":0,"k":[-77.334,-175.69,0]},"a":{"a":0,"k":[-77.334,-175.69,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.988,3.181],[-19.8,-5.052]],"o":[[-4.889,-7.822],[26.133,6.668]],"v":[[19.329,271.399],[24.3,294.213]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[4.186,6.698],[-41.687,-10.636]],"o":[[-10.293,-16.469],[55.022,14.038]],"v":[[7.834,173.427],[18.299,221.462]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[14.361,7.522],[-30.261,-6.497]],"o":[[-24.248,-12.701],[34.72,7.454]],"v":[[35.139,128.628],[12.401,185.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[32.578,61.344],[28.072,72.523]],"c":true}]},{"t":31,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[20.578,-416.656],[16.072,-405.477]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[33.144,5.233],[-24.721,-0.221]],"o":[[-35.022,-5.53],[27.911,0.249]],"v":[[19.555,225.935],[15.319,283.751]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[29.02,-23.268],[-29.148,15.285]],"o":[[-36.827,29.528],[27.206,-14.266]],"v":[[18.827,100.658],[25.794,178.294]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[36.864,-1.646],[-34.889,28.965]],"o":[[-35.938,1.604],[46.08,13.495]],"v":[[1.709,52.646],[-0.595,87.535]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.216,-6.659],[-24.954,25.786]],"o":[[-28.004,9.704],[30.5,0]],"v":[[16.732,-8.349],[24.773,17.714]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[12.002,-2.4],[-12.348,2.646]],"o":[[-16.253,3.251],[13.442,-2.88]],"v":[[25.798,-28.588],[29.158,-15.146]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":51,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[47.81,-67.072],[-25.632,9.313]],"o":[[-93.246,48.698],[25.632,-9.313]],"v":[[-74.754,121.302],[-13.132,182.313]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[16.302,-91.514],[22.297,-55.155]],"o":[[-51.846,41.486],[63.795,-25.293]],"v":[[-77.654,-43.486],[-14.297,37.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[65.508,-110.844],[-62.172,37.016]],"o":[[-46.719,4.672],[111.508,-8.984]],"v":[[-77.508,-81.156],[-31.508,-13.016]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[44.392,-34.904],[-43.656,10.635]],"o":[[-57.534,8.63],[43.656,-10.635]],"v":[[-64.892,-151.596],[-43.156,-90.865]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[45.056,-54.944],[-36.611,29.426]],"o":[[-75.944,-2.944],[74.944,33.426]],"v":[[-55.556,-184.556],[-45.389,-150.426]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[14.5,-3],[-14.25,5.5]],"o":[[-14.5,3],[14.25,-5.5]],"v":[[-44.875,-250.5],[-40.375,-234]],"c":true}]},{"t":60,"s":[{"i":[[1.16,-0.24],[-1.14,0.44]],"o":[[-1.16,0.24],[1.14,-0.44]],"v":[[-45.92,-278.26],[-45.56,-276.94]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[38.071,2.586],[-51.674,3.841],[-14.328,5.136]],"o":[[-71.639,-4.866],[8.07,11.41],[22.966,-8.233]],"v":[[122.074,242.366],[89.943,278.535],[130.995,288.864]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[20.869,-12.454],[-146.79,82.022],[-12.062,30.104]],"o":[[-112.454,-96.052],[15.663,4.937],[27.265,-8.079]],"v":[[157.954,39.052],[123.79,100.978],[169.735,69.137]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[74.55,-67.464],[-92.612,89.3],[0.78,32.797]],"o":[[-175.95,-43.988],[-28.612,-55.2],[57.28,16.797]],"v":[[109.45,-30.036],[99.612,30.2],[150.72,14.703]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[79.321,-50.136],[-20.934,-21.241],[-26.364,9.073]],"o":[[-58.179,-42.136],[17.914,18.177],[76.169,29.621]],"v":[[102.679,-76.364],[46.086,-27.677],[111.331,-23.621]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[62.617,-21.138],[-8.337,-17.814],[-11.971,0.122]],"o":[[-29.444,-9.815],[3.681,7.865],[67.802,31.907]],"v":[[103.66,-125.362],[70.352,-89.569],[93.689,-75.907]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[19.334,0],[-4.841,-5.763],[-4.486,0.179]],"o":[[-13.412,0],[2.137,2.544],[14.647,-0.586]],"v":[[106.839,-168.5],[96.133,-153.01],[106.253,-148.873]],"c":true}]},{"t":60,"s":[{"i":[[5.5,0],[-1.377,-1.639],[-1.276,0.051]],"o":[[-3.815,0],[0.608,0.724],[4.167,-0.167]],"v":[[103.5,-198.125],[100.454,-193.718],[103.333,-192.542]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-3.499,-24.848],[-6.884,-16.478],[-0.301,7.46],[-4.644,0.228],[-25.003,12.782],[33.341,-4.053]],"o":[[-34.078,22.449],[3.276,7.842],[1.294,-32.082],[19.012,20.499],[85.358,33.282],[-6.489,-19.362]],"v":[[-102.261,200.848],[-133.404,262.516],[-118.294,281.082],[-102.012,287.001],[-31.997,296.218],[-11.544,205.996]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[-0.747,-48.874],[-2.455,-35.731],[-40.679,19.746],[-10.189,-28.494],[-11.643,4.734],[85.039,22.458]],"o":[[-21.211,-29.874],[-35.532,22.641],[-1.215,-25.539],[22.811,19.506],[80.357,20.734],[29.075,-42.399]],"v":[[-100.253,19.874],[-169.932,40.216],[-144.785,98.539],[-93.811,115.494],[-40.357,117.266],[-14.075,40.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[4.596,-2.157],[-2.838,-33.101],[-23.392,21.782],[64.52,-34.091],[-7.412,7.454],[7.166,18.65]],"o":[[-43.115,-41.195],[-25.698,17.837],[-20.978,-41.699],[20.24,22.409],[13.576,-13.654],[-5.083,-13.23]],"v":[[-78.36,-37.305],[-165.347,-14.561],[-142.022,35.199],[-94.52,47.591],[-39.74,43.428],[-28.273,-12.423]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.586,24.077],[1.481,-16.058],[-4.9,-5.507],[-10.179,0.727],[8.719,-39.971],[89.45,-41.348]],"o":[[-19.447,-7.56],[-0.705,7.642],[4.542,5.105],[-3.153,-23.324],[83.219,35.029],[-14.55,-40.348]],"v":[[-113.586,-84.077],[-147.276,-60.135],[-141.231,-39.204],[-119.347,-31.676],[-66.219,-26.529],[-64.45,-76.152]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[13.764,-16.52],[-0.797,-14.42],[-5.435,-4.397],[-9.222,1.812],[-4.425,5.611],[7.725,10.732]],"o":[[-22.868,-5.87],[0.379,6.863],[5.039,4.077],[14.57,16.763],[8.105,-10.278],[-5.48,-7.613]],"v":[[-80.463,-124.48],[-113.116,-101.094],[-104.345,-83.066],[-82.915,-78.763],[-39.977,-80.996],[-37.614,-117.588]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[9.081,-4.12],[1.941,-4.856],[-0.074,-2.217],[-1.433,-1.373],[-2.412,2.764],[3.877,5.235]],"o":[[-4.629,2.1],[-0.924,2.311],[0.069,2.056],[11.907,11.408],[4.417,-5.063],[-2.75,-3.714]],"v":[[-75.274,-166.527],[-85.585,-154.758],[-86.926,-147.859],[-85.155,-142.261],[-54.718,-143.901],[-53.648,-161.404]],"c":true}]},{"t":60,"s":[{"i":[[0.601,-0.289],[0.013,-0.847],[-0.221,-0.37],[-0.364,-0.269],[-0.704,0.652],[0.934,1.027]],"o":[[-1.307,0.628],[-0.006,0.403],[0.205,0.343],[0.87,0.643],[1.289,-1.194],[-0.662,-0.729]],"v":[[-79.613,-181.42],[-81.494,-179.083],[-81.161,-177.91],[-80.299,-176.982],[-73.89,-177.455],[-73.114,-181.387]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392216701,0.972549079446,0.949019667682,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[280.666,670.31,0]},"a":{"a":0,"k":[24.666,286.31,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[20,20,100]},{"t":20,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.988,3.181],[-19.8,-5.052]],"o":[[-4.889,-7.822],[26.133,6.668]],"v":[[19.329,271.399],[24.3,294.213]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[6.101,9.761],[-60.75,-15.5]],"o":[[-15,-24],[80.183,20.458]],"v":[[0,163],[15.25,233]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[21,11],[-44.25,-9.5]],"o":[[-35.458,-18.573],[50.771,10.9]],"v":[[35,125],[1.75,207.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[32.578,61.344],[28.072,72.523]],"c":true}]},{"t":31,"s":[{"i":[[2.846,1.491],[-5.996,-1.287]],"o":[[-4.805,-2.517],[6.88,1.477]],"v":[[20.578,-416.656],[16.072,-405.477]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[66.5,10.5],[-49.599,-0.443]],"o":[[-70.267,-11.095],[56,0.5]],"v":[[20.5,199.5],[12,315.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[43.734,-35.066],[-43.928,23.035]],"o":[[-55.5,44.5],[41,-21.5]],"v":[[15.5,72.5],[26,189.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[56,-2.5],[-53,44]],"o":[[-54.592,2.437],[70,20.5]],"v":[[-4.5,51.5],[-8,104.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[34.653,-12.008],[-45,46.5]],"o":[[-50.5,17.5],[55,0]],"v":[[9.5,-11.5],[24,35.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[25,-5],[-25.722,5.512]],"o":[[-33.855,6.771],[28,-6]],"v":[[23,-29],[30,-1]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":51,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[69.5,-97.5],[-113.5,45]],"o":[[-66.5,8.5],[86.092,-34.134]],"v":[[-90.5,124.5],[-22,227]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[22,-123.5],[-113.5,45]],"o":[[-41.5,7],[86.092,-34.134]],"v":[[-91.5,-56.5],[-6,53]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[29.5,-125],[-86.5,51.5]],"o":[[-65,6.5],[164.5,11.5]],"v":[[-87.5,-87],[-23.5,-7.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[71,-76.5],[-34.5,28]],"o":[[-90,13.5],[106.5,47.5]],"v":[[-68.5,-157.5],[-34.5,-62.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[48.5,-66],[-34.5,28]],"o":[[-81,3],[106.5,47.5]],"v":[[-57.5,-194],[-53,-132]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[29,-6],[-28.5,11]],"o":[[-29,6],[28.5,-11]],"v":[[-46,-251.5],[-37,-218.5]],"c":true}]},{"t":60,"s":[{"i":[[1.16,-0.24],[-1.14,0.44]],"o":[[-1.16,0.24],[1.14,-0.44]],"v":[[-45.92,-278.26],[-45.56,-276.94]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[70.414,4.783],[-95.574,7.104],[-26.5,9.5]],"o":[[-132.5,-9],[14.926,21.104],[42.478,-15.228]],"v":[[116,239],[56.574,305.896],[132.5,325]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[31,-18.5],[-168.751,36.203],[-17.918,44.718]],"o":[[-248,-61.5],[23.267,7.334],[40.5,-12]],"v":[[167,24],[116.251,135.297],[184.5,88]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[101,-20.5],[-133.016,57.816],[-23.678,31.225]],"o":[[-222,-55.5],[17.83,12.614],[53,-13]],"v":[[114.5,-57.5],[94.516,65.184],[159,45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[108,-21],[-25.045,-34.694],[-37.826,13.017]],"o":[[-46.826,-19.424],[11.058,15.318],[78.5,26]],"v":[[96,-99.5],[40.628,-7.406],[112,2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[78.5,-26.5],[-10.452,-22.332],[-15.008,0.153]],"o":[[-36.913,-12.304],[4.615,9.86],[85,40]],"v":[[101.5,-126.5],[59.744,-81.628],[89,-64.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[33,0],[-8.262,-9.837],[-7.657,0.306]],"o":[[-22.893,0],[3.648,4.343],[25,-1]],"v":[[103,-170],[84.726,-143.561],[102,-136.5]],"c":true}]},{"t":60,"s":[{"i":[[5.5,0],[-1.377,-1.639],[-1.276,0.051]],"o":[[-3.815,0],[0.608,0.724],[4.167,-0.167]],"v":[[103.5,-198.125],[100.454,-193.718],[103.333,-192.542]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-4.046,-28.733],[-7.961,-19.055],[-7.058,-4.972],[-5.37,0.263],[-10.383,14.21],[38.553,-4.686]],"o":[[-39.406,25.959],[3.789,9.068],[6.543,4.609],[20.166,40.644],[98.704,38.486],[-7.504,-22.39]],"v":[[-106.454,196.733],[-142.466,268.042],[-124.994,289.511],[-106.166,296.356],[-25.204,307.014],[-1.553,202.686]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[7.754,-10.103],[-2.864,-41.686],[-47.459,23.038],[-17.819,6.695],[-11.001,13.349],[99.213,26.201]],"o":[[-24.746,-34.853],[-41.454,26.415],[10.089,11.703],[9.571,21.591],[45.708,87.19],[-6.546,-22.901]],"v":[[-105.754,6.853],[-187.046,30.585],[-146.041,114.963],[-104.071,125.409],[-34.708,136.81],[-5.213,30.799]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[5.648,-2.651],[-3.488,-40.68],[-28.748,26.769],[-22.563,7.933],[-9.109,9.161],[8.807,22.921]],"o":[[-52.986,-50.627],[-31.582,21.921],[15.404,15.153],[24.874,27.539],[16.685,-16.781],[-6.247,-16.259]],"v":[[-65.014,-36.373],[-171.918,-8.421],[-143.252,52.731],[-84.874,67.961],[-17.551,62.845],[-3.459,-5.795]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.861,-24.879],[2.389,-25.903],[-7.904,-8.883],[-16.419,1.173],[-8.619,13.394],[50.602,-9.162]],"o":[[-31.37,-12.195],[-1.137,12.327],[7.327,8.235],[20.153,51.095],[59.455,-7.707],[-9.228,-15.356]],"v":[[-114.361,-87.621],[-168.705,-49.001],[-158.954,-15.239],[-123.653,-3.095],[-37.955,5.207],[-35.102,-74.838]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[20.907,-25.093],[-1.21,-21.904],[-8.256,-6.68],[-14.008,2.753],[-6.721,8.523],[11.735,16.301]],"o":[[-34.736,-8.916],[0.576,10.424],[7.654,6.192],[22.131,25.463],[12.311,-15.612],[-8.324,-11.564]],"v":[[-85.407,-125.907],[-135.006,-90.384],[-121.683,-62.999],[-89.131,-56.463],[-23.909,-59.855],[-20.32,-115.438]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[16.089,-7.299],[3.439,-8.602],[-0.131,-3.928],[-2.539,-2.432],[-4.273,4.897],[6.868,9.275]],"o":[[-8.201,3.721],[-1.637,4.094],[0.122,3.642],[21.095,20.211],[7.826,-8.97],[-4.872,-6.579]],"v":[[-77.089,-167.201],[-95.356,-146.351],[-97.732,-134.128],[-94.595,-124.211],[-40.671,-127.117],[-38.776,-158.124]],"c":true}]},{"t":60,"s":[{"i":[[1.463,-0.702],[0.031,-2.062],[-0.538,-0.9],[-0.887,-0.655],[-1.713,1.587],[2.273,2.5]],"o":[[-3.182,1.528],[-0.015,0.981],[0.498,0.834],[2.118,1.565],[3.138,-2.907],[-1.612,-1.773]],"v":[[-78.589,-181.314],[-83.167,-175.626],[-82.357,-172.772],[-80.258,-170.512],[-64.659,-171.663],[-62.771,-181.234]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.945098099054,0.90588241278,0.792156922583,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.917647118662,0.847058883368,0.721568627451,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":60,"st":0,"bm":0}]},{"id":"comp_25","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 12","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[256,743,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[40,40,100]},{"t":8,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":88,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"02 fire middle end","parent":1,"refId":"comp_11","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":32,"op":88,"st":32,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"02 fire middle cycle","parent":1,"refId":"comp_12","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"02 fire middle flame2","parent":1,"refId":"comp_13","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":24,"op":52,"st":24,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"02 fire middle flame2","parent":1,"refId":"comp_13","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":8,"op":36,"st":8,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"02 fire middle center (f0)","parent":1,"refId":"comp_14","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":20,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":16,"op":48,"st":16,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":0,"nm":"02 fire middle cycle outlines","parent":1,"refId":"comp_16","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"02 fire middle end outl","parent":1,"refId":"comp_17","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":32,"op":88,"st":32,"bm":0}]},{"id":"comp_26","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 14","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[256,488,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[40,40,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[60,60,100]},{"t":42,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":92,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"03 fire small wide cycle end","parent":1,"refId":"comp_7","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":32,"op":60,"st":32,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"03 fire small wide cycle","parent":1,"refId":"comp_8","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"01 fire small left bottom flame (f24) 2","parent":1,"refId":"comp_3","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"01 fire small left flame twirl","parent":1,"refId":"comp_4","sr":1,"ks":{"p":{"a":0,"k":[0,-232,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[261.799,285,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-139.238,137.226],[-214.402,227]],"o":[[110.598,-109],[69.514,-73.599]],"v":[[7,157],[22,-231]],"c":false}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[179.736,76.896],[409.598,33]],"o":[[-231.402,-99],[-100.91,-8.13]],"v":[[-11,213],[-186,-187]],"c":false}},"nm":"Path 2","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[0]},{"t":93,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":53,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":83,"s":[8]},{"t":93,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":53,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":83,"s":[24]},{"t":93,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":33,"op":93,"st":33,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"p":{"a":0,"k":[261.799,285,0]},"a":{"a":0,"k":[3.799,5,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[179.736,76.896],[302,241]],"o":[[-231.402,-99],[-79.13,-63.147]],"v":[[-11,213],[-186,-187]],"c":false}},"nm":"Path 2","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":33,"s":[0]},{"t":83,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":33,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[8]},{"t":83,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":33,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[24]},{"t":83,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":33,"op":83,"st":33,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"p":{"a":0,"k":[261.799,285,0]},"a":{"a":0,"k":[3.799,5,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-189.52,47.962],[-214.402,227]],"o":[[407,-103],[69.514,-73.599]],"v":[[7,157],[22,-231]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":38,"s":[0]},{"t":78,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.905882418156,0.298039227724,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":38,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":48,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":68,"s":[8]},{"t":78,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156922817,0.321568638086,0.078431375325,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":38,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":48,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":68,"s":[24]},{"t":78,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":38,"op":78,"st":38,"bm":0}]},{"id":"comp_27","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","parent":2,"sr":1,"ks":{"p":{"a":0,"k":[-77.334,-175.69,0]},"a":{"a":0,"k":[-77.334,-175.69,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[33.144,5.233],[-24.721,-0.221]],"o":[[-35.022,-5.53],[27.911,0.249]],"v":[[19.555,225.935],[15.319,283.751]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[29.02,-23.268],[-29.148,15.285]],"o":[[-36.827,29.528],[27.206,-14.266]],"v":[[18.827,100.658],[25.794,178.294]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[36.864,-1.646],[-34.889,28.965]],"o":[[-35.938,1.604],[46.08,13.495]],"v":[[1.709,52.646],[-0.595,87.535]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[19.216,-6.659],[-24.954,25.786]],"o":[[-28.004,9.704],[30.5,0]],"v":[[16.732,-8.349],[24.773,17.714]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[12.002,-2.4],[-12.348,2.646]],"o":[[-16.253,3.251],[13.442,-2.88]],"v":[[25.798,-28.588],[29.158,-15.146]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":31,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[47.81,-67.072],[-25.632,9.313]],"o":[[-93.246,48.698],[25.632,-9.313]],"v":[[-74.754,121.302],[-13.132,182.313]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[16.302,-91.514],[22.297,-55.155]],"o":[[-51.846,41.486],[63.795,-25.293]],"v":[[-77.654,-43.486],[-14.297,37.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[65.508,-110.844],[-62.172,37.016]],"o":[[-46.719,4.672],[111.508,-8.984]],"v":[[-77.508,-81.156],[-31.508,-13.016]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[44.392,-34.904],[-43.656,10.635]],"o":[[-57.534,8.63],[43.656,-10.635]],"v":[[-64.892,-151.596],[-43.156,-90.865]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[45.056,-54.944],[-36.611,29.426]],"o":[[-75.944,-2.944],[74.944,33.426]],"v":[[-55.556,-184.556],[-45.389,-150.426]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[14.5,-3],[-14.25,5.5]],"o":[[-14.5,3],[14.25,-5.5]],"v":[[-44.875,-250.5],[-40.375,-234]],"c":true}]},{"t":36,"s":[{"i":[[1.16,-0.24],[-1.14,0.44]],"o":[[-1.16,0.24],[1.14,-0.44]],"v":[[-45.92,-278.26],[-45.56,-276.94]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[38.071,2.586],[-51.674,3.841],[-14.328,5.136]],"o":[[-71.639,-4.866],[8.07,11.41],[22.966,-8.233]],"v":[[122.074,242.366],[89.943,278.535],[130.995,288.864]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[20.869,-12.454],[-146.79,82.022],[-12.062,30.104]],"o":[[-112.454,-96.052],[15.663,4.937],[27.265,-8.079]],"v":[[157.954,39.052],[123.79,100.978],[169.735,69.137]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[74.55,-67.464],[-92.612,89.3],[0.78,32.797]],"o":[[-175.95,-43.988],[-28.612,-55.2],[57.28,16.797]],"v":[[109.45,-30.036],[99.612,30.2],[150.72,14.703]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[79.321,-50.136],[-20.934,-21.241],[-26.364,9.073]],"o":[[-58.179,-42.136],[17.914,18.177],[76.169,29.621]],"v":[[102.679,-76.364],[46.086,-27.677],[111.331,-23.621]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[62.617,-21.138],[-8.337,-17.814],[-11.971,0.122]],"o":[[-29.444,-9.815],[3.681,7.865],[67.802,31.907]],"v":[[103.66,-125.362],[70.352,-89.569],[93.689,-75.907]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.334,0],[-4.841,-5.763],[-4.486,0.179]],"o":[[-13.412,0],[2.137,2.544],[14.647,-0.586]],"v":[[106.839,-168.5],[96.133,-153.01],[106.253,-148.873]],"c":true}]},{"t":36,"s":[{"i":[[5.5,0],[-1.377,-1.639],[-1.276,0.051]],"o":[[-3.815,0],[0.608,0.724],[4.167,-0.167]],"v":[[103.5,-198.125],[100.454,-193.718],[103.333,-192.542]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[29.237,-0.424],[18.659,-25.84],[-78.306,-8.932],[-2.4,-26.987],[0.564,-42.682],[30.491,12.722]],"o":[[-3.4,-14.426],[-41.473,-22.64],[-12.806,-41.432],[1.048,11.781],[27.835,19.62],[5.813,-25.223]],"v":[[106.649,132.4],[49.973,125.64],[49.306,238.932],[79.9,226.987],[116.436,225.182],[131.993,185.252]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[18.356,-65.631],[1.004,-68.279],[-92.58,7.072],[-16.188,1.28],[-17.016,-0.736],[29.057,17.998]],"o":[[12.356,-49.631],[-116.996,-73.279],[18.803,-1.436],[15.763,-1.246],[26.568,1.15],[25.666,-30.324]],"v":[[114.644,-62.369],[48.996,-92.721],[50.08,17.428],[99.158,-10.713],[145.932,9.85],[160.334,-37.676]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[40.408,-8.621],[8.905,-22.866],[-63.43,19.052],[-2.553,-27.854],[-0.664,-27.112],[37.995,7.577]],"o":[[-15.642,-10.077],[-92.499,-21.673],[-3,-37.948],[6.447,-39.854],[25.263,14.209],[-3.038,-25.183]],"v":[[102.472,-151.481],[53.507,-145.134],[65,-51.052],[106.553,-41.146],[153.664,-66.888],[163.996,-115.133]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[25.979,-38.681],[-12.816,-22.414],[-71.606,9.11],[-7.652,-23.023],[-8.541,18],[3.417,17.237]],"o":[[6.479,-39.181],[-74.816,-41.414],[-4.106,-29.39],[7.848,-31.023],[7.401,-15.598],[-5.628,-28.395]],"v":[[80.521,-174.319],[39.316,-172.086],[38.606,-82.61],[103.652,-89.977],[145.259,-114.137],[152.031,-167.148]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[35.852,-24.204],[6.121,-6.787],[-30.147,-5.617],[-12.364,11.19],[-4.863,14.486],[5.544,11.648]],"o":[[-15.19,-0.044],[-18.957,21.021],[11.029,2.055],[17.765,0.658],[4.214,-12.553],[-9.133,-19.188]],"v":[[66.426,-225.746],[34.942,-214.601],[66.05,-144.055],[101.841,-156.26],[135.809,-181.382],[133.83,-220.533]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[11.188,-2.48],[2.145,-2.133],[-8.537,-4.421],[-3.25,0.288],[-2.088,4.234],[1.472,3.752]],"o":[[-5.164,1.145],[-6.643,6.608],[3.123,1.618],[6.515,-0.577],[1.81,-3.669],[-2.424,-6.18]],"v":[[99.368,-273.52],[88.579,-268.455],[96.629,-247.5],[106.444,-245.289],[119.333,-253.457],[119.83,-265.206]],"c":true}]},{"t":36,"s":[{"i":[[2.469,0.044],[0.542,-0.415],[-1.676,-1.418],[-0.709,0.023],[-0.595,0.897],[0.16,0.894]],"o":[[-1.158,-0.021],[-1.68,1.285],[0.613,0.519],[1.347,-0.043],[0.516,-0.777],[-0.264,-1.473]],"v":[[96.518,-299.069],[93.999,-298.426],[94.96,-292.841],[96.99,-292.023],[99.944,-293.62],[100.503,-296.249]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392216701,0.972549079446,0.949019667682,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[280.666,670.31,0]},"a":{"a":0,"k":[24.666,286.31,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[20,20,100]},{"t":12,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[66.5,10.5],[-49.599,-0.443]],"o":[[-70.267,-11.095],[56,0.5]],"v":[[20.5,199.5],[12,315.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[43.734,-35.066],[-43.928,23.035]],"o":[[-55.5,44.5],[41,-21.5]],"v":[[15.5,72.5],[26,189.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[56,-2.5],[-53,44]],"o":[[-54.592,2.437],[70,20.5]],"v":[[-4.5,51.5],[-8,104.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[34.653,-12.008],[-45,46.5]],"o":[[-50.5,17.5],[55,0]],"v":[[9.5,-11.5],[24,35.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[25,-5],[-25.722,5.512]],"o":[[-33.855,6.771],[28,-6]],"v":[[23,-29],[30,-1]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":31,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[69.5,-97.5],[-113.5,45]],"o":[[-66.5,8.5],[86.092,-34.134]],"v":[[-90.5,124.5],[-22,227]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[22,-123.5],[-113.5,45]],"o":[[-41.5,7],[86.092,-34.134]],"v":[[-91.5,-56.5],[-6,53]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[29.5,-125],[-86.5,51.5]],"o":[[-65,6.5],[164.5,11.5]],"v":[[-87.5,-87],[-23.5,-7.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[71,-76.5],[-34.5,28]],"o":[[-90,13.5],[106.5,47.5]],"v":[[-68.5,-157.5],[-34.5,-62.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[48.5,-66],[-34.5,28]],"o":[[-81,3],[106.5,47.5]],"v":[[-57.5,-194],[-53,-132]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[29,-6],[-28.5,11]],"o":[[-29,6],[28.5,-11]],"v":[[-46,-251.5],[-37,-218.5]],"c":true}]},{"t":36,"s":[{"i":[[1.16,-0.24],[-1.14,0.44]],"o":[[-1.16,0.24],[1.14,-0.44]],"v":[[-45.92,-278.26],[-45.56,-276.94]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[70.414,4.783],[-95.574,7.104],[-26.5,9.5]],"o":[[-132.5,-9],[14.926,21.104],[42.478,-15.228]],"v":[[116,239],[56.574,305.896],[132.5,325]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[31,-18.5],[-168.751,36.203],[-17.918,44.718]],"o":[[-248,-61.5],[23.267,7.334],[40.5,-12]],"v":[[167,24],[116.251,135.297],[184.5,88]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[101,-20.5],[-133.016,57.816],[-23.678,31.225]],"o":[[-222,-55.5],[17.83,12.614],[53,-13]],"v":[[114.5,-57.5],[94.516,65.184],[159,45]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[108,-21],[-25.045,-34.694],[-37.826,13.017]],"o":[[-46.826,-19.424],[11.058,15.318],[78.5,26]],"v":[[96,-99.5],[40.628,-7.406],[112,2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[78.5,-26.5],[-10.452,-22.332],[-15.008,0.153]],"o":[[-36.913,-12.304],[4.615,9.86],[85,40]],"v":[[101.5,-126.5],[59.744,-81.628],[89,-64.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[33,0],[-8.262,-9.837],[-7.657,0.306]],"o":[[-22.893,0],[3.648,4.343],[25,-1]],"v":[[103,-170],[84.726,-143.561],[102,-136.5]],"c":true}]},{"t":36,"s":[{"i":[[5.5,0],[-1.377,-1.639],[-1.276,0.051]],"o":[[-3.815,0],[0.608,0.724],[4.167,-0.167]],"v":[[103.5,-198.125],[100.454,-193.718],[103.333,-192.542]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[40.224,-0.584],[25.671,-35.551],[-117.344,4.076],[-13.845,-2.09],[-15.219,20.222],[41.95,17.503]],"o":[[-4.678,-19.848],[-63.829,-76.551],[7.749,18.334],[16.09,2.429],[38.296,26.994],[7.997,-34.703]],"v":[[108.178,121.348],[34.329,117.551],[25.844,278.924],[70,279],[118.204,260.006],[148.55,186.497]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[42.564,-6.226],[40.813,-50.225],[-85.297,31.262],[-32.063,3.791],[-15.43,23.796],[36.231,22.442]],"o":[[-6.076,-18.138],[-120.187,-27.225],[8.47,17.96],[23.983,5.161],[35.183,9.711],[3.616,-40.885]],"v":[[125.779,-104.633],[37.687,-123.775],[35.297,47.238],[94,72],[154.817,37.789],[175.269,-33.942]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[50.807,-10.84],[11.197,-28.751],[-79.754,23.956],[-21.881,6.283],[-12.991,22.817],[47.774,9.527]],"o":[[-19.668,-12.67],[-116.303,-27.251],[12.896,12.677],[24.971,6.064],[31.765,17.866],[-3.819,-31.664]],"v":[[94.869,-161.73],[33.303,-153.749],[47.754,-35.456],[100,-23],[159.235,-55.366],[172.226,-116.027]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[61.33,-27.94],[13.035,-11.794],[-88,34.498],[-24.613,14.871],[-11.285,23.783],[4.514,22.775]],"o":[[-22.734,-8.189],[-81.69,0.79],[16.437,2.441],[24.287,7.59],[9.779,-20.609],[-7.436,-37.518]],"v":[[69.474,-198.525],[15.69,-190.29],[42.5,-42.998],[104,-60],[158.974,-91.922],[167.923,-161.964]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[49.628,-33.504],[8.473,-9.395],[-41.73,-7.776],[-17.115,15.49],[-6.732,20.052],[7.675,16.124]],"o":[[-21.027,-0.06],[-26.241,29.098],[15.267,2.845],[24.591,0.911],[5.833,-17.376],[-12.642,-26.561]],"v":[[48.978,-222.185],[5.396,-206.758],[48.457,-109.106],[98,-126],[145.019,-160.775],[142.281,-214.968]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[20.595,-4.565],[3.948,-3.927],[-15.714,-8.139],[-5.982,0.53],[-3.844,7.794],[2.709,6.906]],"o":[[-9.506,2.107],[-12.229,12.163],[5.749,2.978],[11.992,-1.063],[3.331,-6.754],[-4.463,-11.376]],"v":[[87.975,-274.467],[68.116,-265.142],[82.934,-226.569],[101,-222.5],[124.726,-237.535],[125.642,-259.162]],"c":true}]},{"t":36,"s":[{"i":[[4.312,0.077],[0.947,-0.725],[-2.927,-2.476],[-1.238,0.04],[-1.039,1.566],[0.28,1.562]],"o":[[-2.023,-0.036],[-2.933,2.244],[1.071,0.906],[2.353,-0.076],[0.9,-1.357],[-0.461,-2.572]],"v":[[95.675,-299.303],[91.277,-298.18],[92.954,-288.429],[96.5,-287],[101.657,-289.789],[102.633,-294.38]],"c":true}]}]},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[0.945098099054,0.90588241278,0.792156922583,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.917647118662,0.847058883368,0.721568627451,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":36,"st":0,"bm":0}]},{"id":"comp_28","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 13","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[256,494,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[40,40,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":67,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":94,"s":[60,60,100]},{"t":106,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":156,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"01 fire small cycle","parent":1,"refId":"comp_19","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":64,"op":96,"st":64,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"01 fire small cycle","parent":1,"refId":"comp_19","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"01 fire small cycle","parent":1,"refId":"comp_19","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"01 fire small end","parent":1,"refId":"comp_20","sr":1,"ks":{"p":{"a":0,"k":[0,18,0]},"a":{"a":0,"k":[256,512,0]}},"ao":0,"w":512,"h":512,"ip":96,"op":156,"st":96,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"01 fire small left bottom flame (f24)","parent":1,"refId":"comp_22","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":72,"op":104,"st":72,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"01 fire small left bottom flame (f24) 2","parent":1,"refId":"comp_3","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":40,"op":72,"st":40,"bm":0},{"ddd":0,"ind":8,"ty":0,"nm":"01 fire small left bottom flame (f24)","parent":1,"refId":"comp_22","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":8,"op":40,"st":8,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"01 fire small left bottom flame (f24) 2","parent":1,"refId":"comp_3","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":88,"op":120,"st":88,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"01 fire small left bottom flame (f24)","parent":1,"refId":"comp_22","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":56,"op":88,"st":56,"bm":0},{"ddd":0,"ind":11,"ty":0,"nm":"01 fire small left bottom flame (f24) 2","parent":1,"refId":"comp_3","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":24,"op":56,"st":24,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"01 fire small left flame (f0)","parent":1,"refId":"comp_21","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":80,"op":112,"st":80,"bm":0},{"ddd":0,"ind":13,"ty":0,"nm":"01 fire small left flame (f0)","parent":1,"refId":"comp_21","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":48,"op":80,"st":48,"bm":0},{"ddd":0,"ind":14,"ty":0,"nm":"01 fire small left flame (f0)","parent":1,"refId":"comp_21","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":16,"op":48,"st":16,"bm":0},{"ddd":0,"ind":15,"ty":0,"nm":"01 fire small left flame (f0)","parent":1,"refId":"comp_21","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":64,"op":96,"st":64,"bm":0},{"ddd":0,"ind":16,"ty":0,"nm":"01 fire small left flame (f0)","parent":1,"refId":"comp_21","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":17,"ty":0,"nm":"01 fire small left flame (f0)","parent":1,"refId":"comp_21","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":0,"nm":"01 fire small cycle outlines","parent":1,"refId":"comp_23","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":64,"op":96,"st":64,"bm":0},{"ddd":0,"ind":19,"ty":0,"nm":"01 fire small cycle outlines","parent":1,"refId":"comp_23","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":20,"ty":0,"nm":"01 fire small cycle outlines","parent":1,"refId":"comp_23","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":21,"ty":0,"nm":"01 fire small left flame twirl","parent":1,"refId":"comp_4","sr":1,"ks":{"p":{"a":0,"k":[0,-238,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":512,"ip":88,"op":120,"st":88,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"p":{"a":0,"k":[259.799,261,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-54.44,187.762],[218,5]],"o":[[49,-169],[-101.211,-2.321]],"v":[[7,157],[-220,-181]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0]},{"t":55,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431378603,0.4392157197,0.058823533356,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[8]},{"t":55,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[24]},{"t":55,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":15,"op":55,"st":15,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"p":{"a":0,"k":[259.799,261,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-88.418,174.357],[70,-25]],"o":[[107,-211],[-95.339,34.05]],"v":[[-77,179],[-204,-167]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":61,"s":[0]},{"t":101,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431378603,0.4392157197,0.058823533356,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":61,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":71,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":91,"s":[8]},{"t":101,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":61,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":71,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":91,"s":[24]},{"t":101,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":61,"op":101,"st":61,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"p":{"a":0,"k":[259.799,261,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-16.887,194.764],[12,-97]],"o":[[15,-173],[-12.429,100.471]],"v":[[-77,179],[-238,85]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[0]},{"t":74,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431378603,0.4392157197,0.058823533356,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":44,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":64,"s":[8]},{"t":74,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":44,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":64,"s":[24]},{"t":74,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":34,"op":74,"st":34,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"p":{"a":0,"k":[259.799,261,0]},"a":{"a":0,"k":[3.799,5,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[52.962,188.184],[-174,-29]],"o":[[-47,-167],[99.86,16.643]],"v":[[7,157],[224,-213]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":0,"k":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":6,"s":[0]},{"t":46,"s":[350]}]},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431378603,0.4392157197,0.058823533356,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":6,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":16,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":36,"s":[8]},{"t":46,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.729411764706,0.007843137255,0.007843137255,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":6,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":16,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":36,"s":[24]},{"t":46,"s":[0]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":6,"op":46,"st":6,"bm":0}]},{"id":"comp_29","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 12","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[256,743,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[40,40,100]},{"t":8,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":124,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"02 fire middle end","parent":1,"refId":"comp_11","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":96,"op":152,"st":96,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"02 fire middle cycle","parent":1,"refId":"comp_12","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":64,"op":96,"st":64,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"02 fire middle cycle","parent":1,"refId":"comp_12","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"02 fire middle cycle","parent":1,"refId":"comp_12","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"02 fire middle flame2","parent":1,"refId":"comp_13","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":72,"op":100,"st":72,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"02 fire middle center (f0)","parent":1,"refId":"comp_14","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":80,"op":100,"st":80,"bm":0},{"ddd":0,"ind":8,"ty":0,"nm":"02 fire middle center (f0)","parent":1,"refId":"comp_14","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":32,"op":52,"st":32,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"02 fire middle flame2","parent":1,"refId":"comp_13","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":8,"op":36,"st":8,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":64,"op":96,"st":64,"bm":0},{"ddd":0,"ind":11,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":80,"op":112,"st":80,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"w":512,"h":768,"ip":48,"op":80,"st":48,"bm":0},{"ddd":0,"ind":13,"ty":0,"nm":"02 fire middle flame1","parent":1,"refId":"comp_15","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":0,"nm":"02 fire middle cycle outlines","parent":1,"refId":"comp_16","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":64,"op":96,"st":64,"bm":0},{"ddd":0,"ind":15,"ty":0,"nm":"02 fire middle cycle outlines","parent":1,"refId":"comp_16","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":32,"op":64,"st":32,"bm":0},{"ddd":0,"ind":16,"ty":0,"nm":"02 fire middle cycle outlines","parent":1,"refId":"comp_16","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":0,"nm":"02 fire middle end outl","parent":1,"refId":"comp_17","sr":1,"ks":{"p":{"a":0,"k":[0,-359,0]},"a":{"a":0,"k":[256,384,0]}},"ao":0,"w":512,"h":768,"ip":96,"op":152,"st":96,"bm":0}]},{"id":"comp_30","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","parent":2,"sr":1,"ks":{"p":{"a":0,"k":[-77.334,-175.69,0]},"a":{"a":0,"k":[-77.334,-175.69,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.795,-2.463],[-22.463,0.136]],"o":[[-6.751,9.267],[21.909,-0.132]],"v":[[28.78,287.216],[26.463,308]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[6.778,-9.304],[-84.843,0.513]],"o":[[-25.5,35],[82.75,-0.5]],"v":[[35,213],[26.25,291.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[41.584,-3.199],[-34.387,-1.279]],"o":[[-37.621,2.894],[34.387,1.279]],"v":[[41.616,212.199],[44.975,259.221]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[9.857,-0.643],[-6.643,-0.429]],"o":[[-9.65,0.629],[6.643,0.429]],"v":[[38.138,192.643],[39.103,203.571]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.286,-0.214],[-2.214,-0.143]],"o":[[-3.217,0.21],[2.214,0.143]],"v":[[21.707,90.214],[22.029,93.857]],"c":true}]},{"t":41,"s":[{"i":[[3.286,-0.214],[-2.214,-0.143]],"o":[[-3.217,0.21],[2.214,0.143]],"v":[[19.707,-427.786],[20.029,-424.143]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[33.144,5.233],[-24.721,-0.221]],"o":[[-35.022,-5.53],[27.911,0.249]],"v":[[19.555,225.935],[15.319,283.751]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[29.02,-23.268],[-29.148,15.285]],"o":[[-36.827,29.528],[27.206,-14.266]],"v":[[18.827,100.658],[25.794,178.294]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[36.864,-1.646],[-34.889,28.965]],"o":[[-35.938,1.604],[46.08,13.495]],"v":[[1.709,52.646],[-0.595,87.535]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.216,-6.659],[-24.954,25.786]],"o":[[-28.004,9.704],[30.5,0]],"v":[[16.732,-8.349],[24.773,17.714]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[12.002,-2.4],[-12.348,2.646]],"o":[[-16.253,3.251],[13.442,-2.88]],"v":[[25.798,-28.588],[29.158,-15.146]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":51,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[29.237,-0.424],[18.659,-25.84],[-78.306,-8.932],[-2.4,-26.987],[0.564,-42.682],[30.491,12.722]],"o":[[-3.4,-14.426],[-41.473,-22.64],[-12.806,-41.432],[1.048,11.781],[27.835,19.62],[5.813,-25.223]],"v":[[106.649,132.4],[49.973,125.64],[49.306,238.932],[79.9,226.987],[116.436,225.182],[131.993,185.252]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[18.356,-65.631],[1.004,-68.279],[-92.58,7.072],[-16.188,1.28],[-17.016,-0.736],[29.057,17.998]],"o":[[12.356,-49.631],[-116.996,-73.279],[18.803,-1.436],[15.763,-1.246],[26.568,1.15],[25.666,-30.324]],"v":[[114.644,-62.369],[48.996,-92.721],[50.08,17.428],[99.158,-10.713],[145.932,9.85],[160.334,-37.676]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[40.408,-8.621],[8.905,-22.866],[-63.43,19.052],[-2.553,-27.854],[-0.664,-27.112],[37.995,7.577]],"o":[[-15.642,-10.077],[-92.499,-21.673],[-3,-37.948],[6.447,-39.854],[25.263,14.209],[-3.038,-25.183]],"v":[[102.472,-151.481],[53.507,-145.134],[65,-51.052],[106.553,-41.146],[153.664,-66.888],[163.996,-115.133]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[25.979,-38.681],[-12.816,-22.414],[-71.606,9.11],[-7.652,-23.023],[-8.541,18],[3.417,17.237]],"o":[[6.479,-39.181],[-74.816,-41.414],[-4.106,-29.39],[7.848,-31.023],[7.401,-15.598],[-5.628,-28.395]],"v":[[80.521,-174.319],[39.316,-172.086],[38.606,-82.61],[103.652,-89.977],[145.259,-114.137],[152.031,-167.148]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[35.852,-24.204],[6.121,-6.787],[-30.147,-5.617],[-12.364,11.19],[-4.863,14.486],[5.544,11.648]],"o":[[-15.19,-0.044],[-18.957,21.021],[11.029,2.055],[17.765,0.658],[4.214,-12.553],[-9.133,-19.188]],"v":[[66.426,-225.746],[34.942,-214.601],[66.05,-144.055],[101.841,-156.26],[135.809,-181.382],[133.83,-220.533]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[11.188,-2.48],[2.145,-2.133],[-8.537,-4.421],[-3.25,0.288],[-2.088,4.234],[1.472,3.752]],"o":[[-5.164,1.145],[-6.643,6.608],[3.123,1.618],[6.515,-0.577],[1.81,-3.669],[-2.424,-6.18]],"v":[[99.368,-273.52],[88.579,-268.455],[96.629,-247.5],[106.444,-245.289],[119.333,-253.457],[119.83,-265.206]],"c":true}]},{"t":60,"s":[{"i":[[2.469,0.044],[0.542,-0.415],[-1.676,-1.418],[-0.709,0.023],[-0.595,0.897],[0.16,0.894]],"o":[[-1.158,-0.021],[-1.68,1.285],[0.613,0.519],[1.347,-0.043],[0.516,-0.777],[-0.264,-1.473]],"v":[[96.518,-299.069],[93.999,-298.426],[94.96,-292.841],[96.99,-292.023],[99.944,-293.62],[100.503,-296.249]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-3.499,-24.848],[-6.884,-16.478],[-0.301,7.46],[-4.644,0.228],[-25.003,12.782],[33.341,-4.053]],"o":[[-34.078,22.449],[3.276,7.842],[1.294,-32.082],[19.012,20.499],[85.358,33.282],[-6.489,-19.362]],"v":[[-102.261,200.848],[-133.404,262.516],[-118.294,281.082],[-102.012,287.001],[-31.997,296.218],[-11.544,205.996]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[-0.747,-48.874],[-2.455,-35.731],[-40.679,19.746],[-10.189,-28.494],[-11.643,4.734],[85.039,22.458]],"o":[[-21.211,-29.874],[-35.532,22.641],[-1.215,-25.539],[22.811,19.506],[80.357,20.734],[29.075,-42.399]],"v":[[-100.253,19.874],[-169.932,40.216],[-144.785,98.539],[-93.811,115.494],[-40.357,117.266],[-14.075,40.399]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[4.596,-2.157],[-2.838,-33.101],[-23.392,21.782],[64.52,-34.091],[-7.412,7.454],[7.166,18.65]],"o":[[-43.115,-41.195],[-25.698,17.837],[-20.978,-41.699],[20.24,22.409],[13.576,-13.654],[-5.083,-13.23]],"v":[[-78.36,-37.305],[-165.347,-14.561],[-142.022,35.199],[-94.52,47.591],[-39.74,43.428],[-28.273,-12.423]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.586,24.077],[1.481,-16.058],[-4.9,-5.507],[-10.179,0.727],[8.719,-39.971],[89.45,-41.348]],"o":[[-19.447,-7.56],[-0.705,7.642],[4.542,5.105],[-3.153,-23.324],[83.219,35.029],[-14.55,-40.348]],"v":[[-113.586,-84.077],[-147.276,-60.135],[-141.231,-39.204],[-119.347,-31.676],[-66.219,-26.529],[-64.45,-76.152]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[13.764,-16.52],[-0.797,-14.42],[-5.435,-4.397],[-9.222,1.812],[-4.425,5.611],[7.725,10.732]],"o":[[-22.868,-5.87],[0.379,6.863],[5.039,4.077],[14.57,16.763],[8.105,-10.278],[-5.48,-7.613]],"v":[[-80.463,-124.48],[-113.116,-101.094],[-104.345,-83.066],[-82.915,-78.763],[-39.977,-80.996],[-37.614,-117.588]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[9.081,-4.12],[1.941,-4.856],[-0.074,-2.217],[-1.433,-1.373],[-2.412,2.764],[3.877,5.235]],"o":[[-4.629,2.1],[-0.924,2.311],[0.069,2.056],[11.907,11.408],[4.417,-5.063],[-2.75,-3.714]],"v":[[-75.274,-166.527],[-85.585,-154.758],[-86.926,-147.859],[-85.155,-142.261],[-54.718,-143.901],[-53.648,-161.404]],"c":true}]},{"t":60,"s":[{"i":[[0.601,-0.289],[0.013,-0.847],[-0.221,-0.37],[-0.364,-0.269],[-0.704,0.652],[0.934,1.027]],"o":[[-1.307,0.628],[-0.006,0.403],[0.205,0.343],[0.87,0.643],[1.289,-1.194],[-0.662,-0.729]],"v":[[-79.613,-181.42],[-81.494,-179.083],[-81.161,-177.91],[-80.299,-176.982],[-73.89,-177.455],[-73.114,-181.387]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392216701,0.972549079446,0.949019667682,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"p":{"a":0,"k":[280.666,670.31,0]},"a":{"a":0,"k":[24.666,286.31,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[20,20,100]},{"t":20,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[1.795,-2.463],[-22.463,0.136]],"o":[[-6.751,9.267],[21.909,-0.132]],"v":[[28.78,287.216],[26.463,308]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[6.778,-9.304],[-84.843,0.513]],"o":[[-25.5,35],[82.75,-0.5]],"v":[[35,213],[26.25,291.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[65,-5],[-53.75,-2]],"o":[[-58.805,4.523],[53.75,2]],"v":[[34,209.5],[39.25,283]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[23,-1.5],[-15.5,-1]],"o":[[-22.516,1.468],[15.5,1]],"v":[[35.5,190.5],[37.75,216]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[3.286,-0.214],[-2.214,-0.143]],"o":[[-3.217,0.21],[2.214,0.143]],"v":[[21.707,90.214],[22.029,93.857]],"c":true}]},{"t":41,"s":[{"i":[[3.286,-0.214],[-2.214,-0.143]],"o":[[-3.217,0.21],[2.214,0.143]],"v":[[19.707,-427.786],[20.029,-424.143]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[66.5,10.5],[-49.599,-0.443]],"o":[[-70.267,-11.095],[56,0.5]],"v":[[20.5,199.5],[12,315.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[43.734,-35.066],[-43.928,23.035]],"o":[[-55.5,44.5],[41,-21.5]],"v":[[15.5,72.5],[26,189.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[56,-2.5],[-53,44]],"o":[[-54.592,2.437],[70,20.5]],"v":[[-4.5,51.5],[-8,104.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[34.653,-12.008],[-45,46.5]],"o":[[-50.5,17.5],[55,0]],"v":[[9.5,-11.5],[24,35.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[25,-5],[-25.722,5.512]],"o":[[-33.855,6.771],[28,-6]],"v":[[23,-29],[30,-1]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[20.461,-75.516],[21.822,-70.072]],"c":true}]},{"t":51,"s":[{"i":[[4.861,-0.972],[-5.001,1.072]],"o":[[-6.583,1.317],[5.444,-1.167]],"v":[[10.461,-427.516],[11.822,-422.072]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[40.224,-0.584],[25.671,-35.551],[-117.344,4.076],[-13.845,-2.09],[-15.219,20.222],[41.95,17.503]],"o":[[-4.678,-19.848],[-63.829,-76.551],[7.749,18.334],[16.09,2.429],[38.296,26.994],[7.997,-34.703]],"v":[[108.178,121.348],[34.329,117.551],[25.844,278.924],[70,279],[118.204,260.006],[148.55,186.497]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[42.564,-6.226],[40.813,-50.225],[-85.297,31.262],[-32.063,3.791],[-15.43,23.796],[36.231,22.442]],"o":[[-6.076,-18.138],[-120.187,-27.225],[8.47,17.96],[23.983,5.161],[35.183,9.711],[3.616,-40.885]],"v":[[125.779,-104.633],[37.687,-123.775],[35.297,47.238],[94,72],[154.817,37.789],[175.269,-33.942]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[50.807,-10.84],[11.197,-28.751],[-79.754,23.956],[-21.881,6.283],[-12.991,22.817],[47.774,9.527]],"o":[[-19.668,-12.67],[-116.303,-27.251],[12.896,12.677],[24.971,6.064],[31.765,17.866],[-3.819,-31.664]],"v":[[94.869,-161.73],[33.303,-153.749],[47.754,-35.456],[100,-23],[159.235,-55.366],[172.226,-116.027]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[61.33,-27.94],[13.035,-11.794],[-88,34.498],[-24.613,14.871],[-11.285,23.783],[4.514,22.775]],"o":[[-22.734,-8.189],[-81.69,0.79],[16.437,2.441],[24.287,7.59],[9.779,-20.609],[-7.436,-37.518]],"v":[[69.474,-198.525],[15.69,-190.29],[42.5,-42.998],[104,-60],[158.974,-91.922],[167.923,-161.964]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[49.628,-33.504],[8.473,-9.395],[-41.73,-7.776],[-17.115,15.49],[-6.732,20.052],[7.675,16.124]],"o":[[-21.027,-0.06],[-26.241,29.098],[15.267,2.845],[24.591,0.911],[5.833,-17.376],[-12.642,-26.561]],"v":[[48.978,-222.185],[5.396,-206.758],[48.457,-109.106],[98,-126],[145.019,-160.775],[142.281,-214.968]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[20.595,-4.565],[3.948,-3.927],[-15.714,-8.139],[-5.982,0.53],[-3.844,7.794],[2.709,6.906]],"o":[[-9.506,2.107],[-12.229,12.163],[5.749,2.978],[11.992,-1.063],[3.331,-6.754],[-4.463,-11.376]],"v":[[87.975,-274.467],[68.116,-265.142],[82.934,-226.569],[101,-222.5],[124.726,-237.535],[125.642,-259.162]],"c":true}]},{"t":60,"s":[{"i":[[4.312,0.077],[0.947,-0.725],[-2.927,-2.476],[-1.238,0.04],[-1.039,1.566],[0.28,1.562]],"o":[[-2.023,-0.036],[-2.933,2.244],[1.071,0.906],[2.353,-0.076],[0.9,-1.357],[-0.461,-2.572]],"v":[[95.675,-299.303],[91.277,-298.18],[92.954,-288.429],[96.5,-287],[101.657,-289.789],[102.633,-294.38]],"c":true}]}]},"nm":"Path 2","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[-4.046,-28.733],[-7.961,-19.055],[-7.058,-4.972],[-5.37,0.263],[-10.383,14.21],[38.553,-4.686]],"o":[[-39.406,25.959],[3.789,9.068],[6.543,4.609],[20.166,40.644],[98.704,38.486],[-7.504,-22.39]],"v":[[-106.454,196.733],[-142.466,268.042],[-124.994,289.511],[-106.166,296.356],[-25.204,307.014],[-1.553,202.686]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[7.754,-10.103],[-2.864,-41.686],[-47.459,23.038],[-17.819,6.695],[-11.001,13.349],[99.213,26.201]],"o":[[-24.746,-34.853],[-41.454,26.415],[10.089,11.703],[9.571,21.591],[45.708,87.19],[-6.546,-22.901]],"v":[[-105.754,6.853],[-187.046,30.585],[-146.041,114.963],[-104.071,125.409],[-34.708,136.81],[-5.213,30.799]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[5.648,-2.651],[-3.488,-40.68],[-28.748,26.769],[-22.563,7.933],[-9.109,9.161],[8.807,22.921]],"o":[[-52.986,-50.627],[-31.582,21.921],[15.404,15.153],[24.874,27.539],[16.685,-16.781],[-6.247,-16.259]],"v":[[-65.014,-36.373],[-171.918,-8.421],[-143.252,52.731],[-84.874,67.961],[-17.551,62.845],[-3.459,-5.795]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[19.861,-24.879],[2.389,-25.903],[-7.904,-8.883],[-16.419,1.173],[-8.619,13.394],[50.602,-9.162]],"o":[[-31.37,-12.195],[-1.137,12.327],[7.327,8.235],[20.153,51.095],[59.455,-7.707],[-9.228,-15.356]],"v":[[-114.361,-87.621],[-168.705,-49.001],[-158.954,-15.239],[-123.653,-3.095],[-37.955,5.207],[-35.102,-74.838]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":40,"s":[{"i":[[20.907,-25.093],[-1.21,-21.904],[-8.256,-6.68],[-14.008,2.753],[-6.721,8.523],[11.735,16.301]],"o":[[-34.736,-8.916],[0.576,10.424],[7.654,6.192],[22.131,25.463],[12.311,-15.612],[-8.324,-11.564]],"v":[[-85.407,-125.907],[-135.006,-90.384],[-121.683,-62.999],[-89.131,-56.463],[-23.909,-59.855],[-20.32,-115.438]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[16.089,-7.299],[3.439,-8.602],[-0.131,-3.928],[-2.539,-2.432],[-4.273,4.897],[6.868,9.275]],"o":[[-8.201,3.721],[-1.637,4.094],[0.122,3.642],[21.095,20.211],[7.826,-8.97],[-4.872,-6.579]],"v":[[-77.089,-167.201],[-95.356,-146.351],[-97.732,-134.128],[-94.595,-124.211],[-40.671,-127.117],[-38.776,-158.124]],"c":true}]},{"t":60,"s":[{"i":[[1.463,-0.702],[0.031,-2.062],[-0.538,-0.9],[-0.887,-0.655],[-1.713,1.587],[2.273,2.5]],"o":[[-3.182,1.528],[-0.015,0.981],[0.498,0.834],[2.118,1.565],[3.138,-2.907],[-1.612,-1.773]],"v":[[-78.589,-181.314],[-83.167,-175.626],[-82.357,-172.772],[-80.258,-170.512],[-64.659,-171.663],[-62.771,-181.234]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.945098099054,0.90588241278,0.792156922583,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.917647118662,0.847058883368,0.721568627451,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":60,"st":0,"bm":0}]},{"id":"comp_31","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 33","sr":1,"ks":{"p":{"a":0,"k":[768,866.288,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-0.223,-3.321],[-0.789,2.008]],"o":[[0.105,1.566],[1.017,-2.585]],"v":[[237.765,-1024.967],[243.141,-1024.24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":69,"s":[{"i":[[-0.223,-3.321],[-0.789,2.007]],"o":[[0.105,1.566],[1.017,-2.585]],"v":[[237.765,-1008.967],[243.141,-1008.24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[-0.223,-3.321],[-0.789,2.007]],"o":[[0.105,1.566],[1.017,-2.585]],"v":[[236.346,-524.967],[241.722,-524.24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[-5.342,-59.486],[-13.348,36.334]],"o":[[2.52,28.059],[17.191,-46.793]],"v":[[201.885,-588.486],[298.618,-577.622]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":140,"s":[{"i":[[-15.455,-60.898],[-7.814,39.963]],"o":[[7.29,28.725],[10.063,-51.467]],"v":[[200.215,-675.425],[302.5,-680.251]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":169,"s":[{"i":[[-0.254,-0.782],[-0.068,0.529]],"o":[[0.12,0.369],[0.088,-0.681]],"v":[[241.36,-761.666],[242.692,-761.816]],"c":true}]},{"t":170,"s":[{"i":[[-0.254,-0.782],[-0.068,0.529]],"o":[[0.12,0.369],[0.088,-0.681]],"v":[[233.36,-1093.666],[234.692,-1093.816]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-1.677,-2.156],[0.08,0.867]],"o":[[0.531,0.683],[-0.184,-1.997]],"v":[[70.838,-933.52],[73.428,-934.29]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":69,"s":[{"i":[[-1.677,-2.156],[0.08,0.867]],"o":[[0.531,0.683],[-0.184,-1.997]],"v":[[70.838,-917.52],[73.428,-918.29]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[-1.677,-2.155],[0.08,0.867]],"o":[[0.531,0.683],[-0.184,-1.997]],"v":[[69.419,-433.52],[72.009,-434.29]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[-87.333,-120.809],[2.702,47.44]],"o":[[27.678,38.287],[-6.226,-109.313]],"v":[[61.469,-509.574],[204.247,-546.629]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[-55.487,-140.171],[-9.175,47.17]],"o":[[17.585,44.423],[21.142,-108.689]],"v":[[49.539,-656.724],[198.711,-657.457]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":166,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[97,-813.288],[97,-813.288]],"c":true}]},{"t":167,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[89,-1145.288],[89,-1145.288]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[-0.952,-1.6],[0.095,0.829],[0.849,-0.064]],"o":[[0.752,1.263],[-0.147,-1.287],[-1.042,0.079]],"v":[[288.935,-941.517],[291.86,-942.421],[289.911,-944.178]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":39,"s":[{"i":[[-0.952,-1.6],[0.095,0.829],[0.849,-0.064]],"o":[[0.752,1.263],[-0.147,-1.287],[-1.042,0.079]],"v":[[290.163,-421.517],[293.089,-422.421],[291.14,-424.178]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[-32.583,-59.201],[2.431,30.186],[29.696,-4.505]],"o":[[25.726,46.741],[-3.774,-46.858],[-36.454,5.531]],"v":[[243.801,-477.029],[345.699,-520.67],[279.392,-580.665]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[-337.602,-308.185],[1.22,28.334],[28.569,-4.104]],"o":[[36.9,33.685],[-1.948,-45.216],[54.848,-243.641]],"v":[[226.602,-500.102],[323.428,-535.807],[262.152,-589.647]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[-77.185,-118.131],[-7.472,28.942],[29.09,7.932]],"o":[[12.775,48.988],[11.584,-45.259],[-13.578,-58.019]],"v":[[211.69,-594.276],[319.765,-589.067],[277.087,-671.871]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":160,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[253,-706.288],[253,-706.288],[253,-706.288]],"c":true}]},{"t":161,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[245,-1038.288],[245,-1038.288],[245,-1038.288]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[-0.302,-3.573],[-0.364,1.897]],"o":[[0.136,1.605],[0.569,-2.965]],"v":[[361.212,-954.714],[365.543,-954.336]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":39,"s":[{"i":[[-0.302,-3.573],[-0.364,1.897]],"o":[[0.136,1.605],[0.569,-2.965]],"v":[[362.44,-434.714],[366.772,-434.336]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[-5.794,-68.569],[-6.98,36.395]],"o":[[2.602,30.794],[10.911,-56.893]],"v":[[328.696,-513.456],[416.726,-492.683]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[-6.145,-72.718],[-7.403,38.597]],"o":[[2.76,32.658],[11.572,-60.336]],"v":[[333.438,-602.657],[422.337,-592.885]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":163,"s":[{"i":[[-0.268,-3.169],[-0.323,1.682]],"o":[[0.12,1.423],[0.504,-2.629]],"v":[[363.189,-680.242],[367.007,-679.969]],"c":true}]},{"t":164,"s":[{"i":[[-0.268,-3.169],[-0.323,1.682]],"o":[[0.12,1.423],[0.504,-2.629]],"v":[[355.189,-1012.242],[359.007,-1011.969]],"c":true}]}]},"nm":"Path 8","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0.451,-3.031],[-0.374,2.746],[0.732,0.578]],"o":[[-0.293,1.969],[0.132,-0.969],[-1.903,-1.504]],"v":[[-115.69,-1002.268],[-108.475,-1001.515],[-109.524,-1003.874]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[0.451,-3.031],[-0.374,2.746],[0.732,0.578]],"o":[[-0.293,1.969],[0.132,-0.969],[-1.903,-1.504]],"v":[[-115.69,-986.268],[-108.475,-985.515],[-109.524,-987.874]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[0.451,-3.031],[-0.374,2.746],[0.732,0.578]],"o":[[-0.293,1.969],[0.132,-0.969],[-1.903,-1.504]],"v":[[-113.69,-406.268],[-106.475,-405.515],[-107.524,-407.874]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[-38.977,-43.803],[-18.145,94.928],[25.732,11.42]],"o":[[-61.102,47.212],[5.038,-26.359],[-66.939,-29.709]],"v":[[-124.176,-501.216],[68.711,-476.216],[31.224,-532.319]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78,"s":[{"i":[[-38.42,-54.458],[40.229,71.489],[22.204,12.329]],"o":[[-55.143,52.759],[10.318,-27.739],[-57.76,-32.072]],"v":[[-154.392,-522.524],[13.156,-502.776],[-11.161,-562.514]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87,"s":[{"i":[[-31.344,-50.289],[32.722,62.873],[19.508,8.479]],"o":[[-42.344,46.274],[7.643,-24.152],[-50.748,-22.057]],"v":[[-138.728,-548.891],[9.639,-555.16],[-13.613,-603.415]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[-103.459,-215.483],[29.308,65.598],[87.719,-26.389]],"o":[[-32.992,48.05],[89.898,-26.744],[35.043,-130.317]],"v":[[-156.394,-589.554],[1.997,-601.886],[-48.501,-691.933]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[-50.993,-134.357],[21.184,70.149],[49.266,-5.489]],"o":[[-6.59,50.834],[32.255,-26.174],[-23.829,-64.578]],"v":[[-168.525,-671.122],[9.598,-703.002],[-39.751,-769.304]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":166,"s":[{"i":[[-0.108,-0.527],[0.103,0.476],[0.156,0.054]],"o":[[0.07,0.342],[-0.036,-0.168],[-0.406,-0.139]],"v":[[-106.568,-820.63],[-105.325,-820.921],[-105.641,-821.251]],"c":true}]},{"t":167,"s":[{"i":[[-0.108,-0.527],[0.103,0.476],[0.156,0.053]],"o":[[0.07,0.343],[-0.036,-0.168],[-0.406,-0.139]],"v":[[-114.568,-1152.63],[-113.325,-1152.921],[-113.641,-1153.251]],"c":true}]}]},"nm":"Path 9","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-348,-1054.288],[-348,-1054.288],[-348,-1054.288],[-348,-1054.288],[-348,-1054.288]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-348,-1038.288],[-348,-1038.288],[-348,-1038.288],[-348,-1038.288],[-348,-1038.288]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-346,-458.288],[-346,-458.288],[-346,-458.288],[-346,-458.288],[-346,-458.288]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[-72.133,20.573],[-8.441,30.069],[31.859,-22.763],[17.807,-4.46],[2.557,-14.832]],"o":[[3.162,25.784],[13.284,-47.323],[-19.821,-11.076],[-26.195,6.561],[-3.16,18.33]],"v":[[-299.075,-502.593],[-195.743,-489.357],[-284.594,-545.552],[-342.838,-553.106],[-391.704,-513.472]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":67,"s":[{"i":[[-69.352,19.195],[-8.604,30.649],[32.519,-23.063],[17.232,-5.947],[2.551,-17.614]],"o":[[3.223,26.282],[13.526,-48.185],[-19.2,-10.538],[-25.349,8.749],[-3.153,21.768]],"v":[[-298.27,-489.569],[-192.944,-494.287],[-283.404,-551.637],[-339.784,-556.016],[-387.153,-508.491]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78,"s":[{"i":[[-87.455,39.11],[-10.617,37.82],[40.23,-28.143],[74.369,-68.916],[-149.028,-56.534]],"o":[[3.977,32.431],[16.659,-59.344],[73.273,-62.648],[-69.881,-63.413],[-83.346,71.374]],"v":[[-322.273,-531.398],[-202.302,-500.697],[-313.69,-571.626],[-394.369,-658.372],[-451.841,-602.482]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":138,"s":[{"i":[[-22.947,-1.251],[-14.249,31.232],[38.78,-17.282],[17.801,-16.201],[-32.86,-23.753]],"o":[[-0.923,28.539],[22.113,-48.468],[18.246,-11.706],[-17.81,-15.984],[-25.777,19.408]],"v":[[-294.35,-597.819],[-183.587,-570.926],[-268.963,-647.742],[-289.754,-669.037],[-322.236,-647.134]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":167,"s":[{"i":[[-0.038,-0.307],[-0.27,0.962],[1.044,-0.652],[0.07,-0.079],[0.052,-0.173]],"o":[[0.101,0.825],[0.417,-1.486],[-0.088,0.055],[-0.103,0.117],[-0.064,0.214]],"v":[[-251.554,-688.673],[-248.249,-688.249],[-251.034,-690.086],[-251.272,-689.885],[-251.508,-689.452]],"c":true}]},{"t":168,"s":[{"i":[[-0.038,-0.307],[-0.27,0.962],[1.044,-0.652],[0.07,-0.079],[0.052,-0.173]],"o":[[0.101,0.825],[0.417,-1.486],[-0.088,0.055],[-0.103,0.117],[-0.064,0.214]],"v":[[-259.554,-1020.673],[-256.249,-1020.249],[-259.034,-1022.086],[-259.272,-1021.885],[-259.508,-1021.452]],"c":true}]}]},"nm":"Path 10","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-0.86,-2.468],[0.177,1.253]],"o":[[0.171,0.491],[-0.306,-2.172]],"v":[[-404.14,-929.549],[-401.505,-930.201]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[-0.86,-2.468],[0.177,1.253]],"o":[[0.171,0.491],[-0.306,-2.172]],"v":[[-404.14,-913.549],[-401.505,-914.201]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[-0.86,-2.468],[0.177,1.253]],"o":[[0.171,0.491],[-0.306,-2.172]],"v":[[-390.14,-545.549],[-387.505,-546.201]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[-23.735,-79.476],[3.806,39.955]],"o":[[4.724,15.818],[-6.599,-69.274]],"v":[[-420.545,-614.352],[-336.102,-631.243]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[-25.221,-84.449],[4.044,42.455]],"o":[[5.02,16.807],[-7.011,-73.609]],"v":[[-423.425,-606.795],[-333.698,-624.743]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":166,"s":[{"i":[[-1.636,-5.48],[0.262,2.755]],"o":[[0.326,1.091],[-0.455,-4.776]],"v":[[-397.322,-690.878],[-391.5,-692.042]],"c":true}]},{"t":167,"s":[{"i":[[-1.636,-5.48],[0.262,2.755]],"o":[[0.326,1.091],[-0.455,-4.776]],"v":[[-405.322,-1022.878],[-399.5,-1024.042]],"c":true}]}]},"nm":"Path 11","hd":false},{"ind":7,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0.444,-2.846],[-0.06,2.162],[0.67,0.546]],"o":[[-0.438,2.81],[0.029,-1.027],[-1.558,-1.269]],"v":[[-486.367,-920.217],[-480.556,-919.65],[-481.655,-922.052]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[0.444,-2.846],[-0.06,2.162],[0.67,0.546]],"o":[[-0.439,2.81],[0.029,-1.027],[-1.558,-1.269]],"v":[[-486.367,-904.217],[-480.556,-903.65],[-481.655,-906.052]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62,"s":[{"i":[[0.444,-2.846],[-0.06,2.162],[0.67,0.546]],"o":[[-0.439,2.81],[0.029,-1.027],[-1.558,-1.269]],"v":[[-485.852,-462.217],[-480.041,-461.65],[-481.14,-464.052]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72,"s":[{"i":[[6.07,-38.905],[-0.827,29.56],[9.164,7.465]],"o":[[-5.995,38.42],[0.393,-14.04],[-21.298,-17.349]],"v":[[-513.324,-514.208],[-433.885,-506.456],[-448.904,-539.292]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":140,"s":[{"i":[[5.401,-34.613],[-0.736,26.299],[8.153,6.641]],"o":[[-5.333,34.182],[0.349,-12.492],[-18.949,-15.435]],"v":[[-506.688,-592.469],[-436.012,-585.573],[-449.374,-614.787]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":169,"s":[{"i":[[0.25,-1.604],[-0.034,1.218],[0.378,0.308]],"o":[[-0.247,1.584],[0.016,-0.579],[-0.878,-0.715]],"v":[[-487.492,-654.871],[-484.218,-654.552],[-484.837,-655.905]],"c":true}]},{"t":170,"s":[{"i":[[0.25,-1.604],[-0.034,1.218],[0.378,0.308]],"o":[[-0.247,1.584],[0.016,-0.579],[-0.878,-0.715]],"v":[[-495.492,-986.871],[-492.218,-986.552],[-492.837,-987.905]],"c":true}]}]},"nm":"Path 12","hd":false},{"ind":8,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-0.439,-2.059],[0.427,1.684]],"o":[[0.386,1.811],[-0.592,-2.334]],"v":[[-553.821,-921.181],[-549.938,-921.954]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[-0.439,-2.059],[0.427,1.684]],"o":[[0.386,1.811],[-0.592,-2.334]],"v":[[-553.821,-905.181],[-549.938,-905.954]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[-0.439,-2.059],[0.427,1.684]],"o":[[0.386,1.811],[-0.592,-2.334]],"v":[[-539.821,-537.181],[-535.938,-537.954]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[-5.879,-16.594],[5.372,13.497]],"o":[[5.172,14.6],[-7.446,-18.709]],"v":[[-545.121,-570.309],[-513.765,-580.939]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":139,"s":[{"i":[[-5.901,-16.658],[5.392,13.549]],"o":[[5.192,14.656],[-7.474,-18.781]],"v":[[-544.278,-567.943],[-512.801,-578.614]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":168,"s":[{"i":[[-1.05,-2.965],[0.96,2.412]],"o":[[0.924,2.609],[-1.33,-3.343]],"v":[[-542.164,-610.896],[-536.561,-612.796]],"c":true}]},{"t":169,"s":[{"i":[[-1.05,-2.965],[0.96,2.412]],"o":[[0.924,2.609],[-1.33,-3.343]],"v":[[-550.164,-942.896],[-544.561,-944.796]],"c":true}]}]},"nm":"Path 13","hd":false},{"ind":9,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0.436,1.704],[0.799,-0.276]],"o":[[-0.306,-1.196],[-0.603,0.208]],"v":[[-610.147,-971.692],[-612.448,-973.012]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[0.436,1.704],[0.799,-0.276]],"o":[[-0.306,-1.196],[-0.603,0.208]],"v":[[-610.147,-955.692],[-612.448,-957.012]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62,"s":[{"i":[[0.436,1.704],[0.799,-0.276]],"o":[[-0.306,-1.196],[-0.603,0.208]],"v":[[-609.632,-513.692],[-611.933,-515.012]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72,"s":[{"i":[[-2.489,13.826],[4.33,-5.184]],"o":[[2.181,-12.115],[-13.423,16.072]],"v":[[-591.165,-538.614],[-612.812,-549.155]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":136,"s":[{"i":[[-2.612,20.204],[6.681,-12.817]],"o":[[2.756,-21.685],[-15.352,28.585]],"v":[[-568.224,-566.492],[-617.715,-589.605]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":165,"s":[{"i":[[-0.049,0.416],[0.139,-0.29]],"o":[[0.055,-0.466],[-0.295,0.614]],"v":[[-601.382,-612.704],[-602.49,-613.218]],"c":true}]},{"t":166,"s":[{"i":[[-0.049,0.416],[0.139,-0.29]],"o":[[0.055,-0.466],[-0.295,0.614]],"v":[[-609.382,-944.704],[-610.49,-945.218]],"c":true}]}]},"nm":"Path 16","hd":false},{"ind":10,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0.164,-3.557],[-0.651,2.476]],"o":[[-0.175,3.776],[0.806,-3.065]],"v":[[-288.352,-1112.731],[-283.559,-1111.922]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[0.164,-3.557],[-0.651,2.476]],"o":[[-0.175,3.776],[0.806,-3.065]],"v":[[-288.351,-1096.731],[-283.559,-1095.922]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[0.164,-3.557],[-0.651,2.476]],"o":[[-0.175,3.776],[0.806,-3.065]],"v":[[-286.351,-516.731],[-281.559,-515.922]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[2.324,-50.247],[-9.202,34.979]],"o":[[-2.467,53.341],[11.393,-43.304]],"v":[[-306.993,-580.629],[-239.292,-569.199]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":133,"s":[{"i":[[2.153,-46.558],[-8.527,32.41]],"o":[[-2.286,49.424],[10.556,-40.125]],"v":[[-319.121,-667.712],[-256.391,-657.122]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":162,"s":[{"i":[[0.056,-1.207],[-0.221,0.84]],"o":[[-0.059,1.281],[0.274,-1.04]],"v":[[-311.688,-727.569],[-310.062,-727.295]],"c":true}]},{"t":163,"s":[{"i":[[0.056,-1.207],[-0.221,0.84]],"o":[[-0.059,1.281],[0.274,-1.04]],"v":[[-319.688,-1059.569],[-318.062,-1059.295]],"c":true}]}]},"nm":"Path 14","hd":false},{"ind":11,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-0.099,-1.547],[0.122,0.483]],"o":[[-0.233,0.571],[0.37,-0.986]],"v":[[7.57,-944.741],[9.108,-944.508]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":69,"s":[{"i":[[-0.099,-1.547],[0.122,0.483]],"o":[[-0.233,0.571],[0.37,-0.986]],"v":[[7.57,-928.741],[9.109,-928.508]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[-0.099,-1.547],[0.122,0.483]],"o":[[-0.233,0.571],[0.37,-0.986]],"v":[[6.151,-444.741],[7.689,-444.508]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[-10.573,-109.271],[-19.225,37.116]],"o":[[3.245,47.089],[35.066,-67.698]],"v":[[-33.245,-508.877],[74.434,-492.59]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":132,"s":[{"i":[[-0.865,-86.858],[0.432,24.939]],"o":[[-5.822,31.41],[13.995,-55.319]],"v":[[-31.132,-607.48],[56.494,-594.226]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":161,"s":[{"i":[[0.113,-4.165],[-0.19,1.123]],"o":[[-0.04,1.484],[0.449,-2.651]],"v":[[-3.119,-699.053],[1.124,-698.411]],"c":true}]},{"t":162,"s":[{"i":[[0.113,-4.165],[-0.19,1.124]],"o":[[-0.04,1.484],[0.449,-2.651]],"v":[[-11.119,-1031.053],[-6.876,-1030.411]],"c":true}]}]},"nm":"Path 15","hd":false},{"ind":12,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[-0.098,-0.633],[-0.857,0.923],[1.79,-0.946]],"o":[[0.077,0.496],[1.653,0.298],[-0.823,-0.12]],"v":[[470.629,-994.934],[473.075,-994.626],[472.283,-995.63]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":39,"s":[{"i":[[-0.098,-0.633],[-0.857,0.923],[1.79,-0.946]],"o":[[0.077,0.496],[1.653,0.298],[-0.823,-0.12]],"v":[[471.857,-474.934],[474.304,-474.626],[473.511,-475.63]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[-7.221,-46.494],[-87.892,79.766],[198.581,-167.912]],"o":[[5.652,36.39],[194.108,50.766],[-51.203,-77.754]],"v":[[345.774,-588.494],[504.892,-567.053],[451.346,-603.347]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":138,"s":[{"i":[[-6.848,-44.095],[-19.098,36.808],[60.094,-29.415]],"o":[[5.258,33.857],[38.602,-12.297],[-34.472,-23.002]],"v":[[317.847,-666.223],[428.931,-655.095],[390.612,-711.041]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":167,"s":[{"i":[[-0.076,-0.492],[-0.1,0.334],[0.27,0.078]],"o":[[0.058,0.376],[0.097,-0.326],[-0.341,-0.098]],"v":[[351.49,-743.717],[352.564,-743.622],[352.167,-744.254]],"c":true}]},{"t":168,"s":[{"i":[[-0.076,-0.492],[-0.1,0.334],[0.27,0.078]],"o":[[0.058,0.376],[0.097,-0.326],[-0.341,-0.098]],"v":[[343.49,-1075.717],[344.564,-1075.622],[344.167,-1076.254]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":13,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-1.266,-4.784],[-0.496,1.775]],"o":[[0.615,2.322],[1.11,-3.968]],"v":[[456.064,-939.834],[460.091,-939.764]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":125,"s":[{"i":[[-1.266,-4.784],[-0.496,1.775]],"o":[[0.615,2.322],[1.11,-3.968]],"v":[[456.064,-923.834],[460.091,-923.764]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-1.015,-3.834],[-0.398,1.422]],"o":[[0.493,1.861],[0.89,-3.18]],"v":[[457.428,-614.453],[460.656,-614.398]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[-17.438,-65.879],[-6.836,24.44]],"o":[[8.463,31.973],[15.284,-54.644]],"v":[[437.705,-661.26],[493.165,-660.3]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":169,"s":[{"i":[[-0.455,-1.719],[-0.178,0.638]],"o":[[0.221,0.834],[0.399,-1.425]],"v":[[455.358,-734.122],[456.805,-734.097]],"c":true}]},{"t":170,"s":[{"i":[[-0.455,-1.719],[-0.178,0.638]],"o":[[0.221,0.834],[0.399,-1.425]],"v":[[447.358,-1066.122],[448.805,-1066.097]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":14,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0.929,-6.009],[-0.89,3.766]],"o":[[-0.943,6.104],[1.317,-5.57]],"v":[[523.547,-932.234],[530.691,-929.895]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":125,"s":[{"i":[[0.929,-6.009],[-0.89,3.766]],"o":[[-0.943,6.104],[1.317,-5.57]],"v":[[523.547,-916.234],[530.691,-913.895]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0.863,-5.584],[-0.827,3.5]],"o":[[-0.877,5.673],[1.224,-5.176]],"v":[[524.877,-606.867],[531.515,-604.694]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[4.466,-28.896],[-4.281,18.109]],"o":[[-4.537,29.354],[6.332,-26.783]],"v":[[517.241,-652.642],[551.594,-641.397]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":168,"s":[{"i":[[0.101,-0.651],[-0.096,0.408]],"o":[[-0.102,0.661],[0.143,-0.603]],"v":[[533.63,-697.949],[534.404,-697.695]],"c":true}]},{"t":169,"s":[{"i":[[0.101,-0.651],[-0.096,0.408]],"o":[[-0.102,0.661],[0.143,-0.603]],"v":[[525.63,-1029.949],[526.404,-1029.696]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":15,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-0.994,-4.026],[0.15,3.139]],"o":[[0.58,2.348],[-0.173,-3.625]],"v":[[597.52,-990.395],[602.541,-991.428]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":125,"s":[{"i":[[-0.994,-4.026],[0.15,3.139]],"o":[[0.58,2.348],[-0.173,-3.625]],"v":[[597.52,-974.395],[602.541,-975.428]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-0.914,-3.7],[0.137,2.885]],"o":[[0.533,2.158],[-0.159,-3.331]],"v":[[598.914,-665.492],[603.528,-666.441]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[-5.486,-22.219],[0.825,17.323]],"o":[[3.2,12.957],[-0.953,-20.006]],"v":[[595.718,-699.245],[623.426,-704.944]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":166,"s":[{"i":[[-0.343,-1.389],[0.052,1.083]],"o":[[0.2,0.81],[-0.06,-1.25]],"v":[[611.212,-749.097],[612.944,-749.454]],"c":true}]},{"t":167,"s":[{"i":[[-0.343,-1.389],[0.052,1.083]],"o":[[0.2,0.81],[-0.06,-1.25]],"v":[[603.212,-1081.097],[604.944,-1081.454]],"c":true}]}]},"nm":"Path 17","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392216701,0.972549079446,0.949019667682,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":39,"op":170,"st":-6,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 31","sr":1,"ks":{"p":{"a":0,"k":[768,866.288,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-0.223,-3.321],[-0.789,2.007]],"o":[[0.105,1.566],[1.017,-2.585]],"v":[[237.765,-1004.967],[243.141,-1004.24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":69,"s":[{"i":[[-0.223,-3.321],[-0.789,2.007]],"o":[[0.105,1.566],[1.017,-2.585]],"v":[[237.765,-1008.967],[243.141,-1008.24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[-0.223,-3.321],[-0.789,2.007]],"o":[[0.105,1.566],[1.017,-2.585]],"v":[[236.346,-524.967],[241.722,-524.24]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[-7.579,-84.395],[-18.938,51.549]],"o":[[3.575,39.808],[24.389,-66.388]],"v":[[171.688,-576.426],[308.927,-561.012]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":140,"s":[{"i":[[-20.874,-82.249],[-10.553,53.975]],"o":[[9.846,38.796],[13.591,-69.512]],"v":[[174.345,-662.15],[312.493,-668.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":169,"s":[{"i":[[-0.254,-0.782],[-0.068,0.529]],"o":[[0.12,0.369],[0.088,-0.681]],"v":[[241.36,-761.666],[242.692,-761.816]],"c":true}]},{"t":170,"s":[{"i":[[-0.254,-0.782],[-0.068,0.529]],"o":[[0.12,0.369],[0.088,-0.681]],"v":[[233.36,-1093.666],[234.692,-1093.816]],"c":true}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":39,"s":[{"i":[[-1.677,-2.156],[0.08,0.867]],"o":[[0.531,0.683],[-0.184,-1.997]],"v":[[70.838,-913.52],[73.428,-914.29]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":69,"s":[{"i":[[-1.677,-2.156],[0.08,0.867]],"o":[[0.531,0.683],[-0.184,-1.997]],"v":[[70.838,-917.52],[73.428,-918.29]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[-1.677,-2.155],[0.08,0.867]],"o":[[0.531,0.683],[-0.184,-1.997]],"v":[[69.419,-433.52],[72.009,-434.29]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[-121.422,-167.964],[3.756,65.958]],"o":[[38.481,53.231],[-8.656,-151.98]],"v":[[23.4,-471.434],[221.908,-522.952]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[-76.553,-193.388],[-12.659,65.078]],"o":[[24.261,61.288],[29.168,-149.954]],"v":[[7.222,-628.036],[213.028,-629.047]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":166,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[97,-813.288],[97,-813.288]],"c":true}]},{"t":167,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[89,-1145.288],[89,-1145.288]],"c":true}]}]},"nm":"Path 6","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[-0.952,-1.6],[0.095,0.829],[0.849,-0.064]],"o":[[0.752,1.263],[-0.147,-1.287],[-1.042,0.079]],"v":[[288.935,-941.517],[291.86,-942.421],[289.911,-944.178]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":39,"s":[{"i":[[-0.952,-1.6],[0.095,0.829],[0.849,-0.064]],"o":[[0.752,1.263],[-0.147,-1.287],[-1.042,0.079]],"v":[[290.163,-421.517],[293.089,-422.421],[291.14,-424.178]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[-45.167,-82.063],[3.371,41.844],[41.164,-6.245]],"o":[[35.661,64.792],[-5.232,-64.954],[-50.531,7.666]],"v":[[215.147,-440.242],[356.396,-500.737],[264.483,-583.9]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[-401.836,-369.422],[1.794,41.659],[42.005,-6.035]],"o":[[54.08,49.718],[-2.863,-66.482],[69.366,-256.413]],"v":[[211.836,-454.866],[354.199,-507.363],[262.634,-593.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":131,"s":[{"i":[[-107.287,-164.201],[-10.386,40.23],[40.435,11.025]],"o":[[17.757,68.093],[16.102,-62.91],[-18.874,-80.647]],"v":[[186.999,-565.801],[337.223,-558.562],[277.901,-673.658]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":160,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[253,-706.288],[253,-706.288],[253,-706.288]],"c":true}]},{"t":161,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[245,-1038.288],[245,-1038.288],[245,-1038.288]],"c":true}]}]},"nm":"Path 7","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[-0.302,-3.573],[-0.364,1.897]],"o":[[0.136,1.605],[0.569,-2.965]],"v":[[361.212,-954.714],[365.543,-954.336]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":39,"s":[{"i":[[-0.302,-3.573],[-0.364,1.897]],"o":[[0.136,1.605],[0.569,-2.965]],"v":[[362.44,-434.714],[366.772,-434.336]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[-8.568,-101.394],[-10.322,53.817]],"o":[[3.848,45.535],[16.135,-84.128]],"v":[[298.871,-499.176],[429.041,-468.458]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[-8.568,-101.393],[-10.322,53.817]],"o":[[3.848,45.535],[16.135,-84.128]],"v":[[305.22,-590.579],[429.173,-576.953]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":163,"s":[{"i":[[-0.268,-3.169],[-0.323,1.682]],"o":[[0.12,1.423],[0.504,-2.629]],"v":[[363.189,-680.242],[367.007,-679.969]],"c":true}]},{"t":164,"s":[{"i":[[-0.268,-3.169],[-0.323,1.682]],"o":[[0.12,1.423],[0.504,-2.629]],"v":[[355.189,-1012.242],[359.007,-1011.969]],"c":true}]}]},"nm":"Path 8","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0.451,-3.031],[-0.374,2.746],[0.732,0.578]],"o":[[-0.293,1.969],[0.132,-0.969],[-1.903,-1.504]],"v":[[-115.69,-982.268],[-108.475,-981.515],[-109.524,-983.874]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[0.451,-3.031],[-0.374,2.746],[0.732,0.578]],"o":[[-0.293,1.969],[0.132,-0.969],[-1.903,-1.504]],"v":[[-115.69,-986.268],[-108.475,-985.515],[-109.524,-987.874]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[0.451,-3.031],[-0.374,2.746],[0.732,0.578]],"o":[[-0.293,1.969],[0.132,-0.969],[-1.903,-1.504]],"v":[[-113.69,-406.268],[-106.475,-405.515],[-107.524,-407.874]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[-54.569,-61.327],[-25.403,132.904],[36.026,15.989]],"o":[[-85.546,66.099],[7.054,-36.904],[-93.718,-41.593]],"v":[[-185.454,-488.386],[84.597,-453.384],[32.114,-531.931]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78,"s":[{"i":[[-48.548,-68.814],[50.834,90.334],[28.057,15.579]],"o":[[-69.68,66.666],[13.038,-35.052],[-72.986,-40.527]],"v":[[-191.665,-512.042],[20.051,-487.088],[-10.676,-562.574]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87,"s":[{"i":[[-45.36,-72.777],[47.355,90.989],[28.232,12.271]],"o":[[-61.28,66.967],[11.061,-34.953],[-73.443,-31.921]],"v":[[-194.953,-524.565],[19.762,-533.638],[-13.887,-603.472]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103,"s":[{"i":[[-145.34,-302.713],[41.172,92.153],[123.229,-37.072]],"o":[[-46.347,67.501],[126.289,-37.571],[49.229,-183.072]],"v":[[-200.799,-553.393],[21.711,-570.717],[-49.229,-697.216]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[-68.967,-181.716],[28.651,94.876],[66.631,-7.424]],"o":[[-8.912,68.753],[43.624,-35.4],[-32.228,-87.341]],"v":[[-213.474,-636.521],[27.433,-679.637],[-39.31,-769.31]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":166,"s":[{"i":[[-0.108,-0.527],[0.103,0.476],[0.156,0.054]],"o":[[0.07,0.342],[-0.036,-0.168],[-0.406,-0.139]],"v":[[-106.568,-820.63],[-105.325,-820.921],[-105.641,-821.251]],"c":true}]},{"t":167,"s":[{"i":[[-0.108,-0.527],[0.103,0.476],[0.156,0.053]],"o":[[0.07,0.343],[-0.036,-0.168],[-0.406,-0.139]],"v":[[-114.568,-1152.63],[-113.325,-1152.921],[-113.641,-1153.251]],"c":true}]}]},"nm":"Path 9","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-348,-1034.288],[-348,-1034.288],[-348,-1034.288],[-348,-1034.288],[-348,-1034.288]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-348,-1038.288],[-348,-1038.288],[-348,-1038.288],[-348,-1038.288],[-348,-1038.288]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-346,-458.288],[-346,-458.288],[-346,-458.288],[-346,-458.288],[-346,-458.288]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[-104.979,29.94],[-12.284,43.761],[46.367,-33.128],[25.915,-6.491],[3.721,-21.586]],"o":[[4.602,37.525],[19.334,-68.873],[-28.847,-16.12],[-38.123,9.548],[-4.599,26.676]],"v":[[-331.896,-483.607],[-181.51,-464.343],[-310.819,-546.127],[-395.586,-557.122],[-466.704,-499.439]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":67,"s":[{"i":[[-99.022,27.407],[-12.284,43.761],[46.431,-32.929],[24.604,-8.492],[3.643,-25.149]],"o":[[4.602,37.525],[19.313,-68.8],[-27.414,-15.046],[-36.194,12.492],[-4.502,31.08]],"v":[[-331.327,-464.449],[-180.941,-471.185],[-310.101,-553.07],[-390.602,-559.322],[-458.235,-491.466]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78,"s":[{"i":[[-101.193,45.253],[-12.284,43.761],[46.55,-32.564],[61.721,-77.489],[-172.438,-65.414]],"o":[[4.602,37.525],[19.276,-68.666],[84.783,-72.489],[-80.858,-73.373],[-96.438,82.586]],"v":[[-330.285,-479.91],[-179.898,-483.727],[-308.783,-565.799],[-391.721,-670.799],[-497.562,-596.874]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":138,"s":[{"i":[[-30.277,3.035],[-12.284,43.761],[47.217,-30.505],[19.991,-24.82],[-47.826,-24.387]],"o":[[4.602,37.526],[19.064,-67.912],[21.489,-19.029],[-26.554,-17.286],[-29.773,30.639]],"v":[[-323.461,-577.504],[-173.074,-564.867],[-300.409,-647.987],[-331.944,-671.615],[-369.983,-636.347]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":167,"s":[{"i":[[-0.038,-0.307],[-0.27,0.962],[1.044,-0.652],[0.07,-0.079],[0.052,-0.173]],"o":[[0.101,0.825],[0.417,-1.486],[-0.088,0.055],[-0.103,0.117],[-0.064,0.214]],"v":[[-251.554,-688.673],[-248.249,-688.249],[-251.034,-690.086],[-251.272,-689.885],[-251.508,-689.452]],"c":true}]},{"t":168,"s":[{"i":[[-0.038,-0.307],[-0.27,0.962],[1.044,-0.652],[0.07,-0.079],[0.052,-0.173]],"o":[[0.101,0.825],[0.417,-1.486],[-0.088,0.055],[-0.103,0.117],[-0.064,0.214]],"v":[[-259.554,-1020.673],[-256.249,-1020.249],[-259.034,-1022.086],[-259.272,-1021.885],[-259.508,-1021.452]],"c":true}]}]},"nm":"Path 10","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-0.86,-2.468],[0.177,1.253]],"o":[[0.171,0.491],[-0.306,-2.172]],"v":[[-404.14,-909.549],[-401.505,-910.201]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[-0.86,-2.468],[0.177,1.253]],"o":[[0.171,0.491],[-0.306,-2.172]],"v":[[-404.14,-913.549],[-401.505,-914.201]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[-0.86,-2.468],[0.177,1.253]],"o":[[0.171,0.491],[-0.306,-2.172]],"v":[[-390.14,-545.549],[-387.505,-546.201]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[-35.184,-117.811],[5.642,59.227]],"o":[[7.003,23.447],[-9.781,-102.689]],"v":[[-444.926,-584.477],[-319.751,-609.515]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[-35.184,-117.811],[5.642,59.227]],"o":[[7.003,23.447],[-9.781,-102.689]],"v":[[-444.926,-584.477],[-319.751,-609.515]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":166,"s":[{"i":[[-1.636,-5.48],[0.262,2.755]],"o":[[0.326,1.091],[-0.455,-4.776]],"v":[[-397.322,-690.878],[-391.5,-692.042]],"c":true}]},{"t":167,"s":[{"i":[[-1.636,-5.48],[0.262,2.755]],"o":[[0.326,1.091],[-0.455,-4.776]],"v":[[-405.322,-1022.878],[-399.5,-1024.042]],"c":true}]}]},"nm":"Path 11","hd":false},{"ind":7,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0.444,-2.846],[-0.06,2.162],[0.67,0.546]],"o":[[-0.439,2.81],[0.029,-1.027],[-1.558,-1.269]],"v":[[-486.367,-900.217],[-480.556,-899.65],[-481.655,-902.052]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[0.444,-2.846],[-0.06,2.162],[0.67,0.546]],"o":[[-0.439,2.81],[0.029,-1.027],[-1.558,-1.269]],"v":[[-486.367,-904.217],[-480.556,-903.65],[-481.655,-906.052]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62,"s":[{"i":[[0.444,-2.846],[-0.06,2.162],[0.67,0.546]],"o":[[-0.439,2.81],[0.029,-1.027],[-1.558,-1.269]],"v":[[-485.852,-462.217],[-480.041,-461.65],[-481.14,-464.052]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72,"s":[{"i":[[8.215,-52.651],[-1.119,40.004],[12.402,10.102]],"o":[[-8.113,51.994],[0.532,-19.001],[-28.823,-23.478]],"v":[[-535.698,-505.876],[-428.191,-495.387],[-448.517,-539.824]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":140,"s":[{"i":[[8.215,-52.651],[-1.119,40.004],[12.402,10.102]],"o":[[-8.113,51.994],[0.532,-19.001],[-28.823,-23.478]],"v":[[-535.187,-581.621],[-427.681,-571.131],[-448.006,-615.568]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":169,"s":[{"i":[[0.25,-1.604],[-0.034,1.218],[0.378,0.308]],"o":[[-0.247,1.584],[0.016,-0.579],[-0.878,-0.715]],"v":[[-487.492,-654.871],[-484.218,-654.552],[-484.837,-655.905]],"c":true}]},{"t":170,"s":[{"i":[[0.25,-1.604],[-0.034,1.218],[0.378,0.308]],"o":[[-0.247,1.584],[0.016,-0.579],[-0.878,-0.715]],"v":[[-495.492,-986.871],[-492.218,-986.552],[-492.837,-987.905]],"c":true}]}]},"nm":"Path 12","hd":false},{"ind":8,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-0.439,-2.059],[0.427,1.684]],"o":[[0.386,1.811],[-0.592,-2.334]],"v":[[-553.821,-901.181],[-549.938,-901.954]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[-0.439,-2.059],[0.427,1.684]],"o":[[0.386,1.811],[-0.592,-2.334]],"v":[[-553.821,-905.181],[-549.938,-905.954]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[-0.439,-2.059],[0.427,1.684]],"o":[[0.386,1.811],[-0.592,-2.334]],"v":[[-539.821,-537.181],[-535.938,-537.954]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[-10.898,-30.763],[9.958,25.023]],"o":[[9.589,27.066],[-13.803,-34.684]],"v":[[-561.449,-554.104],[-503.318,-573.81]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":139,"s":[{"i":[[-10.898,-30.763],[9.958,25.023]],"o":[[9.589,27.066],[-13.803,-34.684]],"v":[[-561.449,-554.104],[-503.318,-573.81]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":168,"s":[{"i":[[-1.05,-2.965],[0.96,2.412]],"o":[[0.924,2.609],[-1.33,-3.343]],"v":[[-542.164,-610.896],[-536.561,-612.796]],"c":true}]},{"t":169,"s":[{"i":[[-1.05,-2.965],[0.96,2.412]],"o":[[0.924,2.609],[-1.33,-3.343]],"v":[[-550.164,-942.896],[-544.561,-944.796]],"c":true}]}]},"nm":"Path 13","hd":false},{"ind":9,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0.436,1.704],[0.799,-0.276]],"o":[[-0.306,-1.196],[-0.603,0.208]],"v":[[-610.147,-951.692],[-612.448,-953.012]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[0.436,1.704],[0.799,-0.276]],"o":[[-0.306,-1.196],[-0.603,0.208]],"v":[[-610.147,-955.692],[-612.448,-957.012]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62,"s":[{"i":[[0.436,1.704],[0.799,-0.276]],"o":[[-0.306,-1.196],[-0.603,0.208]],"v":[[-609.632,-513.692],[-611.933,-515.012]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72,"s":[{"i":[[-3.808,21.156],[6.625,-7.932]],"o":[[3.337,-18.538],[-20.54,24.592]],"v":[[-588.337,-533.75],[-621.46,-549.88]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":136,"s":[{"i":[[-3.841,29.71],[9.825,-18.847]],"o":[[4.052,-31.888],[-22.576,42.035]],"v":[[-562.684,-558.118],[-635.461,-592.108]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":165,"s":[{"i":[[-0.049,0.416],[0.139,-0.29]],"o":[[0.055,-0.466],[-0.295,0.614]],"v":[[-601.382,-612.704],[-602.49,-613.218]],"c":true}]},{"t":166,"s":[{"i":[[-0.049,0.416],[0.139,-0.29]],"o":[[0.055,-0.466],[-0.295,0.614]],"v":[[-609.382,-944.704],[-610.49,-945.218]],"c":true}]}]},"nm":"Path 16","hd":false},{"ind":10,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0.164,-3.557],[-0.651,2.476]],"o":[[-0.175,3.776],[0.806,-3.065]],"v":[[-288.351,-1092.731],[-283.559,-1091.922]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[0.164,-3.557],[-0.651,2.476]],"o":[[-0.175,3.776],[0.806,-3.065]],"v":[[-288.351,-1096.731],[-283.559,-1095.922]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[{"i":[[0.164,-3.557],[-0.651,2.476]],"o":[[-0.175,3.776],[0.806,-3.065]],"v":[[-286.351,-516.731],[-281.559,-515.922]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[3.542,-76.579],[-14.024,53.309]],"o":[[-3.76,81.294],[17.363,-65.997]],"v":[[-336.336,-570.327],[-233.157,-552.908]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":133,"s":[{"i":[[3.542,-76.579],[-14.025,53.309]],"o":[[-3.76,81.294],[17.363,-65.997]],"v":[[-350.151,-655.09],[-246.971,-637.671]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":162,"s":[{"i":[[0.056,-1.207],[-0.221,0.84]],"o":[[-0.059,1.281],[0.274,-1.04]],"v":[[-311.688,-727.569],[-310.062,-727.295]],"c":true}]},{"t":163,"s":[{"i":[[0.056,-1.207],[-0.221,0.84]],"o":[[-0.059,1.281],[0.274,-1.04]],"v":[[-319.688,-1059.569],[-318.062,-1059.295]],"c":true}]}]},"nm":"Path 14","hd":false},{"ind":11,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":39,"s":[{"i":[[-0.099,-1.547],[0.122,0.483]],"o":[[-0.233,0.571],[0.37,-0.986]],"v":[[7.57,-924.741],[9.109,-924.508]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":69,"s":[{"i":[[-0.099,-1.547],[0.122,0.483]],"o":[[-0.233,0.571],[0.37,-0.986]],"v":[[7.57,-928.741],[9.109,-928.508]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[-0.099,-1.547],[0.122,0.483]],"o":[[-0.233,0.571],[0.37,-0.986]],"v":[[6.151,-444.741],[7.689,-444.508]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[-13.333,-137.796],[16.967,45.148]],"o":[[-27.679,51.531],[39.391,-87.843]],"v":[[-68.009,-495.852],[67.779,-475.313]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":132,"s":[{"i":[[-1.34,-134.598],[0.67,38.646]],"o":[[-9.022,48.674],[21.687,-85.725]],"v":[[-68.58,-590.547],[67.208,-570.009]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":161,"s":[{"i":[[0.113,-4.165],[-0.19,1.123]],"o":[[-0.04,1.484],[0.449,-2.651]],"v":[[-3.119,-699.053],[1.124,-698.411]],"c":true}]},{"t":162,"s":[{"i":[[0.113,-4.165],[-0.19,1.124]],"o":[[-0.04,1.484],[0.449,-2.651]],"v":[[-11.119,-1031.053],[-6.876,-1030.411]],"c":true}]}]},"nm":"Path 15","hd":false},{"ind":12,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":38,"s":[{"i":[[-0.098,-0.633],[-0.857,0.923],[1.79,-0.946]],"o":[[0.077,0.496],[1.653,0.298],[-0.823,-0.12]],"v":[[470.629,-994.934],[473.075,-994.626],[472.283,-995.63]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":39,"s":[{"i":[[-0.098,-0.633],[-0.857,0.923],[1.79,-0.946]],"o":[[0.077,0.496],[1.653,0.298],[-0.823,-0.12]],"v":[[471.857,-474.934],[474.304,-474.626],[473.511,-475.63]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":49,"s":[{"i":[[-9.771,-62.915],[-79.097,87.639],[268.714,-227.214]],"o":[[7.647,49.242],[207.317,68.611],[-69.286,-105.214]],"v":[[284.424,-562.912],[518.683,-533.898],[450.286,-610.073]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":138,"s":[{"i":[[-9.96,-64.133],[-27.776,53.533],[87.401,-42.782]],"o":[[7.647,49.242],[56.143,-17.885],[-50.137,-33.455]],"v":[[285.06,-645.746],[446.622,-629.561],[390.89,-710.929]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":167,"s":[{"i":[[-0.076,-0.492],[-0.1,0.334],[0.27,0.078]],"o":[[0.058,0.376],[0.097,-0.326],[-0.341,-0.098]],"v":[[351.49,-743.717],[352.564,-743.622],[352.167,-744.254]],"c":true}]},{"t":168,"s":[{"i":[[-0.076,-0.492],[-0.1,0.334],[0.27,0.078]],"o":[[0.058,0.376],[0.097,-0.326],[-0.341,-0.098]],"v":[[343.49,-1075.717],[344.564,-1075.622],[344.167,-1076.254]],"c":true}]}]},"nm":"Path 3","hd":false},{"ind":13,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-1.266,-4.784],[-0.496,1.775]],"o":[[0.615,2.322],[1.11,-3.968]],"v":[[456.064,-919.834],[460.091,-919.764]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":125,"s":[{"i":[[-1.266,-4.784],[-0.496,1.775]],"o":[[0.615,2.322],[1.11,-3.968]],"v":[[456.064,-923.834],[460.091,-923.764]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-1.015,-3.834],[-0.398,1.422]],"o":[[0.493,1.861],[0.89,-3.18]],"v":[[457.428,-614.453],[460.656,-614.398]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[-23.882,-90.222],[-9.362,33.471]],"o":[[11.59,43.787],[20.932,-74.836]],"v":[[422.077,-648.318],[498.03,-647.002]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":169,"s":[{"i":[[-0.455,-1.719],[-0.178,0.638]],"o":[[0.221,0.834],[0.399,-1.425]],"v":[[455.358,-734.122],[456.805,-734.097]],"c":true}]},{"t":170,"s":[{"i":[[-0.455,-1.719],[-0.178,0.638]],"o":[[0.221,0.834],[0.399,-1.425]],"v":[[447.358,-1066.122],[448.805,-1066.097]],"c":true}]}]},"nm":"Path 4","hd":false},{"ind":14,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[0.929,-6.009],[-0.89,3.766]],"o":[[-0.943,6.104],[1.317,-5.57]],"v":[[523.547,-912.234],[530.691,-909.895]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":125,"s":[{"i":[[0.929,-6.009],[-0.89,3.766]],"o":[[-0.943,6.104],[1.317,-5.57]],"v":[[523.547,-916.234],[530.691,-913.895]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0.863,-5.584],[-0.827,3.5]],"o":[[-0.877,5.673],[1.224,-5.176]],"v":[[524.877,-606.867],[531.515,-604.694]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[6.839,-44.252],[-6.556,27.732]],"o":[[-6.948,44.953],[9.696,-41.016]],"v":[[504.615,-646.984],[557.224,-629.763]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":168,"s":[{"i":[[0.101,-0.651],[-0.096,0.408]],"o":[[-0.102,0.661],[0.143,-0.603]],"v":[[533.63,-697.949],[534.404,-697.695]],"c":true}]},{"t":169,"s":[{"i":[[0.101,-0.651],[-0.096,0.408]],"o":[[-0.102,0.661],[0.143,-0.603]],"v":[[525.63,-1029.949],[526.404,-1029.696]],"c":true}]}]},"nm":"Path 5","hd":false},{"ind":15,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[{"i":[[-0.994,-4.026],[0.15,3.139]],"o":[[0.58,2.348],[-0.173,-3.625]],"v":[[597.52,-970.395],[602.541,-971.428]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":125,"s":[{"i":[[-0.994,-4.026],[0.15,3.139]],"o":[[0.58,2.348],[-0.173,-3.625]],"v":[[597.52,-974.395],[602.541,-975.428]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-0.914,-3.7],[0.137,2.885]],"o":[[0.533,2.158],[-0.159,-3.331]],"v":[[598.914,-665.492],[603.528,-666.441]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[{"i":[[-8.229,-33.327],[1.238,25.984]],"o":[[4.799,19.435],[-1.429,-30.007]],"v":[[586.078,-691.225],[627.639,-699.773]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":166,"s":[{"i":[[-0.343,-1.389],[0.052,1.083]],"o":[[0.2,0.81],[-0.06,-1.25]],"v":[[611.212,-749.097],[612.944,-749.454]],"c":true}]},{"t":167,"s":[{"i":[[-0.343,-1.389],[0.052,1.083]],"o":[[0.2,0.81],[-0.06,-1.25]],"v":[[603.212,-1081.097],[604.944,-1081.454]],"c":true}]}]},"nm":"Path 17","hd":false},{"ty":"fl","c":{"a":0,"k":[0.945098099054,0.90588241278,0.792156922583,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.917647058824,0.847058823529,0.721568627451,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":16},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":39,"op":170,"st":-6,"bm":0}]},{"id":"comp_32","layers":[{"ddd":0,"ind":1,"ty":0,"nm":"ground","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":49,"s":[0]},{"t":72,"s":[100]}]},"p":{"a":0,"k":[1140.139,1466.602,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":52,"s":[56,20.16,100]},{"t":181,"s":[100,36,100]}]}},"ao":0,"w":512,"h":512,"ip":49,"op":208,"st":49,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"ground","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":27,"s":[0]},{"t":50,"s":[100]}]},"p":{"a":0,"k":[639.139,473.658,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":31,"s":[59,21.24,100]},{"t":176,"s":[196,70.56,100]}]}},"ao":0,"w":512,"h":512,"ip":27,"op":186,"st":27,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"ground","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":68,"s":[100]}]},"p":{"a":0,"k":[891.139,353.658,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":49,"s":[72,25.92,100]},{"t":180,"s":[148,53.28,100]}]}},"ao":0,"w":512,"h":512,"ip":45,"op":180,"st":45,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"ground 16","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":67,"s":[0]},{"t":90,"s":[100]}]},"p":{"a":0,"k":[739.139,1293.658,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":73,"s":[80,28.8,100]},{"t":178,"s":[166,59.76,100]}]}},"ao":0,"w":512,"h":512,"ip":67,"op":246,"st":66,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"ground","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":28,"s":[0]},{"t":51,"s":[100]}]},"p":{"a":0,"k":[273.139,653.658,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":33,"s":[67,24.12,100]},{"t":183,"s":[143,51.48,100]}]}},"ao":0,"w":512,"h":512,"ip":28,"op":187,"st":28,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"ground 15","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"t":43,"s":[100]}]},"p":{"a":0,"k":[365.139,1405.658,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":27,"s":[108,38.88,100]},{"t":184,"s":[175,63,100]}]}},"ao":0,"w":512,"h":512,"ip":20,"op":199,"st":19,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"ground","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"t":43,"s":[100]}]},"p":{"a":0,"k":[1125.139,535.658,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":29,"s":[76,27.36,100]},{"t":181,"s":[166,59.76,100]}]}},"ao":0,"w":512,"h":512,"ip":20,"op":180,"st":19,"bm":0},{"ddd":0,"ind":8,"ty":0,"nm":"ground","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[0]},{"t":57,"s":[100]}]},"p":{"a":0,"k":[231.139,1039.658,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":42,"s":[41,14.76,100]},{"t":179,"s":[130,46.8,100]}]}},"ao":0,"w":512,"h":512,"ip":34,"op":214,"st":34,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"ground","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[0]},{"t":48,"s":[100]}]},"p":{"a":0,"k":[777.139,1125.658,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":32,"s":[69.299,24.948,100]},{"t":176,"s":[279,100.44,100]}]}},"ao":0,"w":512,"h":512,"ip":24,"op":180,"st":24,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"ground","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":59,"s":[0]},{"t":82,"s":[100]}]},"p":{"a":0,"k":[573.139,803.658,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":66,"s":[115,41.4,100]},{"t":181,"s":[264,95.04,100]}]}},"ao":0,"w":512,"h":512,"ip":59,"op":180,"st":59,"bm":0},{"ddd":0,"ind":11,"ty":0,"nm":"ground","refId":"comp_33","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"t":43,"s":[100]}]},"p":{"a":0,"k":[565.139,909.658,0]},"a":{"a":0,"k":[256,256,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[50,18,100]},{"t":184,"s":[161,57.96,100]}]}},"ao":0,"w":512,"h":512,"ip":19,"op":185,"st":19,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 30","parent":20,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[100]},{"t":151,"s":[0]}]},"p":{"a":0,"k":[-65.113,-69.948,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":0,"k":[82,82,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,1,0,0.5,1,1,0,1,1,1,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[320,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 22","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Shape Layer 33","parent":21,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[100]},{"t":162,"s":[0]}]},"p":{"a":0,"k":[-65.113,-69.948,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":0,"k":[82,82,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,1,0,0.5,1,1,0,1,1,1,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[320,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 22","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Shape Layer 39","parent":22,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":78,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":143,"s":[100]},{"t":170,"s":[0]}]},"p":{"a":0,"k":[-65.113,-69.948,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":0,"k":[82,82,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,1,0,0.5,1,1,0,1,1,1,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[320,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 22","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Shape Layer 36","parent":23,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":85,"s":[100]},{"t":156,"s":[0]}]},"p":{"a":0,"k":[-65.113,-69.948,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":0,"k":[82,82,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,1,0,0.5,1,1,0,1,1,1,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[320,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 22","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"Shape Layer 29","parent":20,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[100]},{"t":151,"s":[0]}]},"p":{"a":0,"k":[-109.11,-8.841,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":0,"k":[88,88,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0,0,0.5,1,0,0,1,1,0,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[397,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 23","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"Shape Layer 32","parent":21,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[100]},{"t":162,"s":[0]}]},"p":{"a":0,"k":[-109.11,-8.841,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":0,"k":[88,88,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0,0,0.5,1,0,0,1,1,0,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[397,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 23","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"Shape Layer 38","parent":22,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":78,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":143,"s":[100]},{"t":170,"s":[0]}]},"p":{"a":0,"k":[-109.11,-8.841,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":0,"k":[88,88,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0,0,0.5,1,0,0,1,1,0,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[397,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 23","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"Shape Layer 35","parent":23,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":85,"s":[100]},{"t":156,"s":[0]}]},"p":{"a":0,"k":[-109.11,-8.841,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":0,"k":[88,88,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0,0,0.5,1,0,0,1,1,0,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[397,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 23","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Shape Layer 25","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[100]},{"t":151,"s":[0]}]},"p":{"a":0,"k":[776,1244,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":43,"s":[146,52.56,100]},{"t":179,"s":[178,64.08,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":40},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.161,0,0,0.5,0.161,0,0,1,0.161,0,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[397,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 24","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"Shape Layer 31","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[100]},{"t":162,"s":[0]}]},"p":{"a":0,"k":[564,1004,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[106,38.16,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":43,"s":[106,38.16,100]},{"t":179,"s":[136,48.96,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":40},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.161,0,0,0.5,0.161,0,0,1,0.161,0,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[397,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 24","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"Shape Layer 37","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":78,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":143,"s":[100]},{"t":170,"s":[0]}]},"p":{"a":0,"k":[636,816,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[105,37.8,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":43,"s":[105,37.8,100]},{"t":179,"s":[126,45.36,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":40},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.161,0,0,0.5,0.161,0,0,1,0.161,0,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[397,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 24","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":"Shape Layer 34","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":85,"s":[100]},{"t":156,"s":[0]}]},"p":{"a":0,"k":[580,504,0]},"a":{"a":0,"k":[-92,-36,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[70,25.2,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":43,"s":[70,25.2,100]},{"t":179,"s":[149,53.64,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[840,840]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":40},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.161,0,0,0.5,0.161,0,0,1,0.161,0,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[397,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 24","hd":false},{"ty":"tr","p":{"a":0,"k":[-92,-36]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Ellipse 1","bm":0,"hd":false}],"ip":20,"op":180,"st":0,"bm":0}]},{"id":"comp_33","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 10","sr":1,"ks":{"r":{"a":0,"k":-198},"p":{"a":0,"k":[268.825,243.194,0]},"a":{"a":0,"k":[10,10,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[59,59,100]},{"t":175,"s":[107,107,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-104.934],[104.934,0],[0,104.934],[-104.934,0]],"o":[[0,104.934],[-104.934,0],[0,-104.934],[104.934,0]],"v":[[190,0],[0,190],[-190,0],[0,-190]],"c":true}},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[0,1,1,0,0.5,1,1,0,1,1,1,0,0.007,0,0.056,0,0.095,0,0.462,0.5,0.83,1,0.915,0.5,1,0]},{"t":150,"s":[0,0.937,0.22,0,0.5,0.937,0.22,0,1,0.937,0.221,0,0,0,0.336,0,0.673,0,0.817,0.5,0.935,1,0.968,0.5,1,0]}]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[185,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 31","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[77,77]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 8","sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-1]},{"t":179,"s":[-132]}]},"p":{"a":0,"k":[250,261,0]},"a":{"a":0,"k":[-10,-10,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[75,75,100]},{"t":179,"s":[104,104,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-104.934],[104.934,0],[0,104.934],[-104.934,0]],"o":[[0,104.934],[-104.934,0],[0,-104.934],[104.934,0]],"v":[[190,0],[0,190],[-190,0],[0,-190]],"c":true}},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0,0,0.5,1,0,0,1,1,0,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[185,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 32","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Layer 7","sr":1,"ks":{"o":{"a":0,"k":40},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[1080]}]},"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[10,10,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":10,"s":[95,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":20,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":30,"s":[95,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":40,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":50,"s":[95,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":60,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":70,"s":[95,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":80,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":90,"s":[95,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":100,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":110,"s":[95,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":120,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":130,"s":[95,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":140,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":150,"s":[95,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":160,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":170,"s":[95,95,100]},{"t":180,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-104.934],[104.934,0],[0,104.934],[-104.934,0]],"o":[[0,104.934],[-104.934,0],[0,-104.934],[104.934,0]],"v":[[190,0],[0,190],[-190,0],[0,-190]],"c":true}},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.161,0,0,0.5,0.161,0,0,1,0.161,0,0,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[0,0]},"e":{"a":0,"k":[185,0]},"t":2,"h":{"a":0,"k":0},"a":{"a":0,"k":0},"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"fire1536","refId":"comp_0","sr":1,"ks":{"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[768,768,0]},"s":{"a":0,"k":[33,33,100]}},"ao":0,"w":1536,"h":1536,"ip":0,"op":180,"st":0,"bm":0}]} \ No newline at end of file diff --git a/Tests/LottieMesh/Sources/ViewController.swift b/Tests/LottieMesh/Sources/ViewController.swift deleted file mode 100644 index 83251bb6463..00000000000 --- a/Tests/LottieMesh/Sources/ViewController.swift +++ /dev/null @@ -1,37 +0,0 @@ -import Foundation -import UIKit - -import LottieMeshSwift -import Postbox - -public final class ViewController: UIViewController { - override public func viewDidLoad() { - super.viewDidLoad() - - TempBox.initializeShared(basePath: NSTemporaryDirectory(), processType: "test", launchSpecificId: Int64.random(in: Int64.min ..< Int64.max)) - - self.view.backgroundColor = .black - - //let path = Bundle.main.path(forResource: "SUPER Fire", ofType: "json")! - let path = Bundle.main.path(forResource: "Fireworks", ofType: "json")! - //let path = Bundle.main.path(forResource: "Cat", ofType: "json")! - /*for _ in 0 ..< 100 { - let _ = generateMeshAnimation(data: try! Data(contentsOf: URL(fileURLWithPath: path)))! - }*/ - - if #available(iOS 13.0, *) { - let startTime = CFAbsoluteTimeGetCurrent() - let animationFile = generateMeshAnimation(data: try! Data(contentsOf: URL(fileURLWithPath: path)))! - print("Time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0)") - let buffer = MeshReadBuffer(data: try! Data(contentsOf: URL(fileURLWithPath: animationFile.path))) - let animation = MeshAnimation.read(buffer: buffer) - - let renderer = MeshRenderer(wireframe: false)! - - renderer.frame = CGRect(origin: CGPoint(x: 0.0, y: 50.0), size: CGSize(width: 300.0, height: 300.0)) - self.view.addSubview(renderer) - - renderer.add(mesh: animation, offset: CGPoint(), loop: true) - } - } -} diff --git a/Tests/LottieMetalMacTest/.gitignore b/Tests/LottieMetalMacTest/.gitignore new file mode 100644 index 00000000000..31360466c1f --- /dev/null +++ b/Tests/LottieMetalMacTest/.gitignore @@ -0,0 +1,2 @@ +TestData/*.json + diff --git a/Tests/LottieMetalMacTest/BUILD b/Tests/LottieMetalMacTest/BUILD new file mode 100644 index 00000000000..110af1aa5a2 --- /dev/null +++ b/Tests/LottieMetalMacTest/BUILD @@ -0,0 +1,173 @@ +load("@build_bazel_rules_apple//apple:macos.bzl", + "macos_application", +) + +load("@build_bazel_rules_swift//swift:swift.bzl", + "swift_library", +) + +load("//build-system/bazel-utils:plist_fragment.bzl", + "plist_fragment", +) + +load( + "@build_bazel_rules_apple//apple:resources.bzl", + "apple_resource_bundle", + "apple_resource_group", +) + +load( + "@rules_xcodeproj//xcodeproj:defs.bzl", + "top_level_target", + "top_level_targets", + "xcodeproj", + "xcode_provisioning_profile", +) + +load("@build_bazel_rules_apple//apple:apple.bzl", "local_provisioning_profile") + +load( + "@build_configuration//:variables.bzl", + "telegram_bazel_path", +) + +filegroup( + name = "AppResources", + srcs = glob([ + "Resources/**/*", + ], exclude = ["Resources/**/.*"]), +) + +plist_fragment( + name = "BuildNumberInfoPlist", + extension = "plist", + template = + """ + CFBundleVersion + 1 + """ +) + +plist_fragment( + name = "VersionInfoPlist", + extension = "plist", + template = + """ + CFBundleShortVersionString + 1.0 + """ +) + +plist_fragment( + name = "AppNameInfoPlist", + extension = "plist", + template = + """ + CFBundleDisplayName + Test + """ +) + +plist_fragment( + name = "MacAppInfoPlist", + extension = "plist", + template = + """ + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Telegram + CFBundlePackageType + APPL + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + """ +) + +filegroup( + name = "TestDataBundleFiles", + srcs = glob([ + "TestData/*.json", + ]), + visibility = ["//visibility:public"], +) + +plist_fragment( + name = "TestDataBundleInfoPlist", + extension = "plist", + template = + """ + CFBundleIdentifier + org.telegram.TestDataBundle + CFBundleDevelopmentRegion + en + CFBundleName + TestDataBundle + """ +) + +apple_resource_bundle( + name = "TestDataBundle", + infoplists = [ + ":TestDataBundleInfoPlist", + ], + resources = [ + ":TestDataBundleFiles", + ], +) + +swift_library( + name = "MacLib", + srcs = glob([ + "MacSources/**/*.swift", + ]), + data = [ + "Resources/Main.storyboard", + ], +) + +macos_application( + name = "LottieMetalMacTest", + app_icons = [], + bundle_id = "com.example.hello-world-swift", + infoplists = [ + ":MacAppInfoPlist", + ":BuildNumberInfoPlist", + ":VersionInfoPlist", + ], + minimum_os_version = "10.13", + deps = [ + ":MacLib" + ], + visibility = ["//visibility:public"], +) + +xcodeproj( + name = "LottieMetalMacTest_xcodeproj", + build_mode = "bazel", + bazel_path = telegram_bazel_path, + project_name = "LottieMetalMacTest", + tags = ["manual"], + top_level_targets = top_level_targets( + labels = [ + ":LottieMetalMacTest", + ], + ), + xcode_configurations = { + "Debug": { + "//command_line_option:compilation_mode": "dbg", + }, + "Release": { + "//command_line_option:compilation_mode": "opt", + }, + }, + default_xcode_configuration = "Debug" +) diff --git a/Tests/LottieMetalMacTest/MacSources/AppDelegate.swift b/Tests/LottieMetalMacTest/MacSources/AppDelegate.swift new file mode 100644 index 00000000000..377719dffc2 --- /dev/null +++ b/Tests/LottieMetalMacTest/MacSources/AppDelegate.swift @@ -0,0 +1,19 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Cocoa + +@NSApplicationMain +@objc(AppDelegate) +class AppDelegate: NSObject, NSApplicationDelegate {} diff --git a/Tests/LottieMetalMacTest/MacSources/ViewController.swift b/Tests/LottieMetalMacTest/MacSources/ViewController.swift new file mode 100644 index 00000000000..9811f38752a --- /dev/null +++ b/Tests/LottieMetalMacTest/MacSources/ViewController.swift @@ -0,0 +1,11 @@ +import Cocoa + +@objc(ViewController) +class ViewController: NSViewController { + override func viewDidLoad() { + super.viewDidLoad() + + self.view.layer?.backgroundColor = NSColor.blue.cgColor + } +} + diff --git a/Tests/LottieMetalMacTest/Resources/Main.storyboard b/Tests/LottieMetalMacTest/Resources/Main.storyboard new file mode 100644 index 00000000000..d9938c13966 --- /dev/null +++ b/Tests/LottieMetalMacTest/Resources/Main.storyboard @@ -0,0 +1,697 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/LottieMetalTest/.gitignore b/Tests/LottieMetalTest/.gitignore new file mode 100644 index 00000000000..31360466c1f --- /dev/null +++ b/Tests/LottieMetalTest/.gitignore @@ -0,0 +1,2 @@ +TestData/*.json + diff --git a/Tests/LottieMesh/BUILD b/Tests/LottieMetalTest/BUILD similarity index 58% rename from Tests/LottieMesh/BUILD rename to Tests/LottieMetalTest/BUILD index 40da9bebbd8..1e9987af9f3 100644 --- a/Tests/LottieMesh/BUILD +++ b/Tests/LottieMetalTest/BUILD @@ -10,6 +10,27 @@ load("//build-system/bazel-utils:plist_fragment.bzl", "plist_fragment", ) +load( + "@build_bazel_rules_apple//apple:resources.bzl", + "apple_resource_bundle", + "apple_resource_group", +) + +load( + "@rules_xcodeproj//xcodeproj:defs.bzl", + "top_level_target", + "top_level_targets", + "xcodeproj", + "xcode_provisioning_profile", +) + +load("@build_bazel_rules_apple//apple:apple.bzl", "local_provisioning_profile") + +load( + "@build_configuration//:variables.bzl", + "telegram_bazel_path", +) + filegroup( name = "AppResources", srcs = glob([ @@ -26,7 +47,14 @@ swift_library( ":AppResources", ], deps = [ - "//submodules/LottieMeshSwift:LottieMeshSwift", + "//submodules/Display", + "//submodules/MetalEngine", + "//submodules/TelegramUI/Components/LottieCpp", + "//submodules/TelegramUI/Components/LottieMetal", + "//submodules/rlottie:RLottieBinding", + "//Tests/LottieMetalTest/QOILoader", + "//Tests/LottieMetalTest/SoftwareLottieRenderer", + "//Tests/LottieMetalTest/LottieSwift", ], ) @@ -72,7 +100,7 @@ plist_fragment( CFBundleDisplayName Test CFBundleIdentifier - org.telegram.LottieMesh + ph.telegra.Telegraph CFBundleName Telegram CFBundlePackageType @@ -127,12 +155,44 @@ plist_fragment( """ ) +filegroup( + name = "TestDataBundleFiles", + srcs = glob([ + "TestData/*.json", + ]), + visibility = ["//visibility:public"], +) + +plist_fragment( + name = "TestDataBundleInfoPlist", + extension = "plist", + template = + """ + CFBundleIdentifier + org.telegram.TestDataBundle + CFBundleDevelopmentRegion + en + CFBundleName + TestDataBundle + """ +) + +apple_resource_bundle( + name = "TestDataBundle", + infoplists = [ + ":TestDataBundleInfoPlist", + ], + resources = [ + ":TestDataBundleFiles", + ], +) + ios_application( - name = "LottieMesh", - bundle_id = "org.telegram.LottieMesh", + name = "LottieMetalTest", + bundle_id = "ph.telegra.Telegraph", families = ["iphone", "ipad"], - minimum_os_version = "9.0", - provisioning_profile = "@build_configuration//provisioning:Wildcard.mobileprovision", + minimum_os_version = "12.0", + provisioning_profile = "@build_configuration//provisioning:Telegram.mobileprovision", infoplists = [ ":AppInfoPlist", ":BuildNumberInfoPlist", @@ -140,6 +200,7 @@ ios_application( ], resources = [ "//Tests/Common:LaunchScreen", + ":TestDataBundle", ], frameworks = [ ], @@ -147,4 +208,28 @@ ios_application( "//Tests/Common:Main", ":Lib", ], + visibility = ["//visibility:public"], +) + +xcodeproj( + name = "LottieMetalTest_xcodeproj", + build_mode = "bazel", + bazel_path = telegram_bazel_path, + project_name = "LottieMetalTest", + tags = ["manual"], + top_level_targets = top_level_targets( + labels = [ + ":LottieMetalTest", + ], + target_environments = ["device", "simulator"], + ), + xcode_configurations = { + "Debug": { + "//command_line_option:compilation_mode": "dbg", + }, + "Release": { + "//command_line_option:compilation_mode": "opt", + }, + }, + default_xcode_configuration = "Debug" ) diff --git a/Tests/LottieMetalTest/LottieSwift/BUILD b/Tests/LottieMetalTest/LottieSwift/BUILD new file mode 100644 index 00000000000..01dac117768 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/BUILD @@ -0,0 +1,14 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", + "swift_library", +) + +swift_library( + name = "LottieSwift", + module_name = "LottieSwift", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + ], + visibility = ["//visibility:public"], +) diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift new file mode 100644 index 00000000000..66e221ff221 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift @@ -0,0 +1,76 @@ +// Created by Cal Stephens on 1/6/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAAnimation { + /// Creates a `CAAnimation` that wraps this animation, + /// applying timing-related configuration from the given `LayerAnimationContext` + @nonobjc + func timed(with context: LayerAnimationContext, for layer: CALayer) -> CAAnimation { + + // The base animation always has the duration of the full animation, + // since that's the time space where keyframing and interpolating happens. + // So we start with a simple animation timeline from 0% to 100%: + // + // ┌──────────────────────────────────┐ + // │ baseAnimation │ + // └──────────────────────────────────┘ + // 0% 100% + // + let baseAnimation = self + baseAnimation.duration = context.animation.duration + baseAnimation.speed = (context.endFrame < context.startFrame) ? -1 : 1 + + // To select the subrange of the `baseAnimation` that should be played, + // we create a parent animation with the duration of that subrange + // to clip the `baseAnimation`. This parent animation can then loop + // and/or autoreverse over the clipped subrange. + // + // ┌────────────────────┬───────► + // │ clippingParent │ ... + // └────────────────────┴───────► + // 25% 75% + // ┌──────────────────────────────────┐ + // │ baseAnimation │ + // └──────────────────────────────────┘ + // 0% 100% + // + let clippingParent = CAAnimationGroup() + clippingParent.animations = [baseAnimation] + + clippingParent.duration = Double(abs(context.endFrame - context.startFrame)) / context.animation.framerate + baseAnimation.timeOffset = context.animation.time(forFrame: context.startFrame) + + clippingParent.autoreverses = context.timingConfiguration.autoreverses + clippingParent.repeatCount = context.timingConfiguration.repeatCount + clippingParent.timeOffset = context.timingConfiguration.timeOffset + + // Once the animation ends, it should pause on the final frame + clippingParent.fillMode = .both + clippingParent.isRemovedOnCompletion = false + + // We can pause the animation on a specific frame by setting the root layer's + // `speed` to 0, and then setting the `timeOffset` for the given frame. + // - For that setup to work properly, we have to set the `beginTime` + // of this animation to a time slightly before the current time. + // - It's not really clear why this is necessary, but `timeOffset` + // is not applied correctly without this configuration. + // - We can't do this when playing the animation in real time, + // because it can cause keyframe timings to be incorrect. + if context.timingConfiguration.speed == 0 { + let currentTime = layer.convertTime(CACurrentMediaTime(), from: nil) + clippingParent.beginTime = currentTime - .leastNonzeroMagnitude + } + + return clippingParent + } +} + +extension CALayer { + /// Adds the given animation to this layer, timed with the given timing configuration + @nonobjc + func add(_ animation: CAPropertyAnimation, timedWith context: LayerAnimationContext) { + add(animation.timed(with: context, for: self), forKey: animation.keyPath) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift new file mode 100644 index 00000000000..5a4d1b1214b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift @@ -0,0 +1,315 @@ +// Created by Cal Stephens on 12/14/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CALayer { + + // MARK: Internal + + /// Constructs a `CAKeyframeAnimation` that reflects the given keyframes, + /// and adds it to this `CALayer`. + @nonobjc + func addAnimation( + for property: LayerProperty, + keyframes: ContiguousArray>, + value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation, + context: LayerAnimationContext) + throws + { + if let customAnimation = try customizedAnimation(for: property, context: context) { + add(customAnimation, timedWith: context) + } + + else if + let defaultAnimation = try defaultAnimation( + for: property, + keyframes: keyframes, + value: keyframeValueMapping, + context: context) + { + add(defaultAnimation, timedWith: context) + } + } + + // MARK: Private + + /// Constructs a `CAAnimation` that reflects the given keyframes + /// - If the value can be applied directly to the CALayer using KVC, + /// then no `CAAnimation` will be created and the value will be applied directly. + @nonobjc + private func defaultAnimation( + for property: LayerProperty, + keyframes: ContiguousArray>, + value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation, + context: LayerAnimationContext) + throws + -> CAPropertyAnimation? + { + guard !keyframes.isEmpty else { return nil } + + // If there is exactly one keyframe value, we can improve performance + // by applying that value directly to the layer instead of creating + // a relatively expensive `CAKeyframeAnimation`. + if keyframes.count == 1 { + let keyframeValue = try keyframeValueMapping(keyframes[0].value) + + // If the keyframe value is the same as the layer's default value for this property, + // then we can just ignore this set of keyframes. + if keyframeValue == property.defaultValue { + return nil + } + + // If the property on the CALayer being animated hasn't been modified from the default yet, + // then we can apply the keyframe value directly to the layer using KVC instead + // of creating a `CAAnimation`. + if + let defaultValue = property.defaultValue, + defaultValue == value(forKey: property.caLayerKeypath) as? ValueRepresentation + { + setValue(keyframeValue, forKeyPath: property.caLayerKeypath) + return nil + } + + // Otherwise, we still need to create a `CAAnimation`, but we can + // create a simple `CABasicAnimation` that is still less expensive + // than computing a `CAKeyframeAnimation`. + let animation = CABasicAnimation(keyPath: property.caLayerKeypath) + animation.fromValue = keyframeValue + animation.toValue = keyframeValue + return animation + } + + return try keyframeAnimation( + for: property, + keyframes: keyframes, + value: keyframeValueMapping, + context: context) + } + + /// A `CAAnimation` that applies the custom value from the `AnyValueProvider` + /// registered for this specific property's `AnimationKeypath`, + /// if one has been registered using `AnimationView.setValueProvider(_:keypath:)`. + @nonobjc + private func customizedAnimation( + for property: LayerProperty, + context: LayerAnimationContext) + throws + -> CAPropertyAnimation? + { + guard + let customizableProperty = property.customizableProperty, + let customKeyframes = try context.valueProviderStore.customKeyframes( + of: customizableProperty, + for: AnimationKeypath(keys: context.currentKeypath.keys + customizableProperty.name.map { $0.rawValue }), + context: context) + else { return nil } + + // Since custom animations are overriding an existing animation, + // we always have to create a CAKeyframeAnimation for these instead of + // letting `defaultAnimation(...)` try to apply the value using KVC. + return try keyframeAnimation( + for: property, + keyframes: customKeyframes.keyframes, + value: { $0 }, + context: context) + } + + /// Creates a `CAKeyframeAnimation` for the given keyframes + private func keyframeAnimation( + for property: LayerProperty, + keyframes: ContiguousArray>, + value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation, + context: LayerAnimationContext) + throws + -> CAKeyframeAnimation + { + // Convert the list of `Keyframe` into + // the representation used by `CAKeyframeAnimation` + var keyTimes = keyframes.map { keyframeModel -> NSNumber in + NSNumber(value: Float(context.progressTime(for: keyframeModel.time))) + } + + var timingFunctions = self.timingFunctions(for: keyframes) + let calculationMode = try self.calculationMode(for: keyframes, context: context) + + let animation = CAKeyframeAnimation(keyPath: property.caLayerKeypath) + + // Position animations define a `CGPath` curve that should be followed, + // instead of animating directly between keyframe point values. + if property.caLayerKeypath == LayerProperty.position.caLayerKeypath { + animation.path = try path(keyframes: keyframes, value: { value in + guard let point = try keyframeValueMapping(value) as? CGPoint else { + LottieLogger.shared.assertionFailure("Cannot create point from keyframe with value \(value)") + return .zero + } + + return point + }) + } + + // All other types of keyframes provide individual values that are interpolated by Core Animation + else { + var values = try keyframes.map { keyframeModel in + try keyframeValueMapping(keyframeModel.value) + } + + validate(values: &values, keyTimes: &keyTimes, timingFunctions: &timingFunctions, for: calculationMode) + animation.values = values + } + + animation.calculationMode = calculationMode + animation.keyTimes = keyTimes + animation.timingFunctions = timingFunctions + return animation + } + + /// The `CAAnimationCalculationMode` that should be used for a `CAKeyframeAnimation` + /// animating the given keyframes + private func calculationMode( + for keyframes: ContiguousArray>, + context: LayerAnimationContext) + throws + -> CAAnimationCalculationMode + { + // Animations using `isHold` should use `CAAnimationCalculationMode.discrete` + // + // - Since we currently only create a single `CAKeyframeAnimation`, + // we can currently only correctly support animations where + // `isHold` is either always `true` or always `false` + // (this requirement doesn't apply to the first/last keyframes). + // + // - We should be able to support this in the future by creating multiple + // `CAKeyframeAnimation`s with different `calculationMode`s and + // playing them sequentially. + // + let intermediateKeyframes = keyframes.dropFirst().dropLast() + if intermediateKeyframes.contains(where: \.isHold) { + if intermediateKeyframes.allSatisfy(\.isHold) { + return .discrete + } else { + try context.logCompatibilityIssue("Mixed `isHold` / `!isHold` keyframes are currently unsupported") + } + } + + return .linear + } + + /// `timingFunctions` to apply to a `CAKeyframeAnimation` animating the given keyframes + private func timingFunctions( + for keyframes: ContiguousArray>) + -> [CAMediaTimingFunction] + { + // Compute the timing function between each keyframe and the subsequent keyframe + var timingFunctions: [CAMediaTimingFunction] = [] + + for (index, keyframe) in keyframes.enumerated() + where index != keyframes.indices.last + { + let nextKeyframe = keyframes[index + 1] + + let controlPoint1 = keyframe.outTangent?.pointValue ?? .zero + let controlPoint2 = nextKeyframe.inTangent?.pointValue ?? CGPoint(x: 1, y: 1) + + timingFunctions.append(CAMediaTimingFunction( + controlPoints: + Float(controlPoint1.x), + Float(controlPoint1.y), + Float(controlPoint2.x), + Float(controlPoint2.y))) + } + + return timingFunctions + } + + /// Creates a `CGPath` for the given `position` keyframes, + /// which accounts for `spatialInTangent`s and `spatialOutTangents` + private func path( + keyframes positionKeyframes: ContiguousArray>, + value keyframeValueMapping: (KeyframeValue) throws -> CGPoint) rethrows + -> CGPath { + let path = CGMutablePath() + + for (index, keyframe) in positionKeyframes.enumerated() { + if index == positionKeyframes.indices.first { + path.move(to: try keyframeValueMapping(keyframe.value)) + } + + if index != positionKeyframes.indices.last { + let nextKeyframe = positionKeyframes[index + 1] + + if + let controlPoint1 = keyframe.spatialOutTangent?.pointValue, + let controlPoint2 = nextKeyframe.spatialInTangent?.pointValue, + controlPoint1 != .zero, + controlPoint2 != .zero + { + path.addCurve( + to: try keyframeValueMapping(nextKeyframe.value), + control1: try keyframeValueMapping(keyframe.value) + controlPoint1, + control2: try keyframeValueMapping(nextKeyframe.value) + controlPoint2) + } + + else { + path.addLine(to: try keyframeValueMapping(nextKeyframe.value)) + } + } + } + + path.closeSubpath() + return path + } + + /// Validates that the requirements of the `CAKeyframeAnimation` API are met correctly + private func validate( + values: inout [ValueRepresentation], + keyTimes: inout [NSNumber], + timingFunctions: inout [CAMediaTimingFunction], + for calculationMode: CAAnimationCalculationMode) + { + // Validate that we have correct start (0.0) and end (1.0) keyframes. + // From the documentation of `CAKeyframeAnimation.keyTimes`: + // - The first value in the `keyTimes` array must be 0.0 and the last value must be 1.0. + if keyTimes.first != 0.0 { + keyTimes.insert(0.0, at: 0) + values.insert(values[0], at: 0) + timingFunctions.insert(CAMediaTimingFunction(name: .linear), at: 0) + } + + if keyTimes.last != 1.0 { + keyTimes.append(1.0) + values.append(values.last!) + timingFunctions.append(CAMediaTimingFunction(name: .linear)) + } + + switch calculationMode { + case .linear, .cubic: + // From the documentation of `CAKeyframeAnimation.keyTimes`: + // - The number of elements in the keyTimes array + // should match the number of elements in the values property + LottieLogger.shared.assert( + values.count == keyTimes.count, + "`values.count` must exactly equal `keyTimes.count`") + + LottieLogger.shared.assert( + timingFunctions.count == (values.count - 1), + "`timingFunctions.count` must exactly equal `values.count - 1`") + + case .discrete: + // From the documentation of `CAKeyframeAnimation.keyTimes`: + // - If the calculationMode is set to discrete... the keyTimes array + // should have one more entry than appears in the values array. + values.removeLast() + + LottieLogger.shared.assert( + keyTimes.count == values.count + 1, + "`keyTimes.count` must exactly equal `values.count + 1`") + + default: + LottieLogger.shared.assertionFailure(""" + Unexpected keyframe calculation mode \(calculationMode) + """) + } + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift new file mode 100644 index 00000000000..9aadf861635 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift @@ -0,0 +1,52 @@ +// Created by Cal Stephens on 1/28/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + /// Adds animations for the given `CombinedShapeItem` to this `CALayer` + @nonobjc + func addAnimations( + for combinedShapes: CombinedShapeItem, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .path, + keyframes: combinedShapes.shapes.keyframes, + value: { paths in + let combinedPath = CGMutablePath() + for path in paths { + combinedPath.addPath(path.cgPath()) + } + return combinedPath + }, + context: context) + } +} + +// MARK: - CombinedShapeItem + +/// A custom `ShapeItem` subclass that combines multiple `Shape`s into a single `KeyframeGroup` +final class CombinedShapeItem: ShapeItem { + + // MARK: Lifecycle + + init(shapes: KeyframeGroup<[BezierPath]>, name: String) { + self.shapes = shapes + super.init(name: name, type: .shape, hidden: false) + } + + required init(from _: Decoder) throws { + fatalError("init(from:) has not been implemented") + } + + required init(dictionary _: [String: Any]) throws { + fatalError("init(dictionary:) has not been implemented") + } + + // MARK: Internal + + let shapes: KeyframeGroup<[BezierPath]> + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift new file mode 100644 index 00000000000..28262e98a89 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift @@ -0,0 +1,22 @@ +// Created by Cal Stephens on 12/21/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + /// Adds animations for the given `BezierPath` keyframes to this `CALayer` + @nonobjc + func addAnimations( + for customPath: KeyframeGroup, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .path, + keyframes: customPath.keyframes, + value: { pathKeyframe in + pathKeyframe.cgPath() + }, + context: context) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift new file mode 100644 index 00000000000..383f3ae451f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift @@ -0,0 +1,26 @@ +// Created by Cal Stephens on 12/21/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + /// Adds animations for the given `Ellipse` to this `CALayer` + @nonobjc + func addAnimations( + for ellipse: Ellipse, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .path, + keyframes: ellipse.size.keyframes, + value: { sizeKeyframe in + BezierPath.ellipse( + size: sizeKeyframe.sizeValue, + center: try ellipse.position.exactlyOneKeyframe(context: context, description: "ellipse position").value.pointValue, + direction: ellipse.direction) + .cgPath() + }, + context: context) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift new file mode 100644 index 00000000000..d02bda6b8f8 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift @@ -0,0 +1,146 @@ +// Created by Cal Stephens on 1/7/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - GradientShapeItem + +/// A `ShapeItem` that represents a gradient +protocol GradientShapeItem: OpacityAnimationModel { + var startPoint: KeyframeGroup { get } + var endPoint: KeyframeGroup { get } + var gradientType: GradientType { get } + var numberOfColors: Int { get } + var colors: KeyframeGroup<[Double]> { get } +} + +// MARK: - GradientFill + GradientShapeItem + +extension GradientFill: GradientShapeItem { } + +// MARK: - GradientStroke + GradientShapeItem + +extension GradientStroke: GradientShapeItem { } + +// MARK: - GradientRenderLayer + GradientShapeItem + +extension GradientRenderLayer { + + // MARK: Internal + + /// Adds gradient-related animations to this layer, from the given `GradientFill` + func addGradientAnimations(for gradient: GradientShapeItem, context: LayerAnimationContext) throws { + // We have to set `colors` to a non-nil value with some valid number of colors + // for the color animation below to have any effect + colors = .init( + repeating: CGColor.rgb(0, 0, 0), + count: gradient.numberOfColors) + + try addAnimation( + for: .colors, + keyframes: gradient.colors.keyframes, + value: { colorComponents in + gradient.colorConfiguration(from: colorComponents).map { $0.color } + }, + context: context) + + try addAnimation( + for: .locations, + keyframes: gradient.colors.keyframes, + value: { colorComponents in + gradient.colorConfiguration(from: colorComponents).map { $0.location } + }, + context: context) + + try addOpacityAnimation(for: gradient, context: context) + + switch gradient.gradientType { + case .linear: + try addLinearGradientAnimations(for: gradient, context: context) + case .radial: + try addRadialGradientAnimations(for: gradient, context: context) + case .none: + break + } + } + + // MARK: Private + + private func addLinearGradientAnimations( + for gradient: GradientShapeItem, + context: LayerAnimationContext) + throws + { + type = .axial + + try addAnimation( + for: .startPoint, + keyframes: gradient.startPoint.keyframes, + value: { absoluteStartPoint in + percentBasedPointInBounds(from: absoluteStartPoint.pointValue) + }, + context: context) + + try addAnimation( + for: .endPoint, + keyframes: gradient.endPoint.keyframes, + value: { absoluteEndPoint in + percentBasedPointInBounds(from: absoluteEndPoint.pointValue) + }, + context: context) + } + + private func addRadialGradientAnimations(for gradient: GradientShapeItem, context: LayerAnimationContext) throws { + type = .radial + + // To draw the correct gradients, we have to derive a custom `endPoint` + // relative to the `startPoint` value. Since calculating the `endPoint` + // at any given time requires knowing the current `startPoint`, + // we can't allow them to animate separately. + let absoluteStartPoint = try gradient.startPoint + .exactlyOneKeyframe(context: context, description: "gradient startPoint").value.pointValue + + let absoluteEndPoint = try gradient.endPoint + .exactlyOneKeyframe(context: context, description: "gradient endPoint").value.pointValue + + startPoint = percentBasedPointInBounds(from: absoluteStartPoint) + + let radius = absoluteStartPoint.distanceTo(absoluteEndPoint) + endPoint = percentBasedPointInBounds( + from: CGPoint( + x: absoluteStartPoint.x + radius, + y: absoluteStartPoint.y + radius)) + } +} + +extension GradientShapeItem { + /// Converts the compact `[Double]` color components representation + /// into an array of `CGColor`s and the location of those colors within the gradient + fileprivate func colorConfiguration( + from colorComponents: [Double]) + -> [(color: CGColor, location: CGFloat)] + { + precondition( + colorComponents.count >= numberOfColors * 4, + "Each color must have RGB components and a location component") + + var cgColors = [(color: CGColor, location: CGFloat)]() + + // Each group of four `Double` values represents a single `CGColor`, + // and its relative location within the gradient. + for colorIndex in 0.. { + /// The `CALayer` KVC key path that this value should be assigned to + let caLayerKeypath: String + + /// The default value of this property on a `CALayer` + /// - If the keyframe values are just equal to the default value, + /// then we can improve performance a bit by just not creating + /// a CAAnimation (since it would be redundant). + let defaultValue: ValueRepresentation? + + /// A description of how this property can be customized dynamically + /// at runtime using `AnimationView.setValueProvider(_:keypath:)` + let customizableProperty: CustomizableProperty? +} + +// MARK: - CustomizableProperty + +/// A description of how a `CALayer` property can be customized dynamically +/// at runtime using `AnimationView.setValueProvider(_:keypath:)` +struct CustomizableProperty { + /// The name that `AnimationKeypath`s can use to refer to this property + /// - When building an animation for this property that will be applied + /// to a specific layer, this `name` is appended to the end of that + /// layer's `AnimationKeypath`. The combined keypath is used to query + /// the `ValueProviderStore`. + let name: [PropertyName] + + /// A closure that coverts the type-erased value of an `AnyValueProvider` + /// to the strongly-typed representation used by this property, if possible. + let conversion: (Any) -> ValueRepresentation? +} + +// MARK: - PropertyName + +/// The name of a customizable property that can be used in an `AnimationKeypath` +/// - These values should be shared between the two rendering engines, +/// since they form the public API of the `AnimationKeypath` system. +enum PropertyName: String { + case color = "Color" +} + +// MARK: CALayer properties + +extension LayerProperty { + static var position: LayerProperty { + .init( + caLayerKeypath: "transform.translation", + defaultValue: CGPoint(x: 0, y: 0), + customizableProperty: nil /* currently unsupported */) + } + + static var positionX: LayerProperty { + .init( + caLayerKeypath: "transform.translation.x", + defaultValue: 0, + customizableProperty: nil /* currently unsupported */) + } + + static var positionY: LayerProperty { + .init( + caLayerKeypath: "transform.translation.y", + defaultValue: 0, + customizableProperty: nil /* currently unsupported */) + } + + static var scale: LayerProperty { + .init( + caLayerKeypath: "transform.scale", + defaultValue: 1, + customizableProperty: nil /* currently unsupported */) + } + + static var scaleX: LayerProperty { + .init( + caLayerKeypath: "transform.scale.x", + defaultValue: 1, + customizableProperty: nil /* currently unsupported */) + } + + static var scaleY: LayerProperty { + .init( + caLayerKeypath: "transform.scale.y", + defaultValue: 1, + customizableProperty: nil /* currently unsupported */) + } + + static var rotation: LayerProperty { + .init( + caLayerKeypath: "transform.rotation", + defaultValue: 0, + customizableProperty: nil /* currently unsupported */) + } + + static var rotationY: LayerProperty { + .init( + caLayerKeypath: "transform.rotation.y", + defaultValue: 0, + customizableProperty: nil /* currently unsupported */) + } + + static var anchorPoint: LayerProperty { + .init( + caLayerKeypath: #keyPath(CALayer.anchorPoint), + // This is intentionally not `GGPoint(x: 0.5, y: 0.5)` (the actual default) + // to opt `anchorPoint` out of the KVC `setValue` flow, which causes issues. + defaultValue: nil, + customizableProperty: nil /* currently unsupported */) + } + + static var opacity: LayerProperty { + .init( + caLayerKeypath: #keyPath(CALayer.opacity), + defaultValue: 1, + customizableProperty: nil /* currently unsupported */) + } +} + +// MARK: CAShapeLayer properties + +extension LayerProperty { + static var path: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.path), + defaultValue: nil, + customizableProperty: nil /* currently unsupported */) + } + + static var fillColor: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.fillColor), + defaultValue: nil, + customizableProperty: .color) + } + + static var lineWidth: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.lineWidth), + defaultValue: 1, + customizableProperty: nil /* currently unsupported */) + } + + static var lineDashPhase: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.lineDashPhase), + defaultValue: 0, + customizableProperty: nil /* currently unsupported */) + } + + static var strokeColor: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.strokeColor), + defaultValue: nil, + customizableProperty: .color) + } + + static var strokeStart: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.strokeStart), + defaultValue: 0, + customizableProperty: nil /* currently unsupported */) + } + + static var strokeEnd: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.strokeEnd), + defaultValue: 1, + customizableProperty: nil /* currently unsupported */) + } +} + +// MARK: CAGradientLayer properties + +extension LayerProperty { + static var colors: LayerProperty<[CGColor]> { + .init( + caLayerKeypath: #keyPath(CAGradientLayer.colors), + defaultValue: nil, + customizableProperty: nil /* currently unsupported */) + } + + static var locations: LayerProperty<[CGFloat]> { + .init( + caLayerKeypath: #keyPath(CAGradientLayer.locations), + defaultValue: nil, + customizableProperty: nil /* currently unsupported */) + } + + static var startPoint: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAGradientLayer.startPoint), + defaultValue: nil, + customizableProperty: nil /* currently unsupported */) + } + + static var endPoint: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAGradientLayer.endPoint), + defaultValue: nil, + customizableProperty: nil /* currently unsupported */) + } +} + +// MARK: - CustomizableProperty types + +extension CustomizableProperty { + static var color: CustomizableProperty { + .init( + name: [.color], + conversion: { typeErasedValue in + guard let color = typeErasedValue as? Color else { + return nil + } + + return .rgba(CGFloat(color.r), CGFloat(color.g), CGFloat(color.b), CGFloat(color.a)) + }) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift new file mode 100644 index 00000000000..d3e81d07dfc --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift @@ -0,0 +1,52 @@ +// Created by Cal Stephens on 5/17/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - OpacityAnimationModel + +protocol OpacityAnimationModel { + /// The opacity animation to apply to a `CALayer` + var opacity: KeyframeGroup { get } +} + +// MARK: - Transform + OpacityAnimationModel + +extension Transform: OpacityAnimationModel { } + +// MARK: - ShapeTransform + OpacityAnimationModel + +extension ShapeTransform: OpacityAnimationModel { } + +// MARK: - Fill + OpacityAnimationModel + +extension Fill: OpacityAnimationModel { } + +// MARK: - GradientFill + OpacityAnimationModel + +extension GradientFill: OpacityAnimationModel { } + +// MARK: - Stroke + OpacityAnimationModel + +extension Stroke: OpacityAnimationModel { } + +// MARK: - GradientStroke + OpacityAnimationModel + +extension GradientStroke: OpacityAnimationModel { } + +extension CALayer { + /// Adds the opacity animation from the given `OpacityAnimationModel` to this layer + @nonobjc + func addOpacityAnimation(for opacity: OpacityAnimationModel, context: LayerAnimationContext) throws { + try addAnimation( + for: .opacity, + keyframes: opacity.opacity.keyframes, + value: { + // Lottie animation files express opacity as a numerical percentage value + // (e.g. 0%, 50%, 100%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.0, 0.5, 1.0). + $0.cgFloatValue / 100 + }, + context: context) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift new file mode 100644 index 00000000000..2dd3ff5277d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift @@ -0,0 +1,29 @@ +// Created by Cal Stephens on 12/21/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + /// Adds animations for the given `Rectangle` to this `CALayer` + @nonobjc + func addAnimations( + for rectangle: Rectangle, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .path, + keyframes: rectangle.size.keyframes, + value: { sizeKeyframe in + BezierPath.rectangle( + position: try rectangle.position + .exactlyOneKeyframe(context: context, description: "rectangle position").value.pointValue, + size: sizeKeyframe.sizeValue, + cornerRadius: try rectangle.cornerRadius + .exactlyOneKeyframe(context: context, description: "rectangle cornerRadius").value.cgFloatValue, + direction: rectangle.direction) + .cgPath() + }, + context: context) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift new file mode 100644 index 00000000000..f439320c73d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift @@ -0,0 +1,123 @@ +// Created by Cal Stephens on 1/7/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + /// Adds a `path` animation for the given `ShapeItem` + @nonobjc + func addAnimations(for shape: ShapeItem, context: LayerAnimationContext) throws { + switch shape { + case let customShape as Shape: + try addAnimations(for: customShape.path, context: context) + + case let combinedShape as CombinedShapeItem: + try addAnimations(for: combinedShape, context: context) + + case let ellipse as Ellipse: + try addAnimations(for: ellipse, context: context) + + case let rectangle as Rectangle: + try addAnimations(for: rectangle, context: context) + + case let star as Star: + try addAnimations(for: star, context: context) + + default: + // None of the other `ShapeItem` subclasses draw a `path` + try context.logCompatibilityIssue("Unexpected shape type \(type(of: shape))") + return + } + } + + /// Adds a `fillColor` animation for the given `Fill` object + @nonobjc + func addAnimations(for fill: Fill, context: LayerAnimationContext) throws { + fillRule = fill.fillRule.caFillRule + + try addAnimation( + for: .fillColor, + keyframes: fill.color.keyframes, + value: \.cgColorValue, + context: context) + + try addOpacityAnimation(for: fill, context: context) + } + + /// Adds animations for `strokeStart` and `strokeEnd` from the given `Trim` object + @nonobjc + func addAnimations(for trim: Trim, context: LayerAnimationContext) throws { + let (strokeStartKeyframes, strokeEndKeyframes) = trim.caShapeLayerKeyframes() + + if trim.offset.keyframes.contains(where: { $0.value.cgFloatValue != 0 }) { + try context.logCompatibilityIssue(""" + The CoreAnimation rendering engine doesn't support Trim offsets + """) + } + + try addAnimation( + for: .strokeStart, + keyframes: strokeStartKeyframes.keyframes, + value: { strokeStart in + // Lottie animation files express stoke trims as a numerical percentage value + // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.25, 0.5, 1.0). + CGFloat(strokeStart.cgFloatValue) / 100 + }, context: context) + + try addAnimation( + for: .strokeEnd, + keyframes: strokeEndKeyframes.keyframes, + value: { strokeEnd in + // Lottie animation files express stoke trims as a numerical percentage value + // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.25, 0.5, 1.0). + CGFloat(strokeEnd.cgFloatValue) / 100 + }, context: context) + } +} + +extension Trim { + + // MARK: Fileprivate + + /// The `strokeStart` and `strokeEnd` keyframes to apply to a `CAShapeLayer` + /// - `CAShapeLayer` requires that `strokeStart` be less than `strokeEnd`. + /// - Since this isn't a requirement in the Lottie schema, there are + /// some animations that have `strokeStart` and `strokeEnd` flipped. + /// - If we detect that this is the case for this specific `Trim`, then + /// we swap the start/end keyframes to match what `CAShapeLayer` expects. + fileprivate func caShapeLayerKeyframes() + -> (strokeStart: KeyframeGroup, strokeEnd: KeyframeGroup) + { + if startValueIsAlwaysGreaterThanEndValue() { + return (strokeStart: end, strokeEnd: start) + } else { + return (strokeStart: start, strokeEnd: end) + } + } + + // MARK: Private + + /// Checks whether or not the value for `trim.start` is greater + /// than the value for every `trim.end` at every keyframe. + private func startValueIsAlwaysGreaterThanEndValue() -> Bool { + let keyframeTimes = Set(start.keyframes.map { $0.time } + end.keyframes.map { $0.time }) + + let startInterpolator = KeyframeInterpolator(keyframes: start.keyframes) + let endInterpolator = KeyframeInterpolator(keyframes: end.keyframes) + + for keyframeTime in keyframeTimes { + guard + let startAtTime = startInterpolator.value(frame: keyframeTime) as? Vector1D, + let endAtTime = endInterpolator.value(frame: keyframeTime) as? Vector1D + else { continue } + + if startAtTime.cgFloatValue < endAtTime.cgFloatValue { + return false + } + } + + return true + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/StarAnimation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/StarAnimation.swift new file mode 100644 index 00000000000..b1c50ca21f7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/StarAnimation.swift @@ -0,0 +1,90 @@ +// Created by Cal Stephens on 1/10/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + + // MARK: Internal + + /// Adds animations for the given `Rectangle` to this `CALayer` + @nonobjc + func addAnimations( + for star: Star, + context: LayerAnimationContext) + throws + { + switch star.starType { + case .star: + try addStarAnimation(for: star, context: context) + case .polygon: + try addPolygonAnimation(for: star, context: context) + case .none: + break + } + } + + // MARK: Private + + @nonobjc + private func addStarAnimation( + for star: Star, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .path, + keyframes: star.position.keyframes, + value: { position in + // We can only use one set of keyframes to animate a given CALayer keypath, + // so we currently animate `position` and ignore any other keyframes. + // TODO: Is there a way to support this properly? + BezierPath.star( + position: position.pointValue, + outerRadius: try star.outerRadius + .exactlyOneKeyframe(context: context, description: "outerRadius").value.cgFloatValue, + innerRadius: try star.innerRadius? + .exactlyOneKeyframe(context: context, description: "innerRadius").value.cgFloatValue ?? 0, + outerRoundedness: try star.outerRoundness + .exactlyOneKeyframe(context: context, description: "outerRoundness").value.cgFloatValue, + innerRoundedness: try star.innerRoundness? + .exactlyOneKeyframe(context: context, description: "innerRoundness").value.cgFloatValue ?? 0, + numberOfPoints: try star.points + .exactlyOneKeyframe(context: context, description: "points").value.cgFloatValue, + rotation: try star.rotation + .exactlyOneKeyframe(context: context, description: "rotation").value.cgFloatValue, + direction: star.direction) + .cgPath() + }, + context: context) + } + + @nonobjc + private func addPolygonAnimation( + for star: Star, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .path, + keyframes: star.position.keyframes, + value: { position in + // We can only use one set of keyframes to animate a given CALayer keypath, + // so we currently animate `position` and ignore any other keyframes. + // TODO: Is there a way to support this properly? + BezierPath.polygon( + position: position.pointValue, + numberOfPoints: try star.points + .exactlyOneKeyframe(context: context, description: "numberOfPoints").value.cgFloatValue, + outerRadius: try star.outerRadius + .exactlyOneKeyframe(context: context, description: "outerRadius").value.cgFloatValue, + outerRoundedness: try star.outerRoundness + .exactlyOneKeyframe(context: context, description: "outerRoundedness").value.cgFloatValue, + rotation: try star.rotation + .exactlyOneKeyframe(context: context, description: "rotation").value.cgFloatValue, + direction: star.direction) + .cgPath() + }, + context: context) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift new file mode 100644 index 00000000000..290b06e96df --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift @@ -0,0 +1,70 @@ +// Created by Cal Stephens on 2/10/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import Foundation +import QuartzCore + +// MARK: - StrokeShapeItem + +/// A `ShapeItem` that represents a stroke +protocol StrokeShapeItem: OpacityAnimationModel { + var strokeColor: KeyframeGroup? { get } + var width: KeyframeGroup { get } + var lineCap: LineCap { get } + var lineJoin: LineJoin { get } + var miterLimit: Double { get } + var dashPattern: [DashElement]? { get } +} + +// MARK: - Stroke + StrokeShapeItem + +extension Stroke: StrokeShapeItem { + var strokeColor: KeyframeGroup? { color } +} + +// MARK: - GradientStroke + StrokeShapeItem + +extension GradientStroke: StrokeShapeItem { + var strokeColor: KeyframeGroup? { nil } +} + +// MARK: - CAShapeLayer + StrokeShapeItem + +extension CAShapeLayer { + /// Adds animations for properties related to the given `Stroke` object (`strokeColor`, `lineWidth`, etc) + @nonobjc + func addStrokeAnimations(for stroke: StrokeShapeItem, context: LayerAnimationContext) throws { + lineJoin = stroke.lineJoin.caLineJoin + lineCap = stroke.lineCap.caLineCap + miterLimit = CGFloat(stroke.miterLimit) + + if let strokeColor = stroke.strokeColor { + try addAnimation( + for: .strokeColor, + keyframes: strokeColor.keyframes, + value: \.cgColorValue, + context: context) + } + + try addAnimation( + for: .lineWidth, + keyframes: stroke.width.keyframes, + value: \.cgFloatValue, + context: context) + + try addOpacityAnimation(for: stroke, context: context) + + if let (dashPattern, dashPhase) = stroke.dashPattern?.shapeLayerConfiguration { + lineDashPattern = try dashPattern.map { + try KeyframeGroup(keyframes: $0) + .exactlyOneKeyframe(context: context, description: "stroke dashPattern").value.cgFloatValue as NSNumber + } + + try addAnimation( + for: .lineDashPhase, + keyframes: dashPhase, + value: \.cgFloatValue, + context: context) + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift new file mode 100644 index 00000000000..11a5a28ddd9 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift @@ -0,0 +1,205 @@ +// Created by Cal Stephens on 12/17/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - TransformModel + +/// This protocol mirrors the interface of `Transform`, +/// but it also implemented by `ShapeTransform` to allow +/// both transform types to share the same animation implementation. +protocol TransformModel { + /// The anchor point of the transform. + var anchorPoint: KeyframeGroup { get } + + /// The position of the transform. This is nil if the position data was split. + var _position: KeyframeGroup? { get } + + /// The positionX of the transform. This is nil if the position property is set. + var _positionX: KeyframeGroup? { get } + + /// The positionY of the transform. This is nil if the position property is set. + var _positionY: KeyframeGroup? { get } + + /// The scale of the transform + var scale: KeyframeGroup { get } + + /// The rotation of the transform. Note: This is single dimensional rotation. + var rotation: KeyframeGroup { get } +} + +// MARK: - Transform + TransformModel + +extension Transform: TransformModel { + var _position: KeyframeGroup? { position } + var _positionX: KeyframeGroup? { positionX } + var _positionY: KeyframeGroup? { positionY } +} + +// MARK: - ShapeTransform + TransformModel + +extension ShapeTransform: TransformModel { + var anchorPoint: KeyframeGroup { anchor } + var _position: KeyframeGroup? { position } + var _positionX: KeyframeGroup? { nil } + var _positionY: KeyframeGroup? { nil } +} + +// MARK: - CALayer + TransformModel + +extension CALayer { + + // MARK: Internal + + /// Adds transform-related animations from the given `TransformModel` to this layer + /// - This _doesn't_ apply `transform.opacity`, which has to be handled separately + /// since child layers don't inherit the `opacity` of their parent. + @nonobjc + func addTransformAnimations(for transformModel: TransformModel, context: LayerAnimationContext) throws { + try addPositionAnimations(from: transformModel, context: context) + try addAnchorPointAnimation(from: transformModel, context: context) + try addScaleAnimations(from: transformModel, context: context) + try addRotationAnimation(from: transformModel, context: context) + } + + // MARK: Private + + @nonobjc + private func addPositionAnimations( + from transformModel: TransformModel, + context: LayerAnimationContext) + throws + { + if let positionKeyframes = transformModel._position?.keyframes { + try addAnimation( + for: .position, + keyframes: positionKeyframes, + value: \.pointValue, + context: context) + } else if + let xKeyframes = transformModel._positionX?.keyframes, + let yKeyframes = transformModel._positionY?.keyframes + { + try addAnimation( + for: .positionX, + keyframes: xKeyframes, + value: \.cgFloatValue, + context: context) + + try addAnimation( + for: .positionY, + keyframes: yKeyframes, + value: \.cgFloatValue, + context: context) + } else { + try context.logCompatibilityIssue(""" + `Transform` values must provide either `position` or `positionX` / `positionY` keyframes + """) + } + } + + @nonobjc + private func addAnchorPointAnimation( + from transformModel: TransformModel, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .anchorPoint, + keyframes: transformModel.anchorPoint.keyframes, + value: { absoluteAnchorPoint in + guard bounds.width > 0, bounds.height > 0 else { + LottieLogger.shared.assertionFailure("Size must be non-zero before an animation can be played") + return .zero + } + + // Lottie animation files express anchorPoint as an absolute point value, + // so we have to divide by the width/height of this layer to get the + // relative decimal values expected by Core Animation. + return CGPoint( + x: CGFloat(absoluteAnchorPoint.x) / bounds.width, + y: CGFloat(absoluteAnchorPoint.y) / bounds.height) + }, + context: context) + } + + @nonobjc + private func addScaleAnimations( + from transformModel: TransformModel, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .scaleX, + keyframes: transformModel.scale.keyframes, + value: { scale in + // Lottie animation files express scale as a numerical percentage value + // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.5, 1.0, 2.0). + // - Negative `scale.x` values aren't applied correctly by Core Animation. + // This appears to be because we animate `transform.scale.x` and `transform.scale.y` + // as separate `CAKeyframeAnimation`s instead of using a single animation of `transform` itself. + // https://openradar.appspot.com/FB9862872 + // - To work around this, we set up a `rotationY` animation below + // to flip the view horizontally, which gives us the desired effect. + abs(CGFloat(scale.x) / 100) + }, + context: context) + + // When `scale.x` is negative, we have to rotate the view + // half way around the y axis to flip it horizontally. + // - We don't do this in snapshot tests because it breaks the tests + // in surprising ways that don't happen at runtime. Definitely not ideal. + if TestHelpers.snapshotTestsAreRunning { + if transformModel.scale.keyframes.contains(where: { $0.value.x < 0 }) { + LottieLogger.shared.warn(""" + Negative `scale.x` values are not displayed correctly in snapshot tests + """) + } + } else { + try addAnimation( + for: .rotationY, + keyframes: transformModel.scale.keyframes, + value: { scale in + if scale.x < 0 { + return .pi + } else { + return 0 + } + }, + context: context) + } + + try addAnimation( + for: .scaleY, + keyframes: transformModel.scale.keyframes, + value: { scale in + // Lottie animation files express scale as a numerical percentage value + // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.5, 1.0, 2.0). + // - Negative `scaleY` values are correctly applied (they flip the view + // vertically), so we don't have to apply an additional rotation animation + // like we do for `scaleX`. + CGFloat(scale.y) / 100 + }, + context: context) + } + + private func addRotationAnimation( + from transformModel: TransformModel, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .rotation, + keyframes: transformModel.rotation.keyframes, + value: { rotationDegrees in + // Lottie animation files express rotation in degrees + // (e.g. 90º, 180º, 360º) so we covert to radians to get the + // values expected by Core Animation (e.g. π/2, π, 2π) + rotationDegrees.cgFloatValue * .pi / 180 + }, + context: context) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/VisibilityAnimation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/VisibilityAnimation.swift new file mode 100644 index 00000000000..8d12356f0a2 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Animations/VisibilityAnimation.swift @@ -0,0 +1,37 @@ +// Created by Cal Stephens on 12/21/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CALayer { + /// Adds an animation for the given `inTime` and `outTime` to this `CALayer` + @nonobjc + func addVisibilityAnimation( + inFrame: AnimationFrameTime, + outFrame: AnimationFrameTime, + context: LayerAnimationContext) + { + let animation = CAKeyframeAnimation(keyPath: #keyPath(isHidden)) + animation.calculationMode = .discrete + + animation.values = [ + true, // hidden, before `inFrame` + false, // visible + true, // hidden, after `outFrame` + ] + + // From the documentation of `keyTimes`: + // - If the calculationMode is set to discrete, the first value in the array + // must be 0.0 and the last value must be 1.0. The array should have one more + // entry than appears in the values array. For example, if there are two values, + // there should be three key times. + animation.keyTimes = [ + NSNumber(value: 0.0), + NSNumber(value: max(Double(context.progressTime(for: inFrame)), 0)), + NSNumber(value: min(Double(context.progressTime(for: outFrame)), 1)), + NSNumber(value: 1.0), + ] + + add(animation, timedWith: context) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/CompatibilityTracker.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/CompatibilityTracker.swift new file mode 100644 index 00000000000..f08e05a9d88 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/CompatibilityTracker.swift @@ -0,0 +1,128 @@ +// Created by Cal Stephens on 5/4/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +// MARK: - CompatibilityIssue + +/// A compatibility issue that was encountered while setting up an animation with the Core Animation engine +struct CompatibilityIssue: CustomStringConvertible { + let message: String + let context: String + + var description: String { + "[\(context)] \(message)" + } +} + +// MARK: - CompatibilityTracker + +/// A type that tracks whether or not an animation is compatible with the Core Animation engine +final class CompatibilityTracker { + + // MARK: Lifecycle + + init(mode: Mode) { + self.mode = mode + } + + // MARK: Internal + + /// How compatibility issues should be handled + enum Mode { + /// When a compatibility issue is encountered, an error will be thrown immediately, + /// aborting the animation setup process as soon as possible. + case abort + + /// When a compatibility issue is encountered, it is stored in `CompatibilityTracker.issues` + case track + } + + enum Error: Swift.Error { + case encounteredCompatibilityIssue(CompatibilityIssue) + } + + /// Records a compatibility issue that will be reported according to `CompatibilityTracker.Mode` + func logIssue(message: String, context: String) throws { + LottieLogger.shared.assert(!context.isEmpty, "Compatibility issue context is unexpectedly empty") + + let issue = CompatibilityIssue( + // Compatibility messages are usually written in source files using multi-line strings, + // but converting them to be a single line makes it easier to read the ultimate log output. + message: message.replacingOccurrences(of: "\n", with: " "), + context: context) + + switch mode { + case .abort: + throw CompatibilityTracker.Error.encounteredCompatibilityIssue(issue) + case .track: + issues.append(issue) + } + } + + /// Asserts that a condition is true, otherwise logs a compatibility issue that will be reported + /// according to `CompatibilityTracker.Mode` + func assert( + _ condition: Bool, + _ message: @autoclosure () -> String, + context: @autoclosure () -> String) + throws + { + if !condition { + try logIssue(message: message(), context: context()) + } + } + + /// Reports the compatibility issues that were recorded when setting up the animation, + /// and clears the set of tracked issues. + func reportCompatibilityIssues(_ handler: ([CompatibilityIssue]) -> Void) { + handler(issues) + issues = [] + } + + // MARK: Private + + private let mode: Mode + + /// Compatibility issues encountered while setting up the animation + private var issues = [CompatibilityIssue]() + +} + +// MARK: - CompatibilityTrackerProviding + +protocol CompatibilityTrackerProviding { + var compatibilityTracker: CompatibilityTracker { get } + var compatibilityIssueContext: String { get } +} + +extension CompatibilityTrackerProviding { + /// Records a compatibility issue that will be reported according to `CompatibilityTracker.Mode` + func logCompatibilityIssue(_ message: String) throws { + try compatibilityTracker.logIssue(message: message, context: compatibilityIssueContext) + } + + /// Asserts that a condition is true, otherwise logs a compatibility issue that will be reported + /// according to `CompatibilityTracker.Mode` + func compatibilityAssert( + _ condition: Bool, + _ message: @autoclosure () -> String) + throws + { + try compatibilityTracker.assert(condition, message(), context: compatibilityIssueContext) + } +} + +// MARK: - LayerContext + CompatibilityTrackerProviding + +extension LayerContext: CompatibilityTrackerProviding { + var compatibilityIssueContext: String { + layerName + } +} + +// MARK: - LayerAnimationContext + CompatibilityTrackerProviding + +extension LayerAnimationContext: CompatibilityTrackerProviding { + var compatibilityIssueContext: String { + currentKeypath.fullPath + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/CoreAnimationLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/CoreAnimationLayer.swift new file mode 100644 index 00000000000..05f140e59f8 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/CoreAnimationLayer.swift @@ -0,0 +1,456 @@ +// Created by Cal Stephens on 12/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import Foundation +import QuartzCore + +// MARK: - CoreAnimationLayer + +/// The root `CALayer` of the Core Animation rendering engine +final class CoreAnimationLayer: BaseAnimationLayer { + + // MARK: Lifecycle + + /// Initializes a `CALayer` that renders the given animation using `CAAnimation`s. + /// - This initializer is throwing, but will only throw when using + /// `CompatibilityTracker.Mode.abort`. + init( + animation: Animation, + imageProvider: AnimationImageProvider, + fontProvider: AnimationFontProvider, + compatibilityTrackerMode: CompatibilityTracker.Mode) + throws + { + self.animation = animation + self.imageProvider = imageProvider + self.fontProvider = fontProvider + compatibilityTracker = CompatibilityTracker(mode: compatibilityTrackerMode) + super.init() + + setup() + try setupChildLayers() + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("init(layer:) incorrectly called with \(type(of: layer))") + } + + animation = typedLayer.animation + currentAnimationConfiguration = typedLayer.currentAnimationConfiguration + imageProvider = typedLayer.imageProvider + fontProvider = typedLayer.fontProvider + didSetUpAnimation = typedLayer.didSetUpAnimation + compatibilityTracker = typedLayer.compatibilityTracker + super.init(layer: typedLayer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + /// Timing-related configuration to apply to this layer's child `CAAnimation`s + /// - This is effectively a configurable subset of `CAMediaTiming` + struct CAMediaTimingConfiguration: Equatable { + var autoreverses = false + var repeatCount: Float = 0 + var speed: Float = 1 + var timeOffset: TimeInterval = 0 + } + + enum PlaybackState: Equatable { + /// The animation is playing in real-time + case playing + /// The animation is statically displaying a specific frame + case paused(frame: AnimationFrameTime) + } + + /// A closure that is called after this layer sets up its animation. + /// If the animation setup was unsuccessful and encountered compatibility issues, + /// those issues are included in this call. + var didSetUpAnimation: (([CompatibilityIssue]) -> Void)? + + /// The `AnimationImageProvider` that `ImageLayer`s use to retrieve images, + /// referenced by name in the animation json. + var imageProvider: AnimationImageProvider { + didSet { reloadImages() } + } + + /// The `FontProvider` that `TextLayer`s use to retrieve the `CTFont` + /// that they should use to render their text content + var fontProvider: AnimationFontProvider { + didSet { reloadFonts() } + } + + /// Queues the animation with the given timing configuration + /// to begin playing at the next `display()` call. + /// - This batches together animations so that even if `playAnimation` + /// is called multiple times in the same run loop cycle, the animation + /// will only be set up a single time. + func playAnimation( + context: AnimationContext, + timingConfiguration: CAMediaTimingConfiguration, + playbackState: PlaybackState = .playing) + { + pendingAnimationConfiguration = ( + animationConfiguration: .init(animationContext: context, timingConfiguration: timingConfiguration), + playbackState: playbackState) + + setNeedsDisplay() + } + + override func layoutSublayers() { + super.layoutSublayers() + + // If no animation has been set up yet, display the first frame + // now that the layer hierarchy has been setup and laid out + if + pendingAnimationConfiguration == nil, + currentAnimationConfiguration == nil, + bounds.size != .zero + { + currentFrame = animation.frameTime(forProgress: animationProgress) + } + } + + override func display() { + // We intentionally don't call `super.display()`, since this layer + // doesn't directly render any content. + // - This fixes an issue where certain animations would unexpectedly + // allocate a very large amount of memory (400mb+). + // - Alternatively this layer could subclass `CATransformLayer`, + // but this causes Core Animation to emit unnecessary logs. + + if let pendingAnimationConfiguration = pendingAnimationConfiguration { + self.pendingAnimationConfiguration = nil + + do { + try setupAnimation(for: pendingAnimationConfiguration.animationConfiguration) + } catch { + if case CompatibilityTracker.Error.encounteredCompatibilityIssue(let compatibilityIssue) = error { + // Even though the animation setup failed, we still update the layer's playback state + // so it can be read by the parent `AnimationView` when handling this error + currentPlaybackState = pendingAnimationConfiguration.playbackState + + didSetUpAnimation?([compatibilityIssue]) + return + } + } + + currentPlaybackState = pendingAnimationConfiguration.playbackState + + compatibilityTracker.reportCompatibilityIssues { compatibilityIssues in + didSetUpAnimation?(compatibilityIssues) + } + } + } + + // MARK: Private + + private struct AnimationConfiguration: Equatable { + let animationContext: AnimationContext + let timingConfiguration: CAMediaTimingConfiguration + } + + /// The configuration for the most recent animation which has been + /// queued by calling `playAnimation` but not yet actually set up + private var pendingAnimationConfiguration: ( + animationConfiguration: AnimationConfiguration, + playbackState: PlaybackState)? + + /// Configuration for the animation that is currently setup in this layer + private var currentAnimationConfiguration: AnimationConfiguration? + + /// The current progress of the placeholder `CAAnimation`, + /// which is also the realtime animation progress of this layer's animation + @objc private var animationProgress: CGFloat = 0 + + private let animation: Animation + private let valueProviderStore = ValueProviderStore() + private let compatibilityTracker: CompatibilityTracker + + /// The current playback state of the animation that is displayed in this layer + private var currentPlaybackState: PlaybackState? { + didSet { + guard playbackState != oldValue else { return } + + switch playbackState { + case .playing, nil: + timeOffset = 0 + case .paused(let frame): + timeOffset = animation.time(forFrame: frame) + } + } + } + + /// The current or pending playback state of the animation displayed in this layer + private var playbackState: PlaybackState? { + pendingAnimationConfiguration?.playbackState ?? currentPlaybackState + } + + /// Context used when setting up and configuring sublayers + private var layerContext: LayerContext { + LayerContext( + animation: animation, + imageProvider: imageProvider, + fontProvider: fontProvider, + compatibilityTracker: compatibilityTracker, + layerName: "root layer") + } + + private func setup() { + bounds = animation.bounds + } + + private func setupChildLayers() throws { + try setupLayerHierarchy( + for: animation.layers, + context: layerContext) + } + + /// Immediately builds and begins playing `CAAnimation`s for each sublayer + private func setupAnimation(for configuration: AnimationConfiguration) throws { + // Remove any existing animations from the layer hierarchy + removeAnimations() + + currentAnimationConfiguration = configuration + + let layerContext = LayerAnimationContext( + animation: animation, + timingConfiguration: configuration.timingConfiguration, + startFrame: configuration.animationContext.playFrom, + endFrame: configuration.animationContext.playTo, + valueProviderStore: valueProviderStore, + compatibilityTracker: compatibilityTracker, + currentKeypath: AnimationKeypath(keys: [])) + + // Perform a layout pass if necessary so all of the sublayers + // have the most up-to-date sizing information + layoutIfNeeded() + + // Set the speed of this layer, which will be inherited + // by all sublayers and their animations. + // - This is required to support scrubbing with a speed of 0 + speed = configuration.timingConfiguration.speed + + // Setup a placeholder animation to let us track the realtime animation progress + setupPlaceholderAnimation(context: layerContext) + + // Set up the new animations with the current `TimingConfiguration` + for animationLayer in sublayers ?? [] { + try (animationLayer as? AnimationLayer)?.setupAnimations(context: layerContext) + } + } + + /// Sets up a placeholder `CABasicAnimation` that tracks the current + /// progress of this animation (between 0 and 1). This lets us provide + /// realtime animation progress via `self.currentFrame`. + private func setupPlaceholderAnimation(context: LayerAnimationContext) { + let animationProgressTracker = CABasicAnimation(keyPath: #keyPath(animationProgress)) + animationProgressTracker.fromValue = 0 + animationProgressTracker.toValue = 1 + + let timedProgressAnimation = animationProgressTracker.timed(with: context, for: self) + timedProgressAnimation.delegate = currentAnimationConfiguration?.animationContext.closure + add(timedProgressAnimation, forKey: #keyPath(animationProgress)) + } + + // Removes the current `CAAnimation`s, and rebuilds new animations + // using the same configuration as the previous animations. + private func rebuildCurrentAnimation() { + guard + let currentConfiguration = currentAnimationConfiguration, + let playbackState = playbackState, + // Don't replace any pending animations that are queued to begin + // on the next run loop cycle, since an existing pending animation + // will cause the animation to be rebuilt anyway. + pendingAnimationConfiguration == nil + else { return } + + removeAnimations() + + switch playbackState { + case .paused(let frame): + currentFrame = frame + + case .playing: + playAnimation( + context: currentConfiguration.animationContext, + timingConfiguration: currentConfiguration.timingConfiguration) + } + } + +} + +// MARK: RootAnimationLayer + +extension CoreAnimationLayer: RootAnimationLayer { + + var primaryAnimationKey: AnimationKey { + .specific(#keyPath(animationProgress)) + } + + var isAnimationPlaying: Bool? { + switch playbackState { + case .playing: + return true + case nil, .paused: + return false + } + } + + var currentFrame: AnimationFrameTime { + get { + switch playbackState { + case .playing, nil: + return animation.frameTime(forProgress: (presentation() ?? self).animationProgress) + case .paused(let frame): + return frame + } + } + set { + // We can display a specific frame of the animation by setting + // `timeOffset` of this layer. This requires setting up the layer hierarchy + // with a specific configuration (speed=0, etc) at least once. But if + // the layer hierarchy is already set up correctly, we can update the + // `timeOffset` very cheaply. + let requiredAnimationConfiguration = AnimationConfiguration( + animationContext: AnimationContext( + playFrom: animation.startFrame, + playTo: animation.endFrame, + closure: nil), + timingConfiguration: CAMediaTimingConfiguration(speed: 0)) + + if + pendingAnimationConfiguration == nil, + currentAnimationConfiguration == requiredAnimationConfiguration + { + currentPlaybackState = .paused(frame: newValue) + } + + else { + playAnimation( + context: requiredAnimationConfiguration.animationContext, + timingConfiguration: requiredAnimationConfiguration.timingConfiguration, + playbackState: .paused(frame: newValue)) + } + } + } + + var renderScale: CGFloat { + get { contentsScale } + set { + contentsScale = newValue + + for sublayer in allSublayers { + sublayer.contentsScale = newValue + } + } + } + + var respectAnimationFrameRate: Bool { + get { false } + set { LottieLogger.shared.assertionFailure("`respectAnimationFrameRate` is currently unsupported") } + } + + var _animationLayers: [CALayer] { + (sublayers ?? []).filter { $0 is AnimationLayer } + } + + var textProvider: AnimationTextProvider { + get { DictionaryTextProvider([:]) } + set { LottieLogger.shared.assertionFailure("`textProvider` is currently unsupported") } + } + + func reloadImages() { + // When the image provider changes, we have to update all `ImageLayer`s + // so they can query the most up-to-date image from the new image provider. + for sublayer in allSublayers { + if let imageLayer = sublayer as? ImageLayer { + imageLayer.setupImage(context: layerContext) + } + } + } + + func reloadFonts() { + // When the text provider changes, we have to update all `TextLayer`s + // so they can query the most up-to-date font from the new font provider. + for sublayer in allSublayers { + if let textLayer = sublayer as? TextLayer { + try? textLayer.configureRenderLayer(with: layerContext) + } + } + } + + func forceDisplayUpdate() { + // Unimplemented / unused + } + + func logHierarchyKeypaths() { + // Unimplemented / unused + } + + func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + valueProviderStore.setValueProvider(valueProvider, keypath: keypath) + + // We need to rebuild the current animation after registering a value provider, + // since any existing `CAAnimation`s could now be out of date. + rebuildCurrentAnimation() + } + + func getValue(for _: AnimationKeypath, atFrame _: AnimationFrameTime?) -> Any? { + LottieLogger.shared.assertionFailure(""" + The Core Animation rendering engine doesn't support querying values for individual frames + """) + return nil + } + + func getOriginalValue(for _: AnimationKeypath, atFrame _: AnimationFrameTime?) -> Any? { + LottieLogger.shared.assertionFailure(""" + The Core Animation rendering engine doesn't support querying values for individual frames + """) + return nil + } + + func layer(for _: AnimationKeypath) -> CALayer? { + LottieLogger.shared.assertionFailure("`AnimationKeypath`s are currently unsupported") + return nil + } + + func animatorNodes(for _: AnimationKeypath) -> [AnimatorNode]? { + LottieLogger.shared.assertionFailure("`AnimatorNode`s are not used in this rendering implementation") + return nil + } + + func removeAnimations() { + currentAnimationConfiguration = nil + currentPlaybackState = nil + removeAllAnimations() + + for sublayer in allSublayers { + sublayer.removeAllAnimations() + } + } + +} + +// MARK: - CALayer + allSublayers + +extension CALayer { + /// All of the layers in the layer tree that are descendants from this later + @nonobjc + var allSublayers: [CALayer] { + var allSublayers: [CALayer] = [] + + for sublayer in sublayers ?? [] { + allSublayers.append(sublayer) + allSublayers.append(contentsOf: sublayer.allSublayers) + } + + return allSublayers + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift new file mode 100644 index 00000000000..5b0baf16d1e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift @@ -0,0 +1,35 @@ +// Created by Cal Stephens on 12/15/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - CALayer + fillBoundsOfSuperlayer + +extension CALayer { + /// Updates the `bounds` of this layer to fill the bounds of its `superlayer` + /// without setting `frame` (which is not permitted if the layer can rotate) + @nonobjc + func fillBoundsOfSuperlayer() { + guard let superlayer = superlayer else { return } + + if let customLayerLayer = self as? CustomLayoutLayer { + customLayerLayer.layout(superlayerBounds: superlayer.bounds) + } + + else { + // By default the `anchorPoint` of a layer is `CGPoint(x: 0.5, y: 0.5)`. + // Setting it to `.zero` makes the layer have the same coordinate space + // as its superlayer, which lets use use `superlayer.bounds` directly. + anchorPoint = .zero + + bounds = superlayer.bounds + } + } +} + +// MARK: - CustomLayoutLayer + +/// A `CALayer` that sets a custom `bounds` and `anchorPoint` relative to its superlayer +protocol CustomLayoutLayer: CALayer { + func layout(superlayerBounds: CGRect) +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift new file mode 100644 index 00000000000..728a8937c7d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift @@ -0,0 +1,37 @@ +// Created by Cal Stephens on 1/11/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +// MARK: - KeyframeGroup + exactlyOneKeyframe + +extension KeyframeGroup { + /// Retrieves the first `Keyframe` from this group, + /// and asserts that there are not any extra keyframes that would be ignored + /// + /// - There are several places in Lottie animation definitions where multiple + /// sets of keyframe timings can be provided for properties that have to + /// be applied to a single `CALayer` property (for example, the definition for a + /// `Rectangle` technically lets you animate `size`, `position`, and `cornerRadius` + /// separately, but these all have to be combined into a single `CAKeyframeAnimation` + /// on the `CAShapeLayer.path` property. + /// + /// - In those sorts of cases, we currently choose one one `KeyframeGroup` to provide the + /// timing information, and disallow simultaneous animations on the other properties. + /// + func exactlyOneKeyframe( + context: CompatibilityTrackerProviding, + description: String, + fileID _: StaticString = #fileID, + line _: UInt = #line) + throws + -> Keyframe + { + try context.compatibilityAssert( + keyframes.count == 1, + """ + The Core Animation rendering engine does not support animating multiple keyframes + for \(description) values (due to limitations of Core Animation `CAKeyframeAnimation`s). + """) + + return keyframes[0] + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Extensions/Keyframes+combinedIfPossible.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Extensions/Keyframes+combinedIfPossible.swift new file mode 100644 index 00000000000..04237b5a28c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Extensions/Keyframes+combinedIfPossible.swift @@ -0,0 +1,61 @@ +// Created by Cal Stephens on 1/28/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +// MARK: - Keyframes + +enum Keyframes { + /// Combines the given `[KeyframeGroup]` of `Keyframe`s + /// into a single `KeyframeGroup` of `Keyframe<[T]>`s + /// if all of the `KeyframeGroup`s have the exact same animation timing + static func combinedIfPossible(_ groups: [KeyframeGroup]) -> KeyframeGroup<[T]>? { + guard + !groups.isEmpty, + groups.allSatisfy({ $0.hasSameTimingParameters(as: groups[0]) }) + else { return nil } + + var combinedKeyframes = ContiguousArray>() + + for index in groups[0].keyframes.indices { + let baseKeyframe = groups[0].keyframes[index] + let combinedValues = groups.map { $0.keyframes[index].value } + combinedKeyframes.append(baseKeyframe.withValue(combinedValues)) + } + + return KeyframeGroup(keyframes: combinedKeyframes) + } + + /// Combines the given `[KeyframeGroup?]` of `Keyframe`s + /// into a single `KeyframeGroup` of `Keyframe<[T]>`s + /// if all of the `KeyframeGroup`s have the exact same animation timing + static func combinedIfPossible(_ groups: [KeyframeGroup?]) -> KeyframeGroup<[T]>? { + let nonOptionalGroups = groups.compactMap { $0 } + guard nonOptionalGroups.count == groups.count else { return nil } + return combinedIfPossible(nonOptionalGroups) + } +} + +extension KeyframeGroup { + /// Whether or not all of the keyframes in this `KeyframeGroup` have the same + /// timing parameters as the corresponding keyframe in the other given `KeyframeGroup` + func hasSameTimingParameters(as other: KeyframeGroup) -> Bool { + guard keyframes.count == other.keyframes.count else { + return false + } + + return zip(keyframes, other.keyframes).allSatisfy { + $0.hasSameTimingParameters(as: $1) + } + } +} + +extension Keyframe { + /// Whether or not this keyframe has the same timing parameters as the given keyframe + func hasSameTimingParameters(as other: Keyframe) -> Bool { + time == other.time + && isHold == other.isHold + && inTangent == other.inTangent + && outTangent == other.outTangent + && spatialInTangent == other.spatialInTangent + && spatialOutTangent == other.spatialOutTangent + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift new file mode 100644 index 00000000000..37ae2531da3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift @@ -0,0 +1,70 @@ +// Created by Cal Stephens on 12/14/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - AnimationLayer + +/// A type of `CALayer` that can be used in a Lottie animation +/// - Layers backed by a `LayerModel` subclass should subclass `BaseCompositionLayer` +protocol AnimationLayer: CALayer { + /// Instructs this layer to setup its `CAAnimation`s + /// using the given `LayerAnimationContext` + func setupAnimations(context: LayerAnimationContext) throws +} + +// MARK: - LayerAnimationContext + +// Context describing the timing parameters of the current animation +struct LayerAnimationContext { + /// The animation being played + let animation: Animation + + /// The timing configuration that should be applied to `CAAnimation`s + let timingConfiguration: CoreAnimationLayer.CAMediaTimingConfiguration + + /// The absolute frame number that this animation begins at + let startFrame: AnimationFrameTime + + /// The absolute frame number that this animation ends at + let endFrame: AnimationFrameTime + + /// The set of custom Value Providers applied to this animation + let valueProviderStore: ValueProviderStore + + /// Information about whether or not an animation is compatible with the Core Animation engine + let compatibilityTracker: CompatibilityTracker + + /// The AnimationKeypath represented by the current layer + var currentKeypath: AnimationKeypath + + /// A closure that remaps the given frame in the child layer's local time to a frame + /// in the animation's overall global time + private(set) var timeRemapping: ((AnimationFrameTime) -> AnimationFrameTime) = { $0 } + + /// Adds the given component string to the `AnimationKeypath` stored + /// that describes the current path being configured by this context value + func addingKeypathComponent(_ component: String) -> LayerAnimationContext { + var context = self + context.currentKeypath.keys.append(component) + return context + } + + /// The `AnimationProgressTime` for the given `AnimationFrameTime` within this layer, + /// accounting for the `timeRemapping` applied to this layer + func progressTime(for frame: AnimationFrameTime) -> AnimationProgressTime { + animation.progressTime(forFrame: timeRemapping(frame), clamped: false) + } + + /// Chains an additional `timeRemapping` closure onto this layer context + func withTimeRemapping( + _ additionalTimeRemapping: @escaping (AnimationFrameTime) -> AnimationFrameTime) + -> LayerAnimationContext + { + var copy = self + copy.timeRemapping = { [existingTimeRemapping = timeRemapping] time in + existingTimeRemapping(additionalTimeRemapping(time)) + } + return copy + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/BaseAnimationLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/BaseAnimationLayer.swift new file mode 100644 index 00000000000..06248b20f87 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/BaseAnimationLayer.swift @@ -0,0 +1,33 @@ +// Created by Cal Stephens on 1/27/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +/// A base `CALayer` that manages the frame and animations +/// of its `sublayers` and `mask` +class BaseAnimationLayer: CALayer, AnimationLayer { + + // MARK: Internal + + override func layoutSublayers() { + super.layoutSublayers() + + for sublayer in managedSublayers { + sublayer.fillBoundsOfSuperlayer() + } + } + + func setupAnimations(context: LayerAnimationContext) throws { + for childAnimationLayer in managedSublayers { + try (childAnimationLayer as? AnimationLayer)?.setupAnimations(context: context) + } + } + + // MARK: Private + + /// All of the sublayers managed by this container + private var managedSublayers: [CALayer] { + (sublayers ?? []) + [mask].compactMap { $0 } + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift new file mode 100644 index 00000000000..67b6bab1514 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift @@ -0,0 +1,87 @@ +// Created by Cal Stephens on 12/20/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - BaseCompositionLayer + +/// The base type of `AnimationLayer` that can contain other `AnimationLayer`s +class BaseCompositionLayer: BaseAnimationLayer { + + // MARK: Lifecycle + + init(layerModel: LayerModel) { + baseLayerModel = layerModel + super.init() + + setupSublayers() + compositingFilter = layerModel.blendMode.filterName + name = layerModel.name + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + baseLayerModel = typedLayer.baseLayerModel + super.init(layer: typedLayer) + } + + // MARK: Internal + + /// Whether or not this layer render should render any visible content + var renderLayerContents: Bool { true } + + /// Sets up the base `LayerModel` animations for this layer, + /// and all child `AnimationLayer`s. + /// - Can be overridden by subclasses, which much call `super`. + override func setupAnimations(context: LayerAnimationContext) throws { + var context = context + if renderLayerContents { + context = context.addingKeypathComponent(baseLayerModel.name) + } + + try setupLayerAnimations(context: context) + try setupChildAnimations(context: context) + } + + func setupLayerAnimations(context: LayerAnimationContext) throws { + let context = context.addingKeypathComponent(baseLayerModel.name) + + try addTransformAnimations(for: baseLayerModel.transform, context: context) + + if renderLayerContents { + try addOpacityAnimation(for: baseLayerModel.transform, context: context) + + addVisibilityAnimation( + inFrame: CGFloat(baseLayerModel.inFrame), + outFrame: CGFloat(baseLayerModel.outFrame), + context: context) + } + } + + func setupChildAnimations(context: LayerAnimationContext) throws { + try super.setupAnimations(context: context) + } + + // MARK: Private + + private let baseLayerModel: LayerModel + + private func setupSublayers() { + if + renderLayerContents, + let masks = baseLayerModel.masks + { + mask = MaskCompositionLayer(masks: masks) + } + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift new file mode 100644 index 00000000000..571605b8544 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift @@ -0,0 +1,131 @@ +// Created by Cal Stephens on 1/11/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CALayer { + /// Sets up an `AnimationLayer` / `CALayer` hierarchy in this layer, + /// using the given list of layers. + @nonobjc + func setupLayerHierarchy( + for layers: [LayerModel], + context: LayerContext) + throws + { + // An `Animation`'s `LayerModel`s are listed from front to back, + // but `CALayer.sublayers` are listed from back to front. + // We reverse the layer ordering to match what Core Animation expects. + // The final view hierarchy must display the layers in this exact order. + let layersInZAxisOrder = layers.reversed() + + let layersByIndex = Dictionary(grouping: layersInZAxisOrder, by: \.index) + .compactMapValues(\.first) + + /// Layers specify a `parent` layer. Child layers inherit the `transform` of their parent. + /// - We can't add the child as a sublayer of the parent `CALayer`, since that would + /// break the ordering specified in `layersInZAxisOrder`. + /// - Instead, we create an invisible `TransformLayer` to handle the parent + /// transform animations, and add the child layer to that `TransformLayer`. + func makeParentTransformLayer( + childLayerModel: LayerModel, + childLayer: CALayer, + name: (LayerModel) -> String) + -> CALayer + { + guard + let parentIndex = childLayerModel.parent, + let parentLayerModel = layersByIndex[parentIndex] + else { return childLayer } + + let parentLayer = TransformLayer(layerModel: parentLayerModel) + parentLayer.name = name(parentLayerModel) + parentLayer.addSublayer(childLayer) + + return makeParentTransformLayer( + childLayerModel: parentLayerModel, + childLayer: parentLayer, + name: name) + } + + // Create an `AnimationLayer` for each `LayerModel` + for (layerModel, maskLayerModel) in try layersInZAxisOrder.pairedLayersAndMasks(context: context) { + guard let layer = try layerModel.makeAnimationLayer(context: context) else { + continue + } + + // If this layer has a `parent`, we create an invisible `TransformLayer` + // to handle displaying / animating the parent transform. + let parentTransformLayer = makeParentTransformLayer( + childLayerModel: layerModel, + childLayer: layer, + name: { parentLayerModel in + "\(layerModel.name) (parent, \(parentLayerModel.name))" + }) + + // Create the `mask` layer for this layer, if it has a `MatteType` + if + let maskLayerModel = maskLayerModel, + let maskLayer = try maskLayerModel.makeAnimationLayer(context: context) + { + let maskParentTransformLayer = makeParentTransformLayer( + childLayerModel: maskLayerModel, + childLayer: maskLayer, + name: { parentLayerModel in + "\(maskLayerModel.name) (mask of \(layerModel.name)) (parent, \(parentLayerModel.name))" + }) + + // Set up a parent container to host both the layer + // and its mask in the same coordinate space + let maskContainer = BaseAnimationLayer() + maskContainer.name = "\(layerModel.name) (parent, masked)" + maskContainer.addSublayer(parentTransformLayer) + + // Core Animation will silently fail to apply a mask if a `mask` layer + // itself _also_ has a `mask`. As a workaround, we can wrap this layer's + // mask in an additional container layer which never has its own `mask`. + let additionalMaskParent = BaseAnimationLayer() + additionalMaskParent.addSublayer(maskParentTransformLayer) + maskContainer.mask = additionalMaskParent + + addSublayer(maskContainer) + } + + else { + addSublayer(parentTransformLayer) + } + } + } + +} + +extension Collection where Element == LayerModel { + /// Pairs each `LayerModel` within this array with + /// a `LayerModel` to use as its mask, if applicable + /// based on the layer's `MatteType` configuration. + /// - Assumes the layers are sorted in z-axis order. + fileprivate func pairedLayersAndMasks(context: LayerContext) throws -> [(layer: LayerModel, mask: LayerModel?)] { + var layersAndMasks = [(layer: LayerModel, mask: LayerModel?)]() + var unprocessedLayers = reversed() + + while let layer = unprocessedLayers.popLast() { + /// If a layer has a `MatteType`, then the next layer will be used as its `mask` + if + let matteType = layer.matte, + matteType != .none, + let maskLayer = unprocessedLayers.popLast() + { + try context.compatibilityAssert( + matteType == .add, + "The Core Animation rendering engine currently only supports `MatteMode.add`.") + + layersAndMasks.append((layer: layer, mask: maskLayer)) + } + + else { + layersAndMasks.append((layer: layer, mask: nil)) + } + } + + return layersAndMasks + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/GradientRenderLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/GradientRenderLayer.swift new file mode 100644 index 00000000000..0238811ba5a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/GradientRenderLayer.swift @@ -0,0 +1,87 @@ +// Created by Cal Stephens on 1/10/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - GradientRenderLayer + +/// A `CAGradientLayer` subclass used to render a gradient _outside_ the normal layer bounds +/// +/// - `GradientFill.startPoint` and `GradientFill.endPoint` are expressed +/// with respect to the `bounds` of the `ShapeItemLayer`. +/// +/// - The gradient itself is supposed to be rendered infinitely in all directions +/// (e.g. including outside of `bounds`). This is because `ShapeItemLayer` paths +/// don't necessarily sit within the layer's `bounds`. +/// +/// - To support this, `GradientRenderLayer` tracks a `gradientReferenceBounds` +/// that `startPoint` / `endPoint` are calculated relative to. +/// The _actual_ `bounds` of this layer is padded by a large amount so that +/// the gradient can be drawn outside of the `gradientReferenceBounds`. +/// +final class GradientRenderLayer: CAGradientLayer { + + // MARK: Internal + + /// The reference bounds within this layer that the gradient's + /// `startPoint` and `endPoint` should be calculated relative to + var gradientReferenceBounds: CGRect = .zero { + didSet { + if oldValue != gradientReferenceBounds { + updateLayout() + } + } + } + + /// Converts the given `CGPoint` within `gradientReferenceBounds` + /// to a percentage value relative to the full `bounds` of this layer + /// - This converts absolute `startPoint` and `endPoint` values into + /// the percent-based values expected by Core Animation, + /// with respect to the custom bounds geometry used by this layer type. + func percentBasedPointInBounds(from referencePoint: CGPoint) -> CGPoint { + guard bounds.width > 0, bounds.height > 0 else { + LottieLogger.shared.assertionFailure("Size must be non-zero before an animation can be played") + return .zero + } + + let pointInBounds = CGPoint( + x: referencePoint.x + gradientPadding, + y: referencePoint.y + gradientPadding) + + return CGPoint( + x: CGFloat(pointInBounds.x) / bounds.width, + y: CGFloat(pointInBounds.y) / bounds.height) + } + + // MARK: Private + + /// Extra padding around the `gradientReferenceBounds` where the gradient is also rendered + /// - This specific value is arbitrary and can be increased if necessary. + /// Theoretically this should be "infinite", to match the behavior of + /// `CGContext.drawLinearGradient` with `[.drawsAfterEndLocation, .drawsBeforeStartLocation]`. + private let gradientPadding: CGFloat = 2_000 + + private func updateLayout() { + anchorPoint = .zero + + bounds = CGRect( + x: gradientReferenceBounds.origin.x, + y: gradientReferenceBounds.origin.y, + width: gradientPadding + gradientReferenceBounds.width + gradientPadding, + height: gradientPadding + gradientReferenceBounds.height + gradientPadding) + + transform = CATransform3DMakeTranslation( + -gradientPadding, + -gradientPadding, + 0) + } + +} + +// MARK: CustomLayoutLayer + +extension GradientRenderLayer: CustomLayoutLayer { + func layout(superlayerBounds: CGRect) { + gradientReferenceBounds = superlayerBounds + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/ImageLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/ImageLayer.swift new file mode 100644 index 00000000000..98350f04544 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/ImageLayer.swift @@ -0,0 +1,79 @@ +// Created by Cal Stephens on 1/10/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - ImageLayer + +/// The `CALayer` type responsible for rendering `ImageLayerModel`s +final class ImageLayer: BaseCompositionLayer { + + // MARK: Lifecycle + + init( + imageLayer: ImageLayerModel, + context: LayerContext) + { + self.imageLayer = imageLayer + super.init(layerModel: imageLayer) + setupImage(context: context) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + imageLayer = typedLayer.imageLayer + super.init(layer: typedLayer) + } + + // MARK: Internal + + func setupImage(context: LayerContext) { + guard + let imageAsset = context.animation.assetLibrary?.imageAssets[imageLayer.referenceID], + let image = context.imageProvider.imageForAsset(asset: imageAsset) + else { + self.imageAsset = nil + contents = nil + return + } + + self.imageAsset = imageAsset + contents = image + setNeedsLayout() + } + + // MARK: Private + + private let imageLayer: ImageLayerModel + private var imageAsset: ImageAsset? + +} + +// MARK: CustomLayoutLayer + +extension ImageLayer: CustomLayoutLayer { + func layout(superlayerBounds: CGRect) { + anchorPoint = .zero + + guard let imageAsset = imageAsset else { + bounds = superlayerBounds + return + } + + // Image layers specifically need to use the size of the image itself + bounds = CGRect( + x: superlayerBounds.origin.x, + y: superlayerBounds.origin.y, + width: CGFloat(imageAsset.width), + height: CGFloat(imageAsset.height)) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift new file mode 100644 index 00000000000..7a2b47e15e8 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift @@ -0,0 +1,60 @@ +// Created by Cal Stephens on 12/20/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - LayerContext + +/// Context available when constructing an `AnimationLayer` +struct LayerContext { + let animation: Animation + let imageProvider: AnimationImageProvider + let fontProvider: AnimationFontProvider + let compatibilityTracker: CompatibilityTracker + var layerName: String + + func forLayer(_ layer: LayerModel) -> LayerContext { + var context = self + context.layerName = layer.name + return context + } +} + +// MARK: - LayerModel + makeAnimationLayer + +extension LayerModel { + /// Constructs an `AnimationLayer` / `CALayer` that represents this `LayerModel` + func makeAnimationLayer(context: LayerContext) throws -> BaseCompositionLayer? { + let context = context.forLayer(self) + + switch (type, self) { + case (.precomp, let preCompLayerModel as PreCompLayerModel): + let preCompLayer = PreCompLayer(preCompLayer: preCompLayerModel) + try preCompLayer.setup(context: context) + return preCompLayer + + case (.solid, let solidLayerModel as SolidLayerModel): + return SolidLayer(solidLayerModel) + + case (.shape, let shapeLayerModel as ShapeLayerModel): + return try ShapeLayer(shapeLayer: shapeLayerModel, context: context) + + case (.image, let imageLayerModel as ImageLayerModel): + return ImageLayer(imageLayer: imageLayerModel, context: context) + + case (.text, let textLayerModel as TextLayerModel): + return try TextLayer(textLayerModel: textLayerModel, context: context) + + case (.null, _): + return TransformLayer(layerModel: self) + + default: + try context.logCompatibilityIssue(""" + Unexpected layer type combination ("\(type)" and "\(Swift.type(of: self))") + """) + + return nil + } + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/MaskCompositionLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/MaskCompositionLayer.swift new file mode 100644 index 00000000000..8663bd5eae2 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/MaskCompositionLayer.swift @@ -0,0 +1,104 @@ +// Created by Cal Stephens on 1/6/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - MaskCompositionLayer + +/// The CALayer type responsible for rendering the `Mask` of a `BaseCompositionLayer` +final class MaskCompositionLayer: CALayer { + + // MARK: Lifecycle + + init(masks: [Mask]) { + maskLayers = masks.map(MaskLayer.init(mask:)) + super.init() + + for maskLayer in maskLayers { + addSublayer(maskLayer) + } + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + maskLayers = typedLayer.maskLayers + super.init(layer: typedLayer) + } + + // MARK: Internal + + override func layoutSublayers() { + super.layoutSublayers() + + for sublayer in sublayers ?? [] { + sublayer.fillBoundsOfSuperlayer() + } + } + + // MARK: Private + + private let maskLayers: [MaskLayer] + +} + +// MARK: AnimationLayer + +extension MaskCompositionLayer: AnimationLayer { + func setupAnimations(context: LayerAnimationContext) throws { + for maskLayer in maskLayers { + try maskLayer.setupAnimations(context: context) + } + } +} + +// MARK: - MaskLayer + +extension MaskCompositionLayer { + final class MaskLayer: CAShapeLayer { + + // MARK: Lifecycle + + init(mask: Mask) { + maskModel = mask + super.init() + fillColor = .rgb(0, 0, 0) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + maskModel = typedLayer.maskModel + super.init(layer: typedLayer) + } + + // MARK: Private + + private let maskModel: Mask + + } +} + +// MARK: - MaskCompositionLayer.MaskLayer + AnimationLayer + +extension MaskCompositionLayer.MaskLayer: AnimationLayer { + func setupAnimations(context: LayerAnimationContext) throws { + try addAnimations(for: maskModel.shape, context: context) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift new file mode 100644 index 00000000000..ee10d9f807a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift @@ -0,0 +1,140 @@ +// Created by Cal Stephens on 12/14/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - PreCompLayer + +/// The `CALayer` type responsible for rendering `PreCompLayerModel`s +final class PreCompLayer: BaseCompositionLayer { + + // MARK: Lifecycle + + init(preCompLayer: PreCompLayerModel) { + self.preCompLayer = preCompLayer + super.init(layerModel: preCompLayer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + preCompLayer = typedLayer.preCompLayer + timeRemappingInterpolator = typedLayer.timeRemappingInterpolator + super.init(layer: typedLayer) + } + + // MARK: Internal + + /// Post-init setup for `PreCompLayer`s. + /// Should always be called after `PreCompLayer.init(preCompLayer:)`. + /// + /// This is a workaround for a hard-to-reproduce crash that was + /// triggered when `PreCompLayer.init` was called reentantly. We didn't + /// have any consistent repro steps for this crash (it happened 100% of + /// the time for some testers, and 0% of the time for other testers), + /// but moving this code out of `PreCompLayer.init` does seem to fix it. + /// + /// The stack trace looked like: + /// - `_os_unfair_lock_recursive_abort` + /// - `-[CALayerAccessibility__UIKit__QuartzCore dealloc]` + /// - `PreCompLayer.__allocating_init(preCompLayer:context:)` <- reentrant init call + /// - ... + /// - `CALayer.setupLayerHierarchy(for:context:)` + /// - `PreCompLayer.init(preCompLayer:context:)` + /// + func setup(context: LayerContext) throws { + if let timeRemappingKeyframes = preCompLayer.timeRemapping { + timeRemappingInterpolator = try .timeRemapping(keyframes: timeRemappingKeyframes, context: context) + } else { + timeRemappingInterpolator = nil + } + + try setupLayerHierarchy( + for: context.animation.assetLibrary?.precompAssets[preCompLayer.referenceID]?.layers ?? [], + context: context) + } + + override func setupAnimations(context: LayerAnimationContext) throws { + var context = context + context = context.addingKeypathComponent(preCompLayer.name) + try setupLayerAnimations(context: context) + + // Precomp layers can adjust the local time of their child layers (relative to the + // animation's global time) via `timeRemapping` or a custom `startTime` + let contextForChildren = context.withTimeRemapping { [preCompLayer, timeRemappingInterpolator] layerLocalFrame in + if let timeRemappingInterpolator = timeRemappingInterpolator { + return timeRemappingInterpolator.value(frame: layerLocalFrame) as? AnimationFrameTime ?? layerLocalFrame + } else { + return layerLocalFrame + AnimationFrameTime(preCompLayer.startTime) + } + } + + try setupChildAnimations(context: contextForChildren) + } + + // MARK: Private + + private let preCompLayer: PreCompLayerModel + private var timeRemappingInterpolator: KeyframeInterpolator? + +} + +// MARK: CustomLayoutLayer + +extension PreCompLayer: CustomLayoutLayer { + func layout(superlayerBounds: CGRect) { + anchorPoint = .zero + + // Pre-comp layers use a size specified in the layer model, + // and clip the composition to that bounds + bounds = CGRect( + x: superlayerBounds.origin.x, + y: superlayerBounds.origin.y, + width: CGFloat(preCompLayer.width), + height: CGFloat(preCompLayer.height)) + + masksToBounds = true + } +} + +extension KeyframeInterpolator where ValueType == AnimationFrameTime { + /// A `KeyframeInterpolator` for the given `timeRemapping` keyframes + static func timeRemapping( + keyframes timeRemappingKeyframes: KeyframeGroup, + context: LayerContext) + throws + -> KeyframeInterpolator + { + try context.logCompatibilityIssue(""" + The Core Animation rendering engine partially supports time remapping keyframes, + but this is somewhat experimental and has some known issues. Since it doesn't work + in all cases, we have to fall back to using the main thread engine when using + `RenderingEngineOption.automatic`. + """) + + // `timeRemapping` is a mapping from the animation's global time to the layer's local time. + // In the Core Animation engine, we need to perform the opposite calculation -- convert + // the layer's local time into the animation's global time. We can get this by inverting + // the time remapping, swapping the x axis (global time) and the y axis (local time). + let localTimeToGlobalTimeMapping = timeRemappingKeyframes.keyframes.map { keyframe in + Keyframe( + value: keyframe.time, + time: keyframe.value.cgFloatValue * CGFloat(context.animation.framerate), + isHold: keyframe.isHold, + inTangent: keyframe.inTangent, + outTangent: keyframe.outTangent, + spatialInTangent: keyframe.spatialInTangent, + spatialOutTangent: keyframe.spatialOutTangent) + } + + return KeyframeInterpolator(keyframes: .init(localTimeToGlobalTimeMapping)) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift new file mode 100644 index 00000000000..9c3d54415e0 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift @@ -0,0 +1,257 @@ +// Created by Cal Stephens on 12/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - ShapeItemLayer + +/// A CALayer type that renders an array of `[ShapeItem]`s, +/// from a `Group` in a `ShapeLayerModel`. +final class ShapeItemLayer: BaseAnimationLayer { + + // MARK: Lifecycle + + /// Initializes a `ShapeItemLayer` that renders a `Group` from a `ShapeLayerModel` + /// - Parameters: + /// - shape: The `ShapeItem` in this group that renders a `GGPath` + /// - otherItems: Other items in this group that affect the appearance of the shape + init(shape: Item, otherItems: [Item], context: LayerContext) throws { + self.shape = shape + self.otherItems = otherItems + + try context.compatibilityAssert( + shape.item.drawsCGPath, + "`ShapeItemLayer` must contain exactly one `ShapeItem` that draws a `GPPath`") + + try context.compatibilityAssert( + !otherItems.contains(where: { $0.item.drawsCGPath }), + "`ShapeItemLayer` must contain exactly one `ShapeItem` that draws a `GPPath`") + + super.init() + + setupLayerHierarchy() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + shape = typedLayer.shape + otherItems = typedLayer.otherItems + super.init(layer: typedLayer) + } + + // MARK: Internal + + /// An item that can be displayed by this layer + struct Item { + /// A `ShapeItem` that should be rendered by this layer + let item: ShapeItem + + /// The group that contains this `ShapeItem`, if applicable + let parentGroup: Group? + } + + override func setupAnimations(context: LayerAnimationContext) throws { + try super.setupAnimations(context: context) + + guard let sublayerConfiguration = sublayerConfiguration else { return } + + switch sublayerConfiguration.fill { + case .solidFill(let shapeLayer): + try setupSolidFillAnimations(shapeLayer: shapeLayer, context: context) + + case .gradientFill(let gradientLayers): + try setupGradientFillAnimations( + gradientLayer: gradientLayers.gradientLayer, + maskLayer: gradientLayers.maskLayer, + context: context) + } + + if let gradientStrokeConfiguration = sublayerConfiguration.gradientStroke { + try setupGradientStrokeAnimations( + gradientLayer: gradientStrokeConfiguration.gradientLayer, + maskLayer: gradientStrokeConfiguration.maskLayer, + context: context) + } + } + + // MARK: Private + + private struct GradientLayers { + /// The `CALayer` that renders the actual gradient + let gradientLayer: GradientRenderLayer + /// The `CAShapeLayer` that clips the gradient layer to the expected shape + let maskLayer: CAShapeLayer + } + + /// The configuration of this layer's `fill` sublayers + private enum FillLayerConfiguration { + /// This layer displays a single `CAShapeLayer` + case solidFill(CAShapeLayer) + + /// This layer displays a `GradientRenderLayer` masked by a `CAShapeLayer`. + case gradientFill(GradientLayers) + } + + /// The `ShapeItem` in this group that renders a `GGPath` + private let shape: Item + + /// Other items in this group that affect the appearance of the shape + private let otherItems: [Item] + + /// The current configuration of this layer's sublayer(s) + private var sublayerConfiguration: (fill: FillLayerConfiguration, gradientStroke: GradientLayers?)? + + private func setupLayerHierarchy() { + // We have to build a different layer hierarchy depending on if + // we're rendering a gradient (a `CAGradientLayer` masked by a `CAShapeLayer`) + // or a solid shape (a simple `CAShapeLayer`). + let fillLayerConfiguration: FillLayerConfiguration + if otherItems.contains(where: { $0.item is GradientFill }) { + fillLayerConfiguration = setupGradientFillLayerHierarchy() + } else { + fillLayerConfiguration = setupSolidFillLayerHierarchy() + } + + let gradientStrokeConfiguration: GradientLayers? + if otherItems.contains(where: { $0.item is GradientStroke }) { + gradientStrokeConfiguration = setupGradientStrokeLayerHierarchy() + } else { + gradientStrokeConfiguration = nil + } + + sublayerConfiguration = (fillLayerConfiguration, gradientStrokeConfiguration) + } + + private func setupSolidFillLayerHierarchy() -> FillLayerConfiguration { + let shapeLayer = LottieCAShapeLayer() + addSublayer(shapeLayer) + + // `CAShapeLayer.fillColor` defaults to black, so we have to + // nil out the background color if there isn't an expected fill color + if !otherItems.contains(where: { $0.item is Fill }) { + shapeLayer.fillColor = nil + } + + return .solidFill(shapeLayer) + } + + private func setupGradientFillLayerHierarchy() -> FillLayerConfiguration { + let pathMask = LottieCAShapeLayer() + pathMask.fillColor = .rgb(0, 0, 0) + mask = pathMask + + let gradientLayer = GradientRenderLayer() + addSublayer(gradientLayer) + + return .gradientFill(.init(gradientLayer: gradientLayer, maskLayer: pathMask)) + } + + private func setupGradientStrokeLayerHierarchy() -> GradientLayers { + let container = BaseAnimationLayer() + + let pathMask = LottieCAShapeLayer() + pathMask.fillColor = nil + pathMask.strokeColor = .rgb(0, 0, 0) + container.mask = pathMask + + let gradientLayer = GradientRenderLayer() + container.addSublayer(gradientLayer) + addSublayer(container) + + return .init(gradientLayer: gradientLayer, maskLayer: pathMask) + } + + private func setupSolidFillAnimations( + shapeLayer: CAShapeLayer, + context: LayerAnimationContext) + throws + { + try shapeLayer.addAnimations(for: shape.item, context: context.for(shape)) + + if let (fill, context) = otherItems.first(Fill.self, context: context) { + try shapeLayer.addAnimations(for: fill, context: context) + } + + if let (stroke, context) = otherItems.first(Stroke.self, context: context) { + try shapeLayer.addStrokeAnimations(for: stroke, context: context) + } + + if let (trim, context) = otherItems.first(Trim.self, context: context) { + try shapeLayer.addAnimations(for: trim, context: context) + } + } + + private func setupGradientFillAnimations( + gradientLayer: GradientRenderLayer, + maskLayer: CAShapeLayer, + context: LayerAnimationContext) + throws + { + try maskLayer.addAnimations(for: shape.item, context: context.for(shape)) + + if let (gradientFill, context) = otherItems.first(GradientFill.self, context: context) { + try gradientLayer.addGradientAnimations(for: gradientFill, context: context) + } + } + + private func setupGradientStrokeAnimations( + gradientLayer: GradientRenderLayer, + maskLayer: CAShapeLayer, + context: LayerAnimationContext) + throws + { + try maskLayer.addAnimations(for: shape.item, context: context.for(shape)) + + if let (gradientStroke, context) = otherItems.first(GradientStroke.self, context: context) { + try gradientLayer.addGradientAnimations(for: gradientStroke, context: context) + try maskLayer.addStrokeAnimations(for: gradientStroke, context: context) + } + + if let (trim, context) = otherItems.first(Trim.self, context: context) { + try maskLayer.addAnimations(for: trim, context: context) + } + } + +} + +// MARK: - [ShapeItem] helpers + +extension Array where Element == ShapeItemLayer.Item { + /// The first `ShapeItem` in this array of the given type + func first( + _: ItemType.Type, context: LayerAnimationContext) + -> (item: ItemType, context: LayerAnimationContext)? + { + for item in self { + if let match = item.item as? ItemType { + return (match, context.for(item)) + } + } + + return nil + } +} + +extension LayerAnimationContext { + /// An updated `LayerAnimationContext` with the`AnimationKeypath` + /// that refers to this specific `ShapeItem`. + func `for`(_ item: ShapeItemLayer.Item) -> LayerAnimationContext { + var context = self + + if let group = item.parentGroup { + context.currentKeypath.keys.append(group.name) + } + + context.currentKeypath.keys.append(item.item.name) + return context + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift new file mode 100644 index 00000000000..a997ea85cc2 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift @@ -0,0 +1,305 @@ +// Created by Cal Stephens on 12/14/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - ShapeLayer + +/// The CALayer type responsible for rendering `ShapeLayerModel`s +final class ShapeLayer: BaseCompositionLayer { + + // MARK: Lifecycle + + init(shapeLayer: ShapeLayerModel, context: LayerContext) throws { + self.shapeLayer = shapeLayer + super.init(layerModel: shapeLayer) + try setupGroups(from: shapeLayer.items, parentGroup: nil, context: context) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + shapeLayer = typedLayer.shapeLayer + super.init(layer: typedLayer) + } + + // MARK: Private + + private let shapeLayer: ShapeLayerModel + +} + +// MARK: - GroupLayer + +/// The CALayer type responsible for rendering `Group`s +final class GroupLayer: BaseAnimationLayer { + + // MARK: Lifecycle + + init(group: Group, inheritedItems: [ShapeItemLayer.Item], context: LayerContext) throws { + self.group = group + self.inheritedItems = inheritedItems + super.init() + try setupLayerHierarchy(context: context) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + group = typedLayer.group + inheritedItems = typedLayer.inheritedItems + super.init(layer: typedLayer) + } + + // MARK: Internal + + override func setupAnimations(context: LayerAnimationContext) throws { + try super.setupAnimations(context: context) + + if let (shapeTransform, context) = nonGroupItems.first(ShapeTransform.self, context: context) { + try addTransformAnimations(for: shapeTransform, context: context) + try addOpacityAnimation(for: shapeTransform, context: context) + } + } + + // MARK: Private + + private let group: Group + + /// `ShapeItem`s that were listed in the parent's `items: [ShapeItem]` array + /// - This layer's parent is either the root `ShapeLayerModel` or some other `Group` + private let inheritedItems: [ShapeItemLayer.Item] + + /// `ShapeItem`s (other than nested `Group`s) that are included in this group + private lazy var nonGroupItems = group.items + .filter { !($0 is Group) } + .map { ShapeItemLayer.Item(item: $0, parentGroup: group) } + + inheritedItems + + private func setupLayerHierarchy(context: LayerContext) throws { + // Groups can contain other groups, so we may have to continue + // recursively creating more `GroupLayer`s + try setupGroups(from: group.items, parentGroup: group, context: context) + + // Create `ShapeItemLayer`s for each subgroup of shapes that should be rendered as a single unit + // - These groups are listed from front-to-back, so we have to add the sublayers in reverse order + for shapeRenderGroup in nonGroupItems.shapeRenderGroups.reversed() { + // If all of the path-drawing `ShapeItem`s have keyframes with the same timing information, + // we can combine the `[KeyframeGroup]` (which have to animate in separate layers) + // into a single `KeyframeGroup<[BezierPath]>`, which can be combined into a single CGPath animation. + // + // This is how Groups with multiple path-drawing items are supposed to be rendered, + // because combining multiple paths into a single `CGPath` (instead of rendering them in separate layers) + // allows `CAShapeLayerFillRule.evenOdd` to be applied if the paths overlap. We just can't do this + // in all cases, due to limitations of Core Animation. + if + shapeRenderGroup.pathItems.count > 1, + let combinedShapeKeyframes = Keyframes.combinedIfPossible( + shapeRenderGroup.pathItems.map { ($0.item as? Shape)?.path }), + // `Trim`s are currently only applied correctly using individual `ShapeItemLayer`s, + // because each path has to be trimmed separately. + !shapeRenderGroup.otherItems.contains(where: { $0.item is Trim }) + { + let combinedShape = CombinedShapeItem( + shapes: combinedShapeKeyframes, + name: group.name) + + let sublayer = try ShapeItemLayer( + shape: ShapeItemLayer.Item(item: combinedShape, parentGroup: group), + otherItems: shapeRenderGroup.otherItems, + context: context) + + addSublayer(sublayer) + } + + // Otherwise, if each `ShapeItem` that draws a `GGPath` animates independently, + // we have to create a separate `ShapeItemLayer` for each one. + else { + for pathDrawingItem in shapeRenderGroup.pathItems { + let sublayer = try ShapeItemLayer( + shape: pathDrawingItem, + otherItems: shapeRenderGroup.otherItems, + context: context) + + addSublayer(sublayer) + } + } + } + } + +} + +extension CALayer { + /// Sets up `GroupLayer`s for each `Group` in the given list of `ShapeItem`s + /// - Each `Group` item becomes its own `GroupLayer` sublayer. + /// - Other `ShapeItem` are applied to all sublayers + fileprivate func setupGroups(from items: [ShapeItem], parentGroup: Group?, context: LayerContext) throws { + let (groupItems, otherItems) = items.grouped(by: { $0 is Group }) + + // Groups are listed from front to back, + // but `CALayer.sublayers` are listed from back to front. + let groupsInZAxisOrder = groupItems.reversed() + + for group in groupsInZAxisOrder { + guard let group = group as? Group else { continue } + + // `ShapeItem`s either draw a path, or modify how a path is rendered. + // - If this group doesn't have any items that draw a path, then its + // items are applied to all of this groups children. + let inheritedItems: [ShapeItemLayer.Item] + if !otherItems.contains(where: { $0.drawsCGPath }) { + inheritedItems = otherItems.map { + ShapeItemLayer.Item(item: $0, parentGroup: parentGroup) + } + } else { + inheritedItems = [] + } + + let groupLayer = try GroupLayer( + group: group, + inheritedItems: inheritedItems, + context: context) + + addSublayer(groupLayer) + } + } +} + +extension ShapeItem { + /// Whether or not this `ShapeItem` is responsible for rendering a `CGPath` + var drawsCGPath: Bool { + switch type { + case .ellipse, .rectangle, .shape, .star: + return true + + case .fill, .gradientFill, .group, .gradientStroke, .merge, + .repeater, .round, .stroke, .trim, .transform, .unknown: + return false + } + } + + /// Whether or not this `ShapeItem` provides a fill for a set of shapes + var isFill: Bool { + switch type { + case .fill, .gradientFill: + return true + + case .ellipse, .rectangle, .shape, .star, .group, .gradientStroke, + .merge, .repeater, .round, .stroke, .trim, .transform, .unknown: + return false + } + } + + /// Whether or not this `ShapeItem` provides a stroke for a set of shapes + var isStroke: Bool { + switch type { + case .stroke, .gradientStroke: + return true + + case .ellipse, .rectangle, .shape, .star, .group, .gradientFill, + .merge, .repeater, .round, .fill, .trim, .transform, .unknown: + return false + } + } +} + +extension Collection { + /// Splits this collection into two groups, based on the given predicate + func grouped(by predicate: (Element) -> Bool) -> (trueElements: [Element], falseElements: [Element]) { + var trueElements = [Element]() + var falseElements = [Element]() + + for element in self { + if predicate(element) { + trueElements.append(element) + } else { + falseElements.append(element) + } + } + + return (trueElements, falseElements) + } +} + +// MARK: - ShapeRenderGroup + +/// A group of `ShapeItem`s that should be rendered together as a single unit +struct ShapeRenderGroup { + /// The items in this group that render `CGPath`s + var pathItems: [ShapeItemLayer.Item] = [] + /// Shape items that modify the appearance of the shapes rendered by this group + var otherItems: [ShapeItemLayer.Item] = [] +} + +extension Array where Element == ShapeItemLayer.Item { + /// Splits this list of `ShapeItem`s into groups that should be rendered together as individual units + var shapeRenderGroups: [ShapeRenderGroup] { + var renderGroups = [ShapeRenderGroup()] + + for item in self { + // `renderGroups` is non-empty, so is guaranteed to have a valid end index + let lastIndex = renderGroups.indices.last! + + if item.item.drawsCGPath { + renderGroups[lastIndex].pathItems.append(item) + } + + // `Fill` items are unique, because they specifically only apply to _previous_ shapes in a `Group` + // - For example, with [Rectangle, Fill(Red), Circle, Fill(Blue)], the Rectangle should be Red + // but the Circle should be Blue. + // - To handle this, we create a new `ShapeRenderGroup` when we encounter a `Fill` item + else if item.item.isFill { + renderGroups[lastIndex].otherItems.append(item) + renderGroups.append(ShapeRenderGroup()) + } + + // Other items in the list are applied to all subgroups + else { + for index in renderGroups.indices { + renderGroups[index].otherItems.append(item) + } + } + } + + // `Fill` and `Stroke` items have an `alpha` property that can be animated separately, + // but each layer only has a single `opacity` property, so we have to create + // separate layers / render groups for each of these if necessary. + return renderGroups.flatMap { group -> [ShapeRenderGroup] in + let (strokesAndFills, otherItems) = group.otherItems.grouped(by: { $0.item.isFill || $0.item.isStroke }) + + // However, if all of the strokes / fills have the exact same opacity animation configuration, + // then we can continue using a single layer / render group. + let allAlphaAnimationsAreIdentical = strokesAndFills.allSatisfy { item in + (item.item as? OpacityAnimationModel)?.opacity + == (strokesAndFills.first?.item as? OpacityAnimationModel)?.opacity + } + + if allAlphaAnimationsAreIdentical { + return [group] + } + + // Create a new group for each stroke / fill + return strokesAndFills.map { strokeOrFill in + ShapeRenderGroup( + pathItems: group.pathItems, + otherItems: [strokeOrFill] + otherItems) + } + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/SolidLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/SolidLayer.swift new file mode 100644 index 00000000000..c2be2550e8a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/SolidLayer.swift @@ -0,0 +1,47 @@ +// Created by Cal Stephens on 12/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - SolidLayer + +final class SolidLayer: BaseCompositionLayer { + + // MARK: Lifecycle + + init(_ solidLayer: SolidLayerModel) { + self.solidLayer = solidLayer + super.init(layerModel: solidLayer) + setupContentLayer() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + solidLayer = typedLayer.solidLayer + super.init(layer: typedLayer) + } + + // MARK: Private + + private let solidLayer: SolidLayerModel + + private func setupContentLayer() { + // Render the fill color in a child `CAShapeLayer` + // - Using a `CAShapeLayer` specifically, instead of a `CALayer` with a `backgroundColor`, + // allows the size of the fill shape to be different from `contentsLayer.size`. + let shapeLayer = LottieCAShapeLayer() + shapeLayer.fillColor = solidLayer.colorHex.cgColor + shapeLayer.path = CGPath(rect: .init(x: 0, y: 0, width: solidLayer.width, height: solidLayer.height), transform: nil) + addSublayer(shapeLayer) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/TextLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/TextLayer.swift new file mode 100644 index 00000000000..28fecbbb45e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/TextLayer.swift @@ -0,0 +1,91 @@ +// Created by Cal Stephens on 2/9/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +/// The `CALayer` type responsible for rendering `TextLayer`s +final class TextLayer: BaseCompositionLayer { + + // MARK: Lifecycle + + init( + textLayerModel: TextLayerModel, + context: LayerContext) + throws + { + self.textLayerModel = textLayerModel + super.init(layerModel: textLayerModel) + setupSublayers() + try configureRenderLayer(with: context) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + textLayerModel = typedLayer.textLayerModel + super.init(layer: typedLayer) + } + + // MARK: Internal + + func configureRenderLayer(with context: LayerContext) throws { + // We can't use `CATextLayer`, because it doesn't support enough features we use. + // Instead, we use the same `CoreTextRenderLayer` (with a custom `draw` implementation) + // used by the Main Thread rendering engine. This means the Core Animation engine can't + // _animate_ text properties, but it can display static text without any issues. + let text = try textLayerModel.text.exactlyOneKeyframe(context: context, description: "text layer text").value + + // The Core Animation engine doesn't currently support `TextAnimator`s. + // - We could add support for animating the transform-related properties without much trouble. + // - We may be able to support animating `fillColor` by getting clever with layer blend modes + // or masks (e.g. use `CoreTextRenderLayer` to draw black glyphs, and then fill them in + // using a `CAShapeLayer`). + if !textLayerModel.animators.isEmpty { + try context.logCompatibilityIssue(""" + The Core Animation rendering engine currently doesn't support text animators. + """) + } + + renderLayer.text = text.text + renderLayer.font = context.fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize)) + + renderLayer.alignment = text.justification.textAlignment + renderLayer.lineHeight = CGFloat(text.lineHeight) + renderLayer.tracking = (CGFloat(text.fontSize) * CGFloat(text.tracking)) / 1000 + + renderLayer.fillColor = text.fillColorData?.cgColorValue + renderLayer.strokeColor = text.strokeColorData?.cgColorValue + renderLayer.strokeWidth = CGFloat(text.strokeWidth ?? 0) + renderLayer.strokeOnTop = text.strokeOverFill ?? false + + renderLayer.preferredSize = text.textFrameSize?.sizeValue + renderLayer.sizeToFit() + + renderLayer.transform = CATransform3DIdentity + renderLayer.position = text.textFramePosition?.pointValue ?? .zero + } + + // MARK: Private + + private let textLayerModel: TextLayerModel + private let renderLayer = CoreTextRenderLayer() + + private func setupSublayers() { + // Place the text render layer in an additional container + // - Direct sublayers of a `BaseCompositionLayer` always fill the bounds + // of their superlayer -- so this container will be the bounds of self, + // and the text render layer can be positioned anywhere. + let textContainerLayer = CALayer() + textContainerLayer.addSublayer(renderLayer) + addSublayer(textContainerLayer) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/TransformLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/TransformLayer.swift new file mode 100644 index 00000000000..027739a4473 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/Layers/TransformLayer.swift @@ -0,0 +1,11 @@ +// Created by Cal Stephens on 12/21/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +/// The CALayer type responsible for only rendering the `transform` of a `LayerModel` +final class TransformLayer: BaseCompositionLayer { + + /// `TransformLayer`s don't render any visible content, + /// they just `transform` their sublayers + override var renderLayerContents: Bool { false } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/ValueProviderStore.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/ValueProviderStore.swift new file mode 100644 index 00000000000..a3d4aecf90b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/CoreAnimation/ValueProviderStore.swift @@ -0,0 +1,127 @@ +// Created by Cal Stephens on 1/13/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - ValueProviderStore + +/// Registration and storage for `AnyValueProvider`s that can dynamically +/// provide custom values for `AnimationKeypath`s within an `Animation`. +final class ValueProviderStore { + + // MARK: Internal + + /// Registers an `AnyValueProvider` for the given `AnimationKeypath` + func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + LottieLogger.shared.assert( + valueProvider.typeErasedStorage.isSupportedByCoreAnimationRenderingEngine, + """ + The Core Animation rendering engine doesn't support Value Providers that vend a closure, + because that would require calling the closure on the main thread once per frame. + """) + + // TODO: Support more value types + LottieLogger.shared.assert( + keypath.keys.last == PropertyName.color.rawValue, + "The Core Animation rendering engine currently only supports customizing color values") + + valueProviders.append((keypath: keypath, valueProvider: valueProvider)) + } + + // Retrieves the custom value keyframes for the given property, + // if an `AnyValueProvider` was registered for the given keypath. + func customKeyframes( + of customizableProperty: CustomizableProperty, + for keypath: AnimationKeypath, + context: LayerAnimationContext) + throws + -> KeyframeGroup? + { + guard let anyValueProvider = valueProvider(for: keypath) else { + return nil + } + + // Retrieve the type-erased keyframes from the custom `ValueProvider` + let typeErasedKeyframes: [Keyframe] + switch anyValueProvider.typeErasedStorage { + case .singleValue(let typeErasedValue): + typeErasedKeyframes = [Keyframe(typeErasedValue)] + + case .keyframes(let keyframes, _): + typeErasedKeyframes = keyframes + + case .closure: + try context.logCompatibilityIssue(""" + The Core Animation rendering engine doesn't support Value Providers that vend a closure, + because that would require calling the closure on the main thread once per frame. + """) + return nil + } + + // Convert the type-erased keyframe values using this `CustomizableProperty`'s conversion closure + let typedKeyframes = typeErasedKeyframes.compactMap { typeErasedKeyframe -> Keyframe? in + guard let convertedValue = customizableProperty.conversion(typeErasedKeyframe.value) else { + LottieLogger.shared.assertionFailure(""" + Could not convert value of type \(type(of: typeErasedKeyframe.value)) to expected type \(Value.self) + """) + return nil + } + + return typeErasedKeyframe.withValue(convertedValue) + } + + // Verify that all of the keyframes were successfully converted to the expected type + guard typedKeyframes.count == typeErasedKeyframes.count else { + return nil + } + + return KeyframeGroup(keyframes: ContiguousArray(typedKeyframes)) + } + + // MARK: Private + + private var valueProviders = [(keypath: AnimationKeypath, valueProvider: AnyValueProvider)]() + + /// Retrieves the most-recently-registered Value Provider that matches the given keypat + private func valueProvider(for keypath: AnimationKeypath) -> AnyValueProvider? { + // Find the last keypath matching the given keypath, + // so we return the value provider that was registered most-recently + valueProviders.last(where: { registeredKeypath, _ in + keypath.matches(registeredKeypath) + })?.valueProvider + } + +} + +extension AnyValueProviderStorage { + /// Whether or not this type of value provider is supported + /// by the new Core Animation rendering engine + var isSupportedByCoreAnimationRenderingEngine: Bool { + switch self { + case .singleValue, .keyframes: + return true + case .closure: + return false + } + } +} + +extension AnimationKeypath { + /// Whether or not this keypath from the animation hierarchy + /// matches the given keypath (which may contain wildcards) + func matches(_ keypath: AnimationKeypath) -> Bool { + var regex = "^" // match the start of the string + + keypath.keys.joined(separator: "\\.") // match this keypath, escaping "." characters + + "$" // match the end of the string + + // ** wildcards match anything + // - "**.Color" matches both "Layer 1.Color" and "Layer 1.Layer 2.Color" + regex = regex.replacingOccurrences(of: "**", with: ".+") + + // * wildcards match any individual path component + // - "*.Color" matches "Layer 1.Color" but not "Layer 1.Layer 2.Color" + regex = regex.replacingOccurrences(of: "*", with: "[^.]+") + + return fullPath.range(of: regex, options: .regularExpression) != nil + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp new file mode 100644 index 00000000000..e4fca1e328d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp @@ -0,0 +1,29 @@ +#include "CompositionLayer.hpp" + +#include "Lottie/Public/Primitives/RenderTree.hpp" + +namespace lottie { + +InvertedMatteLayer::InvertedMatteLayer(std::shared_ptr inputMatte) : +_inputMatte(inputMatte) { + setBounds(inputMatte->bounds()); + setNeedsDisplay(true); + + addSublayer(_inputMatte); +} + +void InvertedMatteLayer::setup() { + _inputMatte->setLayerDelegate(shared_from_base()); +} + +void InvertedMatteLayer::frameUpdated(double frame) { + setNeedsDisplay(true); +} + +std::shared_ptr makeInvertedMatteLayer(std::shared_ptr compositionLayer) { + auto result = std::make_shared(compositionLayer); + result->setup(); + return result; +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp new file mode 100644 index 00000000000..df28991e4e7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp @@ -0,0 +1,217 @@ +#ifndef CompositionLayer_hpp +#define CompositionLayer_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayerDelegate.hpp" + +#include + +namespace lottie { + +class CompositionLayer; +class InvertedMatteLayer; + +/// A layer that inverses the alpha output of its input layer. +class InvertedMatteLayer: public CALayer, public CompositionLayerDelegate { +public: + InvertedMatteLayer(std::shared_ptr inputMatte); + + void setup(); + + std::shared_ptr _inputMatte; + //let wrapperLayer = CALayer() + + virtual void frameUpdated(double frame) override; + /*virtual bool implementsDraw() const override; + virtual void draw(std::shared_ptr const &context) override;*/ + //virtual std::shared_ptr renderableItem() override; + + virtual bool isInvertedMatte() const override { + return true; + } +}; + +std::shared_ptr makeInvertedMatteLayer(std::shared_ptr compositionLayer); + +/// The base class for a child layer of CompositionContainer +class CompositionLayer: public CALayer, public KeypathSearchable { +public: + CompositionLayer(std::shared_ptr const &layer, Vector2D size) { + _contentsLayer = std::make_shared(); + + _transformNode = std::make_shared(layer->transform); + + if (layer->masks.has_value()) { + _maskLayer = std::make_shared(layer->masks.value()); + } else { + _maskLayer = nullptr; + } + + _matteType = layer->matte; + + _inFrame = layer->inFrame; + _outFrame = layer->outFrame; + _timeStretch = layer->timeStretch(); + _startFrame = layer->startTime; + if (layer->name.has_value()) { + _keypathName = layer->name.value(); + } else { + _keypathName = "Layer"; + } + + _childKeypaths.push_back(_transformNode->transformProperties()); + + _contentsLayer->setBounds(CGRect(0.0, 0.0, size.x, size.y)); + + if (layer->blendMode.has_value() && layer->blendMode.value() != BlendMode::Normal) { + setCompositingFilter(layer->blendMode); + } + + addSublayer(_contentsLayer); + + if (_maskLayer) { + _contentsLayer->setMask(_maskLayer); + } + } + + virtual std::string keypathName() const override { + return _keypathName; + } + + virtual std::map> keypathProperties() const override { + return {}; + } + + virtual std::shared_ptr keypathLayer() const override { + return _contentsLayer; + } + + void displayWithFrame(double frame, bool forceUpdates) { + _transformNode->updateTree(frame, forceUpdates); + bool layerVisible = isInRangeOrEqual(frame, _inFrame, _outFrame); + /// Only update contents if current time is within the layers time bounds. + if (layerVisible) { + displayContentsWithFrame(frame, forceUpdates); + if (_maskLayer) { + _maskLayer->updateWithFrame(frame, forceUpdates); + } + } + _contentsLayer->setTransform(_transformNode->globalTransform()); + _contentsLayer->setOpacity(_transformNode->opacity()); + _contentsLayer->setIsHidden(!layerVisible); + + if (const auto delegate = _layerDelegate.lock()) { + delegate->frameUpdated(frame); + } + } + + virtual void displayContentsWithFrame(double frame, bool forceUpdates) { + /// To be overridden by subclass + } + + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + std::shared_ptr _matteLayer; + void setMatteLayer(std::shared_ptr matteLayer) { + _matteLayer = matteLayer; + if (matteLayer) { + if (_matteType.has_value() && _matteType.value() == MatteType::Invert) { + setMask(makeInvertedMatteLayer(matteLayer)); + } else { + setMask(matteLayer); + } + } else { + setMask(nullptr); + } + } + + std::weak_ptr const &layerDelegate() const { + return _layerDelegate; + } + void setLayerDelegate(std::weak_ptr const &layerDelegate) { + _layerDelegate = layerDelegate; + } + + std::shared_ptr const &contentsLayer() const { + return _contentsLayer; + } + + std::shared_ptr const &maskLayer() const { + return _maskLayer; + } + void setMaskLayer(std::shared_ptr const &maskLayer) { + _maskLayer = maskLayer; + } + + std::optional const &matteType() const { + return _matteType; + } + + double inFrame() const { + return _inFrame; + } + double outFrame() const { + return _outFrame; + } + double startFrame() const { + return _startFrame; + } + double timeStretch() const { + return _timeStretch; + } + + virtual std::shared_ptr renderTreeNode() { + return nullptr; + } + +public: + std::shared_ptr const transformNode() const { + return _transformNode; + } + +protected: + std::shared_ptr _contentsLayer; + std::optional _matteType; + +private: + std::weak_ptr _layerDelegate; + + std::shared_ptr _transformNode; + + std::shared_ptr _maskLayer; + + double _inFrame = 0.0; + double _outFrame = 0.0; + double _startFrame = 0.0; + double _timeStretch = 0.0; + + // MARK: Keypath Searchable + + std::string _keypathName; + + //std::shared_ptr _renderTree; + +public: + virtual bool isImageCompositionLayer() const { + return false; + } + + virtual bool isTextCompositionLayer() const { + return false; + } + +protected: + std::vector> _childKeypaths; +}; + +} + +#endif /* CompositionLayer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.swift new file mode 100644 index 00000000000..0c647bf9075 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.swift @@ -0,0 +1,164 @@ +// +// LayerContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation +import QuartzCore + +protocol LottieDrawingLayer: CALayer { +} + +// MARK: - CompositionLayer + +/// The base class for a child layer of CompositionContainer +class CompositionLayer: CALayer, KeypathSearchable { + + // MARK: Lifecycle + + init(layer: LayerModel, size: CGSize) { + transformNode = LayerTransformNode(transform: layer.transform) + if let masks = layer.masks { + maskLayer = MaskContainerLayer(masks: masks) + } else { + maskLayer = nil + } + matteType = layer.matte + inFrame = layer.inFrame.cgFloat + outFrame = layer.outFrame.cgFloat + timeStretch = layer.timeStretch.cgFloat + startFrame = layer.startTime.cgFloat + keypathName = layer.name + childKeypaths = [transformNode.transformProperties] + super.init() + anchorPoint = .zero + actions = [ + "opacity" : NSNull(), + "transform" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "sublayerTransform" : NSNull(), + ] + + contentsLayer.anchorPoint = .zero + contentsLayer.bounds = CGRect(origin: .zero, size: size) + contentsLayer.actions = [ + "opacity" : NSNull(), + "transform" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "sublayerTransform" : NSNull(), + "hidden" : NSNull(), + ] + compositingFilter = layer.blendMode.filterName + addSublayer(contentsLayer) + + if let maskLayer = maskLayer { + contentsLayer.mask = maskLayer + } + + name = layer.name + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? CompositionLayer else { + fatalError("Wrong Layer Class") + } + transformNode = layer.transformNode + matteType = layer.matteType + inFrame = layer.inFrame + outFrame = layer.outFrame + timeStretch = layer.timeStretch + startFrame = layer.startFrame + keypathName = layer.keypathName + childKeypaths = [transformNode.transformProperties] + maskLayer = nil + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + weak var layerDelegate: CompositionLayerDelegate? + + let transformNode: LayerTransformNode + + let contentsLayer = CALayer() + + let maskLayer: MaskContainerLayer? + + let matteType: MatteType? + + let inFrame: CGFloat + let outFrame: CGFloat + let startFrame: CGFloat + let timeStretch: CGFloat + + // MARK: Keypath Searchable + + let keypathName: String + + final var childKeypaths: [KeypathSearchable] + + var renderScale: CGFloat = 1 { + didSet { + updateRenderScale() + } + } + + var matteLayer: CompositionLayer? { + didSet { + if let matte = matteLayer { + if let type = matteType, type == .invert { + mask = InvertedMatteLayer(inputMatte: matte) + } else { + mask = matte + } + } else { + mask = nil + } + } + } + + var keypathProperties: [String: AnyNodeProperty] { + [:] + } + + var keypathLayer: CALayer? { + contentsLayer + } + + final func displayWithFrame(frame: CGFloat, forceUpdates: Bool) { + transformNode.updateTree(frame, forceUpdates: forceUpdates) + let layerVisible = frame.isInRangeOrEqual(inFrame, outFrame) + /// Only update contents if current time is within the layers time bounds. + if layerVisible { + displayContentsWithFrame(frame: frame, forceUpdates: forceUpdates) + maskLayer?.updateWithFrame(frame: frame, forceUpdates: forceUpdates) + } + contentsLayer.transform = transformNode.globalTransform + contentsLayer.opacity = transformNode.opacity + contentsLayer.isHidden = !layerVisible + layerDelegate?.frameUpdated(frame: frame) + } + + func displayContentsWithFrame(frame _: CGFloat, forceUpdates _: Bool) { + /// To be overridden by subclass + } + + func updateRenderScale() { + contentsScale = renderScale + } +} + +// MARK: - CompositionLayerDelegate + +protocol CompositionLayerDelegate: AnyObject { + func frameUpdated(frame: CGFloat) +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayerDelegate.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayerDelegate.hpp new file mode 100644 index 00000000000..59b05a34268 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayerDelegate.hpp @@ -0,0 +1,13 @@ +#ifndef CompositionLayerDelegate_hpp +#define CompositionLayerDelegate_hpp + +namespace lottie { + +class CompositionLayerDelegate { +public: + virtual void frameUpdated(double frame) = 0; +}; + +} + +#endif /* CompositionLayerDelegate_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.cpp new file mode 100644 index 00000000000..ad3b669b24a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "ImageCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp new file mode 100644 index 00000000000..8b9d709245a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp @@ -0,0 +1,42 @@ +#ifndef ImageCompositionLayer_hpp +#define ImageCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Layers/ImageLayerModel.hpp" + +namespace lottie { + +class ImageCompositionLayer: public CompositionLayer { +public: + ImageCompositionLayer(std::shared_ptr const &imageLayer, Vector2D const &size) : + CompositionLayer(imageLayer, size) { + _imageReferenceID = imageLayer->referenceID; + + contentsLayer()->setMasksToBounds(true); + } + + std::shared_ptr image() { + return _image; + } + void setImage(std::shared_ptr image) { + _image = image; + contentsLayer()->setContents(image); + } + + std::string const &imageReferenceID() { + return _imageReferenceID; + } + +public: + virtual bool isImageCompositionLayer() const override { + return true; + } + +private: + std::string _imageReferenceID; + std::shared_ptr _image; +}; + +} + +#endif /* ImageCompositionLayer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift new file mode 100644 index 00000000000..b1be98001dd --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift @@ -0,0 +1,50 @@ +// +// ImageCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import CoreGraphics +import Foundation +import QuartzCore + +final class ImageCompositionLayer: CompositionLayer { + + // MARK: Lifecycle + + init(imageLayer: ImageLayerModel, size: CGSize) { + imageReferenceID = imageLayer.referenceID + super.init(layer: imageLayer, size: size) + contentsLayer.masksToBounds = true + contentsLayer.contentsGravity = CALayerContentsGravity.resize + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? ImageCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + imageReferenceID = layer.imageReferenceID + image = nil + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let imageReferenceID: String + + var image: CGImage? = nil { + didSet { + if let image = image { + contentsLayer.contents = image + } else { + contentsLayer.contents = nil + } + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.cpp new file mode 100644 index 00000000000..398d52d57c1 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.cpp @@ -0,0 +1,5 @@ +#include "MaskContainerLayer.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp new file mode 100644 index 00000000000..069bf5c63b5 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp @@ -0,0 +1,176 @@ +#ifndef MaskContainerLayer_hpp +#define MaskContainerLayer_hpp + +#include "Lottie/Private/Model/Objects/Mask.hpp" +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" + +namespace lottie { + +inline MaskMode usableMaskMode(MaskMode mode) { + switch (mode) { + case MaskMode::Add: + return MaskMode::Add; + case MaskMode::Subtract: + return MaskMode::Subtract; + case MaskMode::Intersect: + return MaskMode::Intersect; + case MaskMode::Lighten: + return MaskMode::Add; + case MaskMode::Darken: + return MaskMode::Darken; + case MaskMode::Difference: + return MaskMode::Intersect; + case MaskMode::None: + return MaskMode::None; + } +} + +class MaskNodeProperties: public NodePropertyMap { +public: + MaskNodeProperties(std::shared_ptr const &mask) : + _mode(mask->mode()), + _inverted(mask->inverted) { + _opacity = std::make_shared>(std::make_shared>(mask->opacity->keyframes)); + _shape = std::make_shared>(std::make_shared>(mask->shape.keyframes)); + _expansion = std::make_shared>(std::make_shared>(mask->expansion->keyframes)); + + _propertyMap.insert(std::make_pair("Opacity", _opacity)); + _propertyMap.insert(std::make_pair("Shape", _shape)); + _propertyMap.insert(std::make_pair("Expansion", _expansion)); + + for (const auto &it : _propertyMap) { + _properties.push_back(it.second); + } + } + + virtual std::vector> &properties() override { + return _properties; + } + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + std::shared_ptr> const &opacity() const { + return _opacity; + } + + std::shared_ptr> const &shape() const { + return _shape; + } + + std::shared_ptr> const &expansion() const { + return _expansion; + } + + MaskMode mode() const { + return _mode; + } + + bool inverted() const { + return _inverted; + } + +private: + std::map> _propertyMap; + std::vector> _childKeypaths; + + std::vector> _properties; + + MaskMode _mode = MaskMode::Add; + bool _inverted = false; + + std::shared_ptr> _opacity; + std::shared_ptr> _shape; + std::shared_ptr> _expansion; +}; + +class MaskLayer: public CALayer { +public: + MaskLayer(std::shared_ptr const &mask) : + _properties(mask) { + _maskLayer = std::make_shared(); + + addSublayer(_maskLayer); + + if (mask->mode() == MaskMode::Add) { + _maskLayer->setFillColor(Color(1.0, 0.0, 0.0, 1.0)); + } else { + _maskLayer->setFillColor(Color(0.0, 1.0, 0.0, 1.0)); + } + _maskLayer->setFillRule(FillRule::EvenOdd); + } + + void updateWithFrame(double frame, bool forceUpdates) { + if (_properties.opacity()->needsUpdate(frame) || forceUpdates) { + _properties.opacity()->update(frame); + setOpacity(_properties.opacity()->value().value); + } + + if (_properties.shape()->needsUpdate(frame) || forceUpdates) { + _properties.shape()->update(frame); + _properties.expansion()->update(frame); + + auto path = _properties.shape()->value().cgPath(); + auto usableMode = usableMaskMode(_properties.mode()); + if ((usableMode == MaskMode::Subtract && !_properties.inverted()) || + (usableMode == MaskMode::Add && _properties.inverted())) { + /// Add a bounds rect to invert the mask + auto newPath = CGPath::makePath(); + newPath->addRect(CGRect::veryLarge()); + newPath->addPath(path); + path = std::static_pointer_cast(newPath); + } + _maskLayer->setPath(path); + } + } + +private: + MaskNodeProperties _properties; + + std::shared_ptr _maskLayer; +}; + +class MaskContainerLayer: public CALayer { +public: + MaskContainerLayer(std::vector> const &masks) { + auto containerLayer = std::make_shared(); + bool firstObject = true; + for (const auto &mask : masks) { + auto maskLayer = std::make_shared(mask); + _maskLayers.push_back(maskLayer); + + auto usableMode = usableMaskMode(mask->mode()); + if (usableMode == MaskMode::None) { + continue; + } else if (usableMode == MaskMode::Add || firstObject) { + firstObject = false; + containerLayer->addSublayer(maskLayer); + } else { + containerLayer->setMask(maskLayer); + auto newContainer = std::make_shared(); + newContainer->addSublayer(containerLayer); + containerLayer = newContainer; + } + } + addSublayer(containerLayer); + } + + // MARK: Internal + + void updateWithFrame(double frame, bool forceUpdates) { + for (const auto &maskLayer : _maskLayers) { + maskLayer->updateWithFrame(frame, forceUpdates); + } + } + +private: + std::vector> _maskLayers; +}; + +} + +#endif /* MaskContainerLayer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift new file mode 100644 index 00000000000..f98d321212a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift @@ -0,0 +1,191 @@ +// +// MaskContainerLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import QuartzCore + +extension MaskMode { + var usableMode: MaskMode { + switch self { + case .add: + return .add + case .subtract: + return .subtract + case .intersect: + return .intersect + case .lighten: + return .add + case .darken: + return .darken + case .difference: + return .intersect + case .none: + return .none + } + } +} + +// MARK: - MaskContainerLayer + +final class MaskContainerLayer: CALayer { + + // MARK: Lifecycle + + init(masks: [Mask]) { + super.init() + anchorPoint = .zero + var containerLayer = CALayer() + var firstObject = true + for mask in masks { + let maskLayer = MaskLayer(mask: mask) + maskLayers.append(maskLayer) + if mask.mode.usableMode == .none { + continue + } else if mask.mode.usableMode == .add || firstObject { + firstObject = false + containerLayer.addSublayer(maskLayer) + } else { + containerLayer.mask = maskLayer + let newContainer = CALayer() + newContainer.addSublayer(containerLayer) + containerLayer = newContainer + } + } + addSublayer(containerLayer) + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? MaskContainerLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { + maskLayers.forEach({ $0.updateWithFrame(frame: frame, forceUpdates: forceUpdates) }) + } + + // MARK: Fileprivate + + fileprivate var maskLayers: [MaskLayer] = [] +} + +extension CGRect { + static var veryLargeRect: CGRect { + CGRect( + x: -100_000_000, + y: -100_000_000, + width: 200_000_000, + height: 200_000_000) + } +} + +// MARK: - MaskLayer + +private class MaskLayer: CALayer { + + // MARK: Lifecycle + + init(mask: Mask) { + properties = MaskNodeProperties(mask: mask) + super.init() + addSublayer(maskLayer) + anchorPoint = .zero + maskLayer.fillColor = mask.mode == .add + ? CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 0, 0, 1]) + : CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 1, 0, 1]) + maskLayer.fillRule = CAShapeLayerFillRule.evenOdd + actions = [ + "opacity" : NSNull(), + ] + + } + + override init(layer: Any) { + properties = nil + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let properties: MaskNodeProperties? + + let maskLayer = LottieCAShapeLayer() + + func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { + guard let properties = properties else { return } + if properties.opacity.needsUpdate(frame: frame) || forceUpdates { + properties.opacity.update(frame: frame) + opacity = Float(properties.opacity.value.cgFloatValue) + } + + if properties.shape.needsUpdate(frame: frame) || forceUpdates { + properties.shape.update(frame: frame) + properties.expansion.update(frame: frame) + + let shapePath = properties.shape.value.cgPath() + var path = shapePath + if + properties.mode.usableMode == .subtract && !properties.inverted || + (properties.mode.usableMode == .add && properties.inverted) + { + /// Add a bounds rect to invert the mask + let newPath = CGMutablePath() + newPath.addRect(CGRect.veryLargeRect) + newPath.addPath(shapePath) + path = newPath + } + maskLayer.path = path + } + + } +} + +// MARK: - MaskNodeProperties + +private class MaskNodeProperties: NodePropertyMap { + + // MARK: Lifecycle + + init(mask: Mask) { + mode = mask.mode + inverted = mask.inverted + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.opacity.keyframes)) + shape = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.shape.keyframes)) + expansion = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.expansion.keyframes)) + propertyMap = [ + "Opacity" : opacity, + "Shape" : shape, + "Expansion" : expansion, + ] + properties = Array(propertyMap.values) + } + + // MARK: Internal + + var propertyMap: [String: AnyNodeProperty] + + var properties: [AnyNodeProperty] + + let mode: MaskMode + let inverted: Bool + + let opacity: NodeProperty + let shape: NodeProperty + let expansion: NodeProperty +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.cpp new file mode 100644 index 00000000000..6c5345cfd61 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "NullCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp new file mode 100644 index 00000000000..c3d3dea5cc0 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp @@ -0,0 +1,17 @@ +#ifndef NullCompositionLayer_hpp +#define NullCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" + +namespace lottie { + +class NullCompositionLayer: public CompositionLayer { +public: + NullCompositionLayer(std::shared_ptr const &layer) : + CompositionLayer(layer, Vector2D::Zero()) { + } +}; + +} + +#endif /* NullCompositionLayer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.swift new file mode 100644 index 00000000000..3fdf1637606 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.swift @@ -0,0 +1,28 @@ +// +// NullCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation + +final class NullCompositionLayer: CompositionLayer { + + init(layer: LayerModel) { + super.init(layer: layer, size: .zero) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? NullCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + super.init(layer: layer) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.cpp new file mode 100644 index 00000000000..d3428a9b1b0 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "PreCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp new file mode 100644 index 00000000000..384d96d645b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp @@ -0,0 +1,200 @@ +#ifndef PreCompositionLayer_hpp +#define PreCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Layers/PreCompLayerModel.hpp" +#include "Lottie/Private/Model/Assets/PrecompAsset.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp" +#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp" +#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp" +#include "Lottie/Private/Model/Assets/AssetLibrary.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp" + +namespace lottie { + +class PreCompositionLayer: public CompositionLayer { +public: + PreCompositionLayer( + std::shared_ptr const &precomp, + PrecompAsset const &asset, + std::shared_ptr const &layerImageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider, + std::shared_ptr const &assetLibrary, + double frameRate + ) : CompositionLayer(precomp, Vector2D(precomp->width, precomp->height)) { + if (precomp->timeRemapping) { + _remappingNode = std::make_shared>(std::make_shared>(precomp->timeRemapping->keyframes)); + } + _frameRate = frameRate; + + setBounds(CGRect(0.0, 0.0, precomp->width, precomp->height)); + contentsLayer()->setMasksToBounds(true); + contentsLayer()->setBounds(bounds()); + + auto layers = initializeCompositionLayers( + asset.layers, + assetLibrary, + layerImageProvider, + textProvider, + fontProvider, + frameRate + ); + + std::vector> imageLayers; + + std::shared_ptr mattedLayer; + + for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) { + std::shared_ptr layer = *layerIt; + layer->setBounds(bounds()); + _animationLayers.push_back(layer); + + if (layer->isImageCompositionLayer()) { + imageLayers.push_back(std::static_pointer_cast(layer)); + } + if (mattedLayer) { + /// The previous layer requires this layer to be its matte + mattedLayer->setMatteLayer(layer); + mattedLayer = nullptr; + continue; + } + if (layer->matteType().has_value() && (layer->matteType().value() == MatteType::Add || layer->matteType().value() == MatteType::Invert)) { + /// We have a layer that requires a matte. + mattedLayer = layer; + } + contentsLayer()->addSublayer(layer); + } + + for (const auto &layer : layers) { + _childKeypaths.push_back(layer); + } + + layerImageProvider->addImageLayers(imageLayers); + } + + virtual std::map> keypathProperties() const override { + if (!_remappingNode) { + return {}; + } + + std::map> result; + result.insert(std::make_pair("Time Remap", _remappingNode)); + + return result; + } + + virtual void displayContentsWithFrame(double frame, bool forceUpdates) override { + double localFrame = 0.0; + if (_remappingNode) { + _remappingNode->update(frame); + localFrame = _remappingNode->value().value * _frameRate; + } else { + localFrame = (frame - startFrame()) / timeStretch(); + } + + for (const auto &animationLayer : _animationLayers) { + animationLayer->displayWithFrame(localFrame, forceUpdates); + } + } + + virtual std::shared_ptr renderTreeNode() override { + if (_contentsLayer->isHidden()) { + return nullptr; + } + + std::shared_ptr maskNode; + bool invertMask = false; + if (_matteLayer) { + maskNode = _matteLayer->renderTreeNode(); + if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) { + invertMask = true; + } + } + + std::vector> renderTreeValue; + auto renderTreeContentItem = renderTree(); + if (renderTreeContentItem) { + renderTreeValue.push_back(renderTreeContentItem); + } + + std::vector> subnodes; + subnodes.push_back(std::make_shared( + _contentsLayer->bounds(), + _contentsLayer->position(), + _contentsLayer->transform(), + _contentsLayer->opacity(), + _contentsLayer->masksToBounds(), + _contentsLayer->isHidden(), + nullptr, + renderTreeValue, + nullptr, + false + )); + + assert(opacity() == 1.0); + assert(!isHidden()); + assert(!masksToBounds()); + assert(transform().isIdentity()); + assert(position() == Vector2D::Zero()); + + return std::make_shared( + bounds(), + position(), + transform(), + opacity(), + masksToBounds(), + isHidden(), + nullptr, + subnodes, + maskNode, + invertMask + ); + } + + std::shared_ptr renderTree() { + std::vector> result; + + for (const auto &animationLayer : _animationLayers) { + bool found = false; + for (const auto &sublayer : contentsLayer()->sublayers()) { + if (animationLayer == sublayer) { + found = true; + break; + } + } + if (found) { + auto node = animationLayer->renderTreeNode(); + if (node) { + result.push_back(node); + } + } + } + + std::vector> subnodes; + return std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + result, + nullptr, + false + ); + } + +private: + double _frameRate = 0.0; + std::shared_ptr> _remappingNode; + + std::vector> _animationLayers; +}; + +} + +#endif /* PreCompositionLayer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift new file mode 100644 index 00000000000..d0722deb781 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift @@ -0,0 +1,121 @@ +// +// PreCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import QuartzCore + +final class PreCompositionLayer: CompositionLayer { + + // MARK: Lifecycle + + init( + precomp: PreCompLayerModel, + asset: PrecompAsset, + layerImageProvider: LayerImageProvider, + textProvider: AnimationTextProvider, + fontProvider: AnimationFontProvider, + assetLibrary: AssetLibrary?, + frameRate: CGFloat) + { + animationLayers = [] + if let keyframes = precomp.timeRemapping?.keyframes { + remappingNode = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframes)) + } else { + remappingNode = nil + } + self.frameRate = frameRate + super.init(layer: precomp, size: CGSize(width: precomp.width, height: precomp.height)) + bounds = CGRect(origin: .zero, size: CGSize(width: precomp.width, height: precomp.height)) + contentsLayer.masksToBounds = true + contentsLayer.bounds = bounds + + let layers = asset.layers.initializeCompositionLayers( + assetLibrary: assetLibrary, + layerImageProvider: layerImageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + frameRate: frameRate) + + var imageLayers = [ImageCompositionLayer]() + + var mattedLayer: CompositionLayer? = nil + + for layer in layers.reversed() { + layer.bounds = bounds + animationLayers.append(layer) + if let imageLayer = layer as? ImageCompositionLayer { + imageLayers.append(imageLayer) + } + if let matte = mattedLayer { + /// The previous layer requires this layer to be its matte + matte.matteLayer = layer + mattedLayer = nil + continue + } + if + let matte = layer.matteType, + matte == .add || matte == .invert + { + /// We have a layer that requires a matte. + mattedLayer = layer + } + contentsLayer.addSublayer(layer) + } + + childKeypaths.append(contentsOf: layers) + + layerImageProvider.addImageLayers(imageLayers) + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? PreCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + frameRate = layer.frameRate + remappingNode = nil + animationLayers = [] + + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let frameRate: CGFloat + let remappingNode: NodeProperty? + + override var keypathProperties: [String: AnyNodeProperty] { + guard let remappingNode = remappingNode else { + return super.keypathProperties + } + return ["Time Remap" : remappingNode] + } + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + let localFrame: CGFloat + if let remappingNode = remappingNode { + remappingNode.update(frame: frame) + localFrame = remappingNode.value.cgFloatValue * frameRate + } else { + localFrame = (frame - startFrame) / timeStretch + } + animationLayers.forEach( { $0.displayWithFrame(frame: localFrame, forceUpdates: forceUpdates) }) + } + + override func updateRenderScale() { + super.updateRenderScale() + animationLayers.forEach( { $0.renderScale = renderScale } ) + } + + // MARK: Fileprivate + + fileprivate var animationLayers: [CompositionLayer] +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp new file mode 100644 index 00000000000..fd1f61ecdc9 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp @@ -0,0 +1,1354 @@ +#include "ShapeCompositionLayer.hpp" + +#include "Lottie/Private/Model/ShapeItems/Group.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/ShapeItems/Rectangle.hpp" +#include "Lottie/Private/Model/ShapeItems/Star.hpp" +#include "Lottie/Private/Model/ShapeItems/Shape.hpp" +#include "Lottie/Private/Model/ShapeItems/Trim.hpp" +#include "Lottie/Private/Model/ShapeItems/Stroke.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientStroke.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp" +#include "Lottie/Private/Model/ShapeItems/ShapeTransform.hpp" + +namespace lottie { + +class ShapeLayerPresentationTree { +public: + class FillOutput { + public: + FillOutput() { + } + ~FillOutput() = default; + + virtual void update(AnimationFrameTime frameTime) = 0; + virtual std::shared_ptr fill() = 0; + }; + + class SolidFillOutput : public FillOutput { + public: + explicit SolidFillOutput(Fill const &fill) : + rule(fill.fillRule.value_or(FillRule::NonZeroWinding)), + color(fill.color.keyframes), + opacity(fill.opacity.keyframes) { + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (color.hasUpdate(frameTime)) { + hasUpdates = true; + colorValue = color.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (!_fill || hasUpdates) { + auto solid = std::make_shared(colorValue, opacityValue * 0.01); + _fill = std::make_shared( + solid, + rule + ); + } + } + + virtual std::shared_ptr fill() override { + return _fill; + } + + private: + FillRule rule; + + KeyframeInterpolator color; + Color colorValue = Color(0.0, 0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + double opacityValue = 0.0; + + std::shared_ptr _fill; + }; + + class GradientFillOutput : public FillOutput { + public: + explicit GradientFillOutput(GradientFill const &gradientFill) : + rule(FillRule::NonZeroWinding), + numberOfColors(gradientFill.numberOfColors), + gradientType(gradientFill.gradientType), + colors(gradientFill.colors.keyframes), + startPoint(gradientFill.startPoint.keyframes), + endPoint(gradientFill.endPoint.keyframes), + opacity(gradientFill.opacity.keyframes) { + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (colors.hasUpdate(frameTime)) { + hasUpdates = true; + colorsValue = colors.value(frameTime); + } + + if (startPoint.hasUpdate(frameTime)) { + hasUpdates = true; + startPointValue = startPoint.value(frameTime); + } + + if (endPoint.hasUpdate(frameTime)) { + hasUpdates = true; + endPointValue = endPoint.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (!_fill || hasUpdates) { + std::vector colors; + std::vector locations; + getGradientParameters(numberOfColors, colorsValue, colors, locations); + + auto gradient = std::make_shared( + opacityValue * 0.01, + gradientType, + colors, + locations, + Vector2D(startPointValue.x, startPointValue.y), + Vector2D(endPointValue.x, endPointValue.y) + ); + _fill = std::make_shared( + gradient, + rule + ); + } + } + + virtual std::shared_ptr fill() override { + return _fill; + } + + private: + FillRule rule; + int numberOfColors = 0; + GradientType gradientType; + + KeyframeInterpolator colors; + GradientColorSet colorsValue; + + KeyframeInterpolator startPoint; + Vector3D startPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator endPoint; + Vector3D endPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + double opacityValue = 0.0; + + std::shared_ptr _fill; + }; + + class StrokeOutput { + public: + StrokeOutput() { + } + ~StrokeOutput() = default; + + virtual void update(AnimationFrameTime frameTime) = 0; + virtual std::shared_ptr stroke() = 0; + }; + + class SolidStrokeOutput : public StrokeOutput { + public: + SolidStrokeOutput(Stroke const &stroke) : + lineJoin(stroke.lineJoin), + lineCap(stroke.lineCap), + miterLimit(stroke.miterLimit.value_or(4.0)), + color(stroke.color.keyframes), + opacity(stroke.opacity.keyframes), + width(stroke.width.keyframes) { + if (stroke.dashPattern.has_value()) { + StrokeShapeDashConfiguration dashConfiguration(stroke.dashPattern.value()); + dashPattern = std::make_unique(dashConfiguration.dashPatterns); + + if (!dashConfiguration.dashPhase.empty()) { + dashPhase = std::make_unique>(dashConfiguration.dashPhase); + } + } + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (color.hasUpdate(frameTime)) { + hasUpdates = true; + colorValue = color.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (width.hasUpdate(frameTime)) { + hasUpdates = true; + widthValue = width.value(frameTime).value; + } + + if (dashPattern) { + if (dashPattern->hasUpdate(frameTime)) { + hasUpdates = true; + dashPatternValue = dashPattern->value(frameTime); + } + } + + if (dashPhase) { + if (dashPhase->hasUpdate(frameTime)) { + hasUpdates = true; + dashPhaseValue = dashPhase->value(frameTime).value; + } + } + + if (!_stroke || hasUpdates) { + bool hasNonZeroDashes = false; + if (!dashPatternValue.values.empty()) { + for (const auto &value : dashPatternValue.values) { + if (value != 0) { + hasNonZeroDashes = true; + break; + } + } + } + + auto solid = std::make_shared(colorValue, opacityValue * 0.01); + _stroke = std::make_shared( + solid, + widthValue, + lineJoin, + lineCap, + miterLimit, + hasNonZeroDashes ? dashPhaseValue : 0.0, + hasNonZeroDashes ? dashPatternValue.values : std::vector() + ); + } + } + + virtual std::shared_ptr stroke() override { + return _stroke; + } + + private: + LineJoin lineJoin; + LineCap lineCap; + double miterLimit = 4.0; + + KeyframeInterpolator color; + Color colorValue = Color(0.0, 0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + double opacityValue = 0.0; + + KeyframeInterpolator width; + double widthValue = 0.0; + + std::unique_ptr dashPattern; + DashPattern dashPatternValue = DashPattern({}); + + std::unique_ptr> dashPhase; + double dashPhaseValue = 0.0; + + std::shared_ptr _stroke; + }; + + class GradientStrokeOutput : public StrokeOutput { + public: + GradientStrokeOutput(GradientStroke const &gradientStroke) : + lineJoin(gradientStroke.lineJoin), + lineCap(gradientStroke.lineCap), + miterLimit(gradientStroke.miterLimit.value_or(4.0)), + numberOfColors(gradientStroke.numberOfColors), + gradientType(gradientStroke.gradientType), + colors(gradientStroke.colors.keyframes), + startPoint(gradientStroke.startPoint.keyframes), + endPoint(gradientStroke.endPoint.keyframes), + opacity(gradientStroke.opacity.keyframes), + width(gradientStroke.width.keyframes) { + if (gradientStroke.dashPattern.has_value()) { + StrokeShapeDashConfiguration dashConfiguration(gradientStroke.dashPattern.value()); + dashPattern = std::make_unique(dashConfiguration.dashPatterns); + + if (!dashConfiguration.dashPhase.empty()) { + dashPhase = std::make_unique>(dashConfiguration.dashPhase); + } + } + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (colors.hasUpdate(frameTime)) { + hasUpdates = true; + colorsValue = colors.value(frameTime); + } + + if (startPoint.hasUpdate(frameTime)) { + hasUpdates = true; + startPointValue = startPoint.value(frameTime); + } + + if (endPoint.hasUpdate(frameTime)) { + hasUpdates = true; + endPointValue = endPoint.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (width.hasUpdate(frameTime)) { + hasUpdates = true; + widthValue = width.value(frameTime).value; + } + + if (dashPattern) { + if (dashPattern->hasUpdate(frameTime)) { + hasUpdates = true; + dashPatternValue = dashPattern->value(frameTime); + } + } + + if (dashPhase) { + if (dashPhase->hasUpdate(frameTime)) { + hasUpdates = true; + dashPhaseValue = dashPhase->value(frameTime).value; + } + } + + if (!_stroke || hasUpdates) { + bool hasNonZeroDashes = false; + if (!dashPatternValue.values.empty()) { + for (const auto &value : dashPatternValue.values) { + if (value != 0) { + hasNonZeroDashes = true; + break; + } + } + } + + std::vector colors; + std::vector locations; + getGradientParameters(numberOfColors, colorsValue, colors, locations); + + auto gradient = std::make_shared( + opacityValue * 0.01, + gradientType, + colors, + locations, + Vector2D(startPointValue.x, startPointValue.y), + Vector2D(endPointValue.x, endPointValue.y) + ); + _stroke = std::make_shared( + gradient, + widthValue, + lineJoin, + lineCap, + miterLimit, + hasNonZeroDashes ? dashPhaseValue : 0.0, + hasNonZeroDashes ? dashPatternValue.values : std::vector() + ); + } + } + + virtual std::shared_ptr stroke() override { + return _stroke; + } + + private: + LineJoin lineJoin; + LineCap lineCap; + double miterLimit = 4.0; + + int numberOfColors = 0; + GradientType gradientType; + + KeyframeInterpolator colors; + GradientColorSet colorsValue; + + KeyframeInterpolator startPoint; + Vector3D startPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator endPoint; + Vector3D endPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + double opacityValue = 0.0; + + KeyframeInterpolator width; + double widthValue = 0.0; + + std::unique_ptr dashPattern; + DashPattern dashPatternValue = DashPattern({}); + + std::unique_ptr> dashPhase; + double dashPhaseValue = 0.0; + + std::shared_ptr _stroke; + }; + + struct TrimParams { + double start = 0.0; + double end = 0.0; + double offset = 0.0; + TrimType type = TrimType::Simultaneously; + size_t subItemLimit = 0; + + TrimParams(double start_, double end_, double offset_, TrimType type_, size_t subItemLimit_) : + start(start_), + end(end_), + offset(offset_), + type(type_), + subItemLimit(subItemLimit_) { + } + }; + + class TrimParamsOutput { + public: + TrimParamsOutput(Trim const &trim, size_t subItemLimit) : + type(trim.trimType), + subItemLimit(subItemLimit), + start(trim.start.keyframes), + end(trim.end.keyframes), + offset(trim.offset.keyframes) { + } + + void update(AnimationFrameTime frameTime) { + if (start.hasUpdate(frameTime)) { + startValue = start.value(frameTime).value; + } + + if (end.hasUpdate(frameTime)) { + endValue = end.value(frameTime).value; + } + + if (offset.hasUpdate(frameTime)) { + offsetValue = offset.value(frameTime).value; + } + } + + TrimParams trimParams() { + double resolvedStartValue = startValue * 0.01; + double resolvedEndValue = endValue * 0.01; + double resolvedStart = std::min(resolvedStartValue, resolvedEndValue); + double resolvedEnd = std::max(resolvedStartValue, resolvedEndValue); + + double resolvedOffset = fmod(offsetValue, 360.0) / 360.0; + + return TrimParams(resolvedStart, resolvedEnd, resolvedOffset, type, subItemLimit); + } + + private: + TrimType type; + size_t subItemLimit = 0; + + KeyframeInterpolator start; + double startValue = 0.0; + + KeyframeInterpolator end; + double endValue = 0.0; + + KeyframeInterpolator offset; + double offsetValue = 0.0; + }; + + struct ShadingVariant { + std::shared_ptr fill; + std::shared_ptr stroke; + size_t subItemLimit = 0; + + std::shared_ptr renderTree; + }; + + struct TransformedPath { + BezierPath path; + CATransform3D transform; + + TransformedPath(BezierPath const &path_, CATransform3D const &transform_) : + path(path_), + transform(transform_) { + } + }; + + class PathOutput { + public: + PathOutput() { + } + virtual ~PathOutput() = default; + + virtual void update(AnimationFrameTime frameTime) = 0; + virtual BezierPath const *currentPath() = 0; + }; + + class StaticPathOutput : public PathOutput { + public: + explicit StaticPathOutput(BezierPath const &path) : + resolvedPath(path) { + } + + virtual void update(AnimationFrameTime frameTime) override { + } + + virtual BezierPath const *currentPath() override { + return &resolvedPath; + } + + private: + BezierPath resolvedPath; + }; + + class ShapePathOutput : public PathOutput { + public: + explicit ShapePathOutput(Shape const &shape) : + path(shape.path.keyframes) { + } + + virtual void update(AnimationFrameTime frameTime) override { + if (!hasValidData || path.hasUpdate(frameTime)) { + path.update(frameTime, resolvedPath); + } + + hasValidData = true; + } + + virtual BezierPath const *currentPath() override { + return &resolvedPath; + } + + private: + bool hasValidData = false; + + BezierPathKeyframeInterpolator path; + + BezierPath resolvedPath; + }; + + class RectanglePathOutput : public PathOutput { + public: + explicit RectanglePathOutput(Rectangle const &rectangle) : + direction(rectangle.direction.value_or(PathDirection::Clockwise)), + position(rectangle.position.keyframes), + size(rectangle.size.keyframes), + cornerRadius(rectangle.cornerRadius.keyframes) { + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (!hasValidData || position.hasUpdate(frameTime)) { + hasUpdates = true; + positionValue = position.value(frameTime); + } + if (!hasValidData || size.hasUpdate(frameTime)) { + hasUpdates = true; + sizeValue = size.value(frameTime); + } + if (!hasValidData || cornerRadius.hasUpdate(frameTime)) { + hasUpdates = true; + cornerRadiusValue = cornerRadius.value(frameTime).value; + } + + if (hasUpdates) { + resolvedPath = makeRectangleBezierPath(Vector2D(positionValue.x, positionValue.y), Vector2D(sizeValue.x, sizeValue.y), cornerRadiusValue, direction); + } + + hasValidData = true; + } + + virtual BezierPath const *currentPath() override { + return &resolvedPath; + } + + private: + bool hasValidData = false; + + PathDirection direction; + + KeyframeInterpolator position; + Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator size; + Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator cornerRadius; + double cornerRadiusValue = 0.0; + + BezierPath resolvedPath; + }; + + class EllipsePathOutput : public PathOutput { + public: + explicit EllipsePathOutput(Ellipse const &ellipse) : + direction(ellipse.direction.value_or(PathDirection::Clockwise)), + position(ellipse.position.keyframes), + size(ellipse.size.keyframes) { + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (!hasValidData || position.hasUpdate(frameTime)) { + hasUpdates = true; + positionValue = position.value(frameTime); + } + if (!hasValidData || size.hasUpdate(frameTime)) { + hasUpdates = true; + sizeValue = size.value(frameTime); + } + + if (hasUpdates) { + resolvedPath = makeEllipseBezierPath(Vector2D(sizeValue.x, sizeValue.y), Vector2D(positionValue.x, positionValue.y), direction); + } + + hasValidData = true; + } + + virtual BezierPath const *currentPath() override { + return &resolvedPath; + } + + private: + bool hasValidData = false; + + PathDirection direction; + + KeyframeInterpolator position; + Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator size; + Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0); + + BezierPath resolvedPath; + }; + + class StarPathOutput : public PathOutput { + public: + explicit StarPathOutput(Star const &star) : + direction(star.direction.value_or(PathDirection::Clockwise)), + position(star.position.keyframes), + outerRadius(star.outerRadius.keyframes), + outerRoundedness(star.outerRoundness.keyframes), + rotation(star.rotation.keyframes), + points(star.points.keyframes) { + if (star.innerRadius.has_value()) { + innerRadius = std::make_unique>(std::make_shared>(star.innerRadius->keyframes)); + } else { + innerRadius = std::make_unique>(std::make_shared>(Vector1D(0.0))); + } + + if (star.innerRoundness.has_value()) { + innerRoundedness = std::make_unique>(std::make_shared>(star.innerRoundness->keyframes)); + } else { + innerRoundedness = std::make_unique>(std::make_shared>(Vector1D(0.0))); + } + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (!hasValidData || position.hasUpdate(frameTime)) { + hasUpdates = true; + positionValue = position.value(frameTime); + } + + if (!hasValidData || outerRadius.hasUpdate(frameTime)) { + hasUpdates = true; + outerRadiusValue = outerRadius.value(frameTime).value; + } + + innerRadius->update(frameTime); + if (!hasValidData || innerRadiusValue != innerRadius->value().value) { + hasUpdates = true; + innerRadiusValue = innerRadius->value().value; + } + + if (!hasValidData || outerRoundedness.hasUpdate(frameTime)) { + hasUpdates = true; + outerRoundednessValue = outerRoundedness.value(frameTime).value; + } + + innerRoundedness->update(frameTime); + if (!hasValidData || innerRoundednessValue != innerRoundedness->value().value) { + hasUpdates = true; + innerRoundednessValue = innerRoundedness->value().value; + } + + if (!hasValidData || points.hasUpdate(frameTime)) { + hasUpdates = true; + pointsValue = points.value(frameTime).value; + } + + if (!hasValidData || rotation.hasUpdate(frameTime)) { + hasUpdates = true; + rotationValue = rotation.value(frameTime).value; + } + + if (hasUpdates) { + resolvedPath = makeStarBezierPath(Vector2D(positionValue.x, positionValue.y), outerRadiusValue, innerRadiusValue, outerRoundednessValue, innerRoundednessValue, pointsValue, rotationValue, direction); + } + + hasValidData = true; + } + + virtual BezierPath const *currentPath() override { + return &resolvedPath; + } + + private: + bool hasValidData = false; + + PathDirection direction; + + KeyframeInterpolator position; + Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator outerRadius; + double outerRadiusValue = 0.0; + + KeyframeInterpolator outerRoundedness; + double outerRoundednessValue = 0.0; + + std::unique_ptr> innerRadius; + double innerRadiusValue = 0.0; + + std::unique_ptr> innerRoundedness; + double innerRoundednessValue = 0.0; + + KeyframeInterpolator rotation; + double rotationValue = 0.0; + + KeyframeInterpolator points; + double pointsValue = 0.0; + + BezierPath resolvedPath; + }; + + class TransformOutput { + public: + TransformOutput(std::shared_ptr shapeTransform) { + if (shapeTransform->anchor) { + _anchor = std::make_unique>(shapeTransform->anchor->keyframes); + } + if (shapeTransform->position) { + _position = std::make_unique>(shapeTransform->position->keyframes); + } + if (shapeTransform->scale) { + _scale = std::make_unique>(shapeTransform->scale->keyframes); + } + if (shapeTransform->rotation) { + _rotation = std::make_unique>(shapeTransform->rotation->keyframes); + } + if (shapeTransform->skew) { + _skew = std::make_unique>(shapeTransform->skew->keyframes); + } + if (shapeTransform->skewAxis) { + _skewAxis = std::make_unique>(shapeTransform->skewAxis->keyframes); + } + if (shapeTransform->opacity) { + _opacity = std::make_unique>(shapeTransform->opacity->keyframes); + } + } + + void update(AnimationFrameTime frameTime) { + bool hasUpdates = false; + + if (!hasValidData) { + hasUpdates = true; + } + if (_anchor && _anchor->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_position && _position->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_scale && _scale->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_rotation && _rotation->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_skew && _skew->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_skewAxis && _skewAxis->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_opacity && _opacity->hasUpdate(frameTime)) { + hasUpdates = true; + } + + if (hasUpdates) { + //TODO:optimize by storing components + + Vector3D anchorValue(0.0, 0.0, 0.0); + if (_anchor) { + anchorValue = _anchor->value(frameTime); + } + + Vector3D positionValue(0.0, 0.0, 0.0); + if (_position) { + positionValue = _position->value(frameTime); + } + + Vector3D scaleValue(100.0, 100.0, 100.0); + if (_scale) { + scaleValue = _scale->value(frameTime); + } + + double rotationValue = 0.0; + if (_rotation) { + rotationValue = _rotation->value(frameTime).value; + } + + double skewValue = 0.0; + if (_skew) { + skewValue = _skew->value(frameTime).value; + } + + double skewAxisValue = 0.0; + if (_skewAxis) { + skewAxisValue = _skewAxis->value(frameTime).value; + } + + if (_opacity) { + _opacityValue = _opacity->value(frameTime).value * 0.01; + } else { + _opacityValue = 1.0; + } + + _transformValue = CATransform3D::identity().translated(Vector2D(positionValue.x, positionValue.y)).rotated(rotationValue).skewed(-skewValue, skewAxisValue).scaled(Vector2D(scaleValue.x * 0.01, scaleValue.y * 0.01)).translated(Vector2D(-anchorValue.x, -anchorValue.y)); + + hasValidData = true; + } + } + + CATransform3D const &transform() { + return _transformValue; + } + + double opacity() { + return _opacityValue; + } + + private: + bool hasValidData = false; + + std::unique_ptr> _anchor; + std::unique_ptr> _position; + std::unique_ptr> _scale; + std::unique_ptr> _rotation; + std::unique_ptr> _skew; + std::unique_ptr> _skewAxis; + std::unique_ptr> _opacity; + + CATransform3D _transformValue = CATransform3D::identity(); + double _opacityValue = 1.0; + }; + + class ContentItem { + public: + ContentItem() { + } + + public: + bool isGroup = false; + + void setPath(std::unique_ptr &&path_) { + path = std::move(path_); + } + + void setTransform(std::unique_ptr &&transform_) { + transform = std::move(transform_); + } + + std::shared_ptr const &renderTree() const { + return _renderTree; + } + + private: + std::unique_ptr path; + std::unique_ptr transform; + + std::vector shadings; + std::vector> trims; + + std::vector> subItems; + + std::shared_ptr _renderTree; + + private: + std::vector collectPaths(AnimationFrameTime frameTime, size_t subItemLimit, CATransform3D parentTransform) { + std::vector mappedPaths; + + CATransform3D effectiveTransform = parentTransform; + CATransform3D effectiveChildTransform = parentTransform; + + size_t maxSubitem = std::min(subItems.size(), subItemLimit); + + if (path) { + path->update(frameTime); + mappedPaths.emplace_back(*(path->currentPath()), effectiveTransform); + } + + for (size_t i = 0; i < maxSubitem; i++) { + auto &subItem = subItems[i]; + CATransform3D subItemTransform = effectiveChildTransform; + + if (subItem->isGroup && subItem->transform) { + subItem->transform->update(frameTime); + subItemTransform = subItem->transform->transform() * subItemTransform; + } + + std::optional currentTrim; + if (!trims.empty()) { + trims[0]->update(frameTime); + currentTrim = trims[0]->trimParams(); + } + + auto subItemPaths = subItem->collectPaths(frameTime, INT32_MAX, subItemTransform); + + if (currentTrim) { + CompoundBezierPath tempPath; + for (auto &path : subItemPaths) { + tempPath.appendPath(path.path.copyUsingTransform(path.transform)); + } + CompoundBezierPath trimmedPath = trimCompoundPath(tempPath, currentTrim->start, currentTrim->end, currentTrim->offset, currentTrim->type); + for (auto &path : trimmedPath.paths) { + mappedPaths.emplace_back(path, CATransform3D::identity()); + } + } else { + for (auto &path : subItemPaths) { + mappedPaths.emplace_back(path.path, path.transform); + } + } + } + + return mappedPaths; + } + + public: + void addSubItem(std::shared_ptr const &subItem) { + subItems.push_back(subItem); + } + + void addFill(std::shared_ptr fill) { + ShadingVariant shading; + shading.subItemLimit = subItems.size(); + shading.fill = fill; + shadings.insert(shadings.begin(), shading); + } + + void addStroke(std::shared_ptr stroke) { + ShadingVariant shading; + shading.subItemLimit = subItems.size(); + shading.stroke = stroke; + shadings.insert(shadings.begin(), shading); + } + + void addTrim(Trim const &trim) { + trims.push_back(std::make_shared(trim, subItems.size())); + } + + public: + void initializeRenderChildren() { + _renderTree = std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + std::vector>(), + nullptr, + false + ); + + if (!shadings.empty()) { + for (int i = 0; i < shadings.size(); i++) { + auto &shadingVariant = shadings[i]; + + if (!(shadingVariant.fill || shadingVariant.stroke)) { + continue; + } + + auto shadingRenderTree = std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + std::vector>(), + nullptr, + false + ); + shadingVariant.renderTree = shadingRenderTree; + _renderTree->_subnodes.push_back(shadingRenderTree); + } + } + + if (isGroup && !subItems.empty()) { + std::vector> subItemNodes; + for (int i = (int)subItems.size() - 1; i >= 0; i--) { + subItems[i]->initializeRenderChildren(); + subItemNodes.push_back(subItems[i]->_renderTree); + } + + if (!subItemNodes.empty()) { + _renderTree->_subnodes.push_back(std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + subItemNodes, + nullptr, + false + )); + } + } + } + + public: + void renderChildren(AnimationFrameTime frameTime, std::optional parentTrim) { + CATransform3D containerTransform = CATransform3D::identity(); + double containerOpacity = 1.0; + if (transform) { + transform->update(frameTime); + containerTransform = transform->transform(); + containerOpacity = transform->opacity(); + } + _renderTree->_transform = containerTransform; + _renderTree->_alpha = containerOpacity; + + for (int i = 0; i < shadings.size(); i++) { + const auto &shadingVariant = shadings[i]; + + if (!(shadingVariant.fill || shadingVariant.stroke)) { + continue; + } + + CompoundBezierPath compoundPath; + auto paths = collectPaths(frameTime, shadingVariant.subItemLimit, CATransform3D::identity()); + for (const auto &path : paths) { + compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); + } + + //std::optional currentTrim = parentTrim; + //TODO:investigate + /*if (!trims.empty()) { + currentTrim = trims[0]; + }*/ + + if (parentTrim) { + compoundPath = trimCompoundPath(compoundPath, parentTrim->start, parentTrim->end, parentTrim->offset, parentTrim->type); + } + + std::vector resultPaths; + for (const auto &path : compoundPath.paths) { + resultPaths.push_back(path); + } + + std::shared_ptr content; + + std::shared_ptr fill; + if (shadingVariant.fill) { + shadingVariant.fill->update(frameTime); + fill = shadingVariant.fill->fill(); + } + + std::shared_ptr stroke; + if (shadingVariant.stroke) { + shadingVariant.stroke->update(frameTime); + stroke = shadingVariant.stroke->stroke(); + } + + content = std::make_shared( + resultPaths, + stroke, + fill + ); + + shadingVariant.renderTree->_content = content; + } + + if (isGroup && !subItems.empty()) { + for (int i = (int)subItems.size() - 1; i >= 0; i--) { + std::optional childTrim = parentTrim; + for (const auto &trim : trims) { + trim->update(frameTime); + + if (i < (int)trim->trimParams().subItemLimit) { + //TODO:allow combination + //assert(!parentTrim); + childTrim = trim->trimParams(); + } + } + + subItems[i]->renderChildren(frameTime, childTrim); + } + } + } + }; + +public: + ShapeLayerPresentationTree(std::vector> const &items) { + itemTree = std::make_shared(); + itemTree->isGroup = true; + ShapeLayerPresentationTree::renderTreeContent(items, itemTree); + } + + ShapeLayerPresentationTree(std::shared_ptr const &solidLayer) { + itemTree = std::make_shared(); + itemTree->isGroup = true; + + std::vector> items; + items.push_back(std::make_shared( + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + solidLayer->hidden, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + KeyframeGroup(Vector3D(0.0, 0.0, 0.0)), + KeyframeGroup(Vector3D(solidLayer->width, solidLayer->height, 0.0)), + KeyframeGroup(Vector1D(0.0)) + )); + ShapeLayerPresentationTree::renderTreeContent(items, itemTree); + } + +private: + static void renderTreeContent(std::vector> const &items, std::shared_ptr &itemTree) { + for (const auto &item : items) { + if (item->hidden()) { + continue; + } + + switch (item->type) { + case ShapeType::Fill: { + Fill const &fill = *((Fill *)item.get()); + + itemTree->addFill(std::make_shared(fill)); + + break; + } + case ShapeType::GradientFill: { + GradientFill const &gradientFill = *((GradientFill *)item.get()); + + itemTree->addFill(std::make_shared(gradientFill)); + + break; + } + case ShapeType::Stroke: { + Stroke const &stroke = *((Stroke *)item.get()); + + itemTree->addStroke(std::make_shared(stroke)); + + break; + } + case ShapeType::GradientStroke: { + GradientStroke const &gradientStroke = *((GradientStroke *)item.get()); + + itemTree->addStroke(std::make_shared(gradientStroke)); + + break; + } + case ShapeType::Group: { + Group const &group = *((Group *)item.get()); + + auto groupItem = std::make_shared(); + groupItem->isGroup = true; + + ShapeLayerPresentationTree::renderTreeContent(group.items, groupItem); + + itemTree->addSubItem(groupItem); + + break; + } + case ShapeType::Shape: { + Shape const &shape = *((Shape *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(shape)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::Trim: { + Trim const &trim = *((Trim *)item.get()); + + itemTree->addTrim(trim); + + break; + } + case ShapeType::Transform: { + auto transform = std::static_pointer_cast(item); + + itemTree->setTransform(std::make_unique(transform)); + + break; + } + case ShapeType::Ellipse: { + Ellipse const &ellipse = *((Ellipse *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(ellipse)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::Merge: { + //assert(false); + break; + } + case ShapeType::Rectangle: { + Rectangle const &rectangle = *((Rectangle *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(rectangle)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::Repeater: { + assert(false); + break; + } + case ShapeType::Star: { + Star const &star = *((Star *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(star)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::RoundedRectangle: { + //TODO:restore + break; + } + default: { + break; + } + } + } + + itemTree->initializeRenderChildren(); + } + +public: + std::shared_ptr itemTree; +}; + +ShapeCompositionLayer::ShapeCompositionLayer(std::shared_ptr const &shapeLayer) : +CompositionLayer(shapeLayer, Vector2D::Zero()) { + _contentTree = std::make_shared(shapeLayer->items); +} + +ShapeCompositionLayer::ShapeCompositionLayer(std::shared_ptr const &solidLayer) : +CompositionLayer(solidLayer, Vector2D::Zero()) { + _contentTree = std::make_shared(solidLayer); +} + +void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) { + _frameTime = frame; + _frameTimeInitialized = true; + + _contentTree->itemTree->renderChildren(_frameTime, std::nullopt); +} + +std::shared_ptr ShapeCompositionLayer::renderTreeNode() { + if (_contentsLayer->isHidden()) { + return nullptr; + } + + assert(_frameTimeInitialized); + + std::shared_ptr maskNode; + bool invertMask = false; + if (_matteLayer) { + maskNode = _matteLayer->renderTreeNode(); + if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) { + invertMask = true; + } + } + + std::vector> renderTreeValue; + renderTreeValue.push_back(_contentTree->itemTree->renderTree()); + + //printf("Name: %s\n", keypathName().c_str()); + /*if (!maskNode && keypathName().find("Shape Layer 3") != -1) { + return std::make_shared( + bounds(), + _contentsLayer->position(), + _contentsLayer->transform(), + _contentsLayer->opacity(), + _contentsLayer->masksToBounds(), + _contentsLayer->isHidden(), + nullptr, + renderTreeValue, + nullptr, + false + ); + }*/ + + std::vector> subnodes; + subnodes.push_back(std::make_shared( + _contentsLayer->bounds(), + _contentsLayer->position(), + _contentsLayer->transform(), + _contentsLayer->opacity(), + _contentsLayer->masksToBounds(), + _contentsLayer->isHidden(), + nullptr, + renderTreeValue, + nullptr, + false + )); + + assert(position() == Vector2D::Zero()); + assert(transform().isIdentity()); + assert(opacity() == 1.0); + assert(!masksToBounds()); + assert(!isHidden()); + + assert(_contentsLayer->bounds() == CGRect(0.0, 0.0, 0.0, 0.0)); + assert(_contentsLayer->position() == Vector2D::Zero()); + assert(!_contentsLayer->masksToBounds()); + + return std::make_shared( + bounds(), + position(), + transform(), + opacity(), + masksToBounds(), + isHidden(), + nullptr, + subnodes, + maskNode, + invertMask + ); +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp new file mode 100644 index 00000000000..ea88c5d0acb --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp @@ -0,0 +1,31 @@ +#ifndef ShapeCompositionLayer_hpp +#define ShapeCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Layers/ShapeLayerModel.hpp" +#include "Lottie/Private/Model/Layers/SolidLayerModel.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" + +namespace lottie { + +class ShapeLayerPresentationTree; + +/// A CompositionLayer responsible for initializing and rendering shapes +class ShapeCompositionLayer: public CompositionLayer { +public: + ShapeCompositionLayer(std::shared_ptr const &shapeLayer); + ShapeCompositionLayer(std::shared_ptr const &solidLayer); + + virtual void displayContentsWithFrame(double frame, bool forceUpdates) override; + virtual std::shared_ptr renderTreeNode() override; + +private: + std::shared_ptr _contentTree; + + AnimationFrameTime _frameTime = 0.0; + bool _frameTimeInitialized = false; +}; + +} + +#endif /* ShapeCompositionLayer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.swift new file mode 100644 index 00000000000..a001ca790d8 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.swift @@ -0,0 +1,57 @@ +// +// ShapeLayerContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import CoreGraphics +import Foundation + +/// A CompositionLayer responsible for initializing and rendering shapes +final class ShapeCompositionLayer: CompositionLayer { + + // MARK: Lifecycle + + init(shapeLayer: ShapeLayerModel) { + let results = shapeLayer.items.initializeNodeTree() + let renderContainer = ShapeContainerLayer() + self.renderContainer = renderContainer + rootNode = results.rootNode + super.init(layer: shapeLayer, size: .zero) + contentsLayer.addSublayer(renderContainer) + for container in results.renderContainers { + renderContainer.insertRenderLayer(container) + } + rootNode?.updateTree(0, forceUpdates: true) + childKeypaths.append(contentsOf: results.childrenNodes) + } + + override init(layer: Any) { + guard let layer = layer as? ShapeCompositionLayer else { + fatalError("init(layer:) wrong class.") + } + rootNode = nil + renderContainer = nil + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let rootNode: AnimatorNode? + let renderContainer: ShapeContainerLayer? + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + rootNode?.updateTree(frame, forceUpdates: forceUpdates) + renderContainer?.markRenderUpdates(forFrame: frame) + } + + override func updateRenderScale() { + super.updateRenderScale() + renderContainer?.renderScale = renderScale + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp new file mode 100644 index 00000000000..cd129d26068 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp @@ -0,0 +1,487 @@ +#include "BezierPathUtils.hpp" + +namespace lottie { + +BezierPath makeEllipseBezierPath( + Vector2D const &size, + Vector2D const ¢er, + PathDirection direction +) { + const double ControlPointConstant = 0.55228; + + Vector2D half = size * 0.5; + if (direction == PathDirection::CounterClockwise) { + half.x = half.x * -1.0; + } + + Vector2D q1(center.x, center.y - half.y); + Vector2D q2(center.x + half.x, center.y); + Vector2D q3(center.x, center.y + half.y); + Vector2D q4(center.x - half.x, center.y); + + Vector2D cp = half * ControlPointConstant; + + BezierPath path(CurveVertex::relative( + q1, + Vector2D(-cp.x, 0), + Vector2D(cp.x, 0))); + path.addVertex(CurveVertex::relative( + q2, + Vector2D(0, -cp.y), + Vector2D(0, cp.y))); + + path.addVertex(CurveVertex::relative( + q3, + Vector2D(cp.x, 0), + Vector2D(-cp.x, 0))); + + path.addVertex(CurveVertex::relative( + q4, + Vector2D(0, cp.y), + Vector2D(0, -cp.y))); + + path.addVertex(CurveVertex::relative( + q1, + Vector2D(-cp.x, 0), + Vector2D(cp.x, 0))); + path.close(); + return path; +} + +BezierPath makeRectangleBezierPath( + Vector2D const &position, + Vector2D const &inputSize, + double cornerRadius, + PathDirection direction +) { + const double ControlPointConstant = 0.55228; + + Vector2D size = inputSize * 0.5; + double radius = std::min(std::min(cornerRadius, size.x), size.y); + + BezierPath bezierPath; + std::vector points; + + if (radius <= 0.0) { + /// No Corners + points = { + /// Lead In + CurveVertex::relative( + Vector2D(size.x, -size.y), + Vector2D::Zero(), + Vector2D::Zero()) + .translated(position), + /// Corner 1 + CurveVertex::relative( + Vector2D(size.x, size.y), + Vector2D::Zero(), + Vector2D::Zero()) + .translated(position), + /// Corner 2 + CurveVertex::relative( + Vector2D(-size.x, size.y), + Vector2D::Zero(), + Vector2D::Zero()) + .translated(position), + /// Corner 3 + CurveVertex::relative( + Vector2D(-size.x, -size.y), + Vector2D::Zero(), + Vector2D::Zero()) + .translated(position), + /// Corner 4 + CurveVertex::relative( + Vector2D(size.x, -size.y), + Vector2D::Zero(), + Vector2D::Zero()) + .translated(position) + }; + } else { + double controlPoint = radius * ControlPointConstant; + points = { + /// Lead In + CurveVertex::absolute( + Vector2D(radius, 0), + Vector2D(radius, 0), + Vector2D(radius, 0)) + .translated(Vector2D(-radius, radius)) + .translated(Vector2D(size.x, -size.y)) + .translated(position), + /// Corner 1 + CurveVertex::absolute( + Vector2D(radius, 0), // Point + Vector2D(radius, 0), // In tangent + Vector2D(radius, controlPoint)) + .translated(Vector2D(-radius, -radius)) + .translated(Vector2D(size.x, size.y)) + .translated(position), + CurveVertex::absolute( + Vector2D(0, radius), // Point + Vector2D(controlPoint, radius), // In tangent + Vector2D(0, radius)) // Out Tangent + .translated(Vector2D(-radius, -radius)) + .translated(Vector2D(size.x, size.y)) + .translated(position), + /// Corner 2 + CurveVertex::absolute( + Vector2D(0, radius), // Point + Vector2D(0, radius), // In tangent + Vector2D(-controlPoint, radius))// Out tangent + .translated(Vector2D(radius, -radius)) + .translated(Vector2D(-size.x, size.y)) + .translated(position), + CurveVertex::absolute( + Vector2D(-radius, 0), // Point + Vector2D(-radius, controlPoint), // In tangent + Vector2D(-radius, 0)) // Out tangent + .translated(Vector2D(radius, -radius)) + .translated(Vector2D(-size.x, size.y)) + .translated(position), + /// Corner 3 + CurveVertex::absolute( + Vector2D(-radius, 0), // Point + Vector2D(-radius, 0), // In tangent + Vector2D(-radius, -controlPoint)) // Out tangent + .translated(Vector2D(radius, radius)) + .translated(Vector2D(-size.x, -size.y)) + .translated(position), + CurveVertex::absolute( + Vector2D(0, -radius), // Point + Vector2D(-controlPoint, -radius), // In tangent + Vector2D(0, -radius)) // Out tangent + .translated(Vector2D(radius, radius)) + .translated(Vector2D(-size.x, -size.y)) + .translated(position), + /// Corner 4 + CurveVertex::absolute( + Vector2D(0, -radius), // Point + Vector2D(0, -radius), // In tangent + Vector2D(controlPoint, -radius)) // Out tangent + .translated(Vector2D(-radius, radius)) + .translated(Vector2D(size.x, -size.y)) + .translated(position), + CurveVertex::absolute( + Vector2D(radius, 0), // Point + Vector2D(radius, -controlPoint), // In tangent + Vector2D(radius, 0)) // Out tangent + .translated(Vector2D(-radius, radius)) + .translated(Vector2D(size.x, -size.y)) + .translated(position) + }; + } + bool reversed = direction == PathDirection::CounterClockwise; + if (reversed) { + for (auto vertexIt = points.rbegin(); vertexIt != points.rend(); vertexIt++) { + bezierPath.addVertex((*vertexIt).reversed()); + } + } else { + for (auto vertexIt = points.begin(); vertexIt != points.end(); vertexIt++) { + bezierPath.addVertex(*vertexIt); + } + } + bezierPath.close(); + return bezierPath; +} + +/// Magic number needed for building path data +static constexpr double StarNodePolystarConstant = 0.47829; + +BezierPath makeStarBezierPath( + Vector2D const &position, + double outerRadius, + double innerRadius, + double inputOuterRoundedness, + double inputInnerRoundedness, + double numberOfPoints, + double rotation, + PathDirection direction +) { + double currentAngle = degreesToRadians(rotation - 90.0); + double anglePerPoint = (2.0 * M_PI) / numberOfPoints; + double halfAnglePerPoint = anglePerPoint / 2.0; + double partialPointAmount = numberOfPoints - floor(numberOfPoints); + double outerRoundedness = inputOuterRoundedness * 0.01; + double innerRoundedness = inputInnerRoundedness * 0.01; + + Vector2D point = Vector2D::Zero(); + + double partialPointRadius = 0.0; + if (partialPointAmount != 0.0) { + currentAngle += halfAnglePerPoint * (1 - partialPointAmount); + partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius); + point.x = (partialPointRadius * cos(currentAngle)); + point.y = (partialPointRadius * sin(currentAngle)); + currentAngle += anglePerPoint * partialPointAmount / 2; + } else { + point.x = (outerRadius * cos(currentAngle)); + point.y = (outerRadius * sin(currentAngle)); + currentAngle += halfAnglePerPoint; + } + + std::vector vertices; + vertices.push_back(CurveVertex::relative(point + position, Vector2D::Zero(), Vector2D::Zero())); + + Vector2D previousPoint = point; + bool longSegment = false; + int numPoints = (int)(ceil(numberOfPoints) * 2.0); + for (int i = 0; i < numPoints; i++) { + double radius = longSegment ? outerRadius : innerRadius; + double dTheta = halfAnglePerPoint; + if (partialPointRadius != 0.0 && i == numPoints - 2) { + dTheta = anglePerPoint * partialPointAmount / 2; + } + if (partialPointRadius != 0.0 && i == numPoints - 1) { + radius = partialPointRadius; + } + previousPoint = point; + point.x = (radius * cos(currentAngle)); + point.y = (radius * sin(currentAngle)); + + if (innerRoundedness == 0.0 && outerRoundedness == 0.0) { + vertices.push_back(CurveVertex::relative(point + position, Vector2D::Zero(), Vector2D::Zero())); + } else { + double cp1Theta = (atan2(previousPoint.y, previousPoint.x) - M_PI / 2.0); + double cp1Dx = cos(cp1Theta); + double cp1Dy = sin(cp1Theta); + + double cp2Theta = (atan2(point.y, point.x) - M_PI / 2.0); + double cp2Dx = cos(cp2Theta); + double cp2Dy = sin(cp2Theta); + + double cp1Roundedness = longSegment ? innerRoundedness : outerRoundedness; + double cp2Roundedness = longSegment ? outerRoundedness : innerRoundedness; + double cp1Radius = longSegment ? innerRadius : outerRadius; + double cp2Radius = longSegment ? outerRadius : innerRadius; + + Vector2D cp1( + cp1Radius * cp1Roundedness * StarNodePolystarConstant * cp1Dx, + cp1Radius * cp1Roundedness * StarNodePolystarConstant * cp1Dy + ); + Vector2D cp2( + cp2Radius * cp2Roundedness * StarNodePolystarConstant * cp2Dx, + cp2Radius * cp2Roundedness * StarNodePolystarConstant * cp2Dy + ); + if (partialPointAmount != 0.0) { + if (i == 0) { + cp1 = cp1 * partialPointAmount; + } else if (i == numPoints - 1) { + cp2 = cp2 * partialPointAmount; + } + } + auto previousVertex = vertices[vertices.size() - 1]; + vertices[vertices.size() - 1] = CurveVertex::absolute( + previousVertex.point, + previousVertex.inTangent, + previousVertex.point - cp1 + ); + vertices.push_back(CurveVertex::relative(point + position, cp2, Vector2D::Zero())); + } + currentAngle += dTheta; + longSegment = !longSegment; + } + + bool reverse = direction == PathDirection::CounterClockwise; + BezierPath path; + if (reverse) { + for (auto vertexIt = vertices.rbegin(); vertexIt != vertices.rend(); vertexIt++) { + path.addVertex((*vertexIt).reversed()); + } + } else { + for (auto vertexIt = vertices.begin(); vertexIt != vertices.end(); vertexIt++) { + path.addVertex(*vertexIt); + } + } + path.close(); + return path; +} + +CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, double end, double offset, TrimType type) { + /// No need to trim, it's a full path + if (start == 0.0 && end == 1.0) { + return sourcePath; + } + + /// All paths are empty. + if (start == end) { + return CompoundBezierPath(); + } + + if (type == TrimType::Simultaneously) { + CompoundBezierPath result; + + for (BezierPath &path : sourcePath.paths) { + CompoundBezierPath tempPath; + tempPath.appendPath(path); + + auto subPaths = tempPath.trim(start, end, offset); + + for (const auto &subPath : subPaths->paths) { + result.appendPath(subPath); + } + } + + return result; + } + + /// Individual path trimming. + + /// Brace yourself for the below code. + + /// Normalize lengths with offset. + double startPosition = fmod(start + offset, 1.0); + double endPosition = fmod(end + offset, 1.0); + + if (startPosition < 0.0) { + startPosition = 1.0 + startPosition; + } + + if (endPosition < 0.0) { + endPosition = 1.0 + endPosition; + } + if (startPosition == 1.0) { + startPosition = 0.0; + } + if (endPosition == 0.0) { + endPosition = 1.0; + } + + /// First get the total length of all paths. + double totalLength = 0.0; + for (auto &upstreamPath : sourcePath.paths) { + totalLength += upstreamPath.length(); + } + + /// Now determine the start and end cut lengths + double startLength = startPosition * totalLength; + double endLength = endPosition * totalLength; + double pathStart = 0.0; + + CompoundBezierPath result; + + /// Now loop through all path containers + for (auto &pathContainer : sourcePath.paths) { + auto pathEnd = pathStart + pathContainer.length(); + + if (!isInRange(startLength, pathStart, pathEnd) && + isInRange(endLength, pathStart, pathEnd)) { + // pathStart|=======E----------------------|pathEnd + // Cut path components, removing after end. + + double pathCutLength = endLength - pathStart; + double subpathStart = 0.0; + double subpathEnd = subpathStart + pathContainer.length(); + if (pathCutLength < subpathEnd) { + /// This is the subpath that needs to be cut. + double cutLength = pathCutLength - subpathStart; + + CompoundBezierPath tempPath; + tempPath.appendPath(pathContainer); + auto newPaths = tempPath.trim(0, cutLength / pathContainer.length(), 0); + for (const auto &newPath : newPaths->paths) { + result.appendPath(newPath); + } + } else { + /// Add to container and move on + result.appendPath(pathContainer); + } + /*if (pathCutLength == subpathEnd) { + /// Right on the end. The next subpath is not included. Break. + break; + } + subpathStart = subpathEnd;*/ + } else if (!isInRange(endLength, pathStart, pathEnd) && + isInRange(startLength, pathStart, pathEnd)) { + // pathStart|-------S======================|pathEnd + // + + // Cut path components, removing before beginning. + double pathCutLength = startLength - pathStart; + // Clear paths from container + double subpathStart = 0.0; + double subpathEnd = subpathStart + pathContainer.length(); + + if (subpathStart < pathCutLength && pathCutLength < subpathEnd) { + /// This is the subpath that needs to be cut. + double cutLength = pathCutLength - subpathStart; + CompoundBezierPath tempPath; + tempPath.appendPath(pathContainer); + auto newPaths = tempPath.trim(cutLength / pathContainer.length(), 1, 0); + for (const auto &newPath : newPaths->paths) { + result.appendPath(newPath); + } + } else if (pathCutLength <= subpathStart) { + result.appendPath(pathContainer); + } + //subpathStart = subpathEnd; + } else if (isInRange(endLength, pathStart, pathEnd) && + isInRange(startLength, pathStart, pathEnd)) { + // pathStart|-------S============E---------|endLength + // pathStart|=====E----------------S=======|endLength + // trim from path beginning to endLength. + + // Cut path components, removing before beginnings. + double startCutLength = startLength - pathStart; + double endCutLength = endLength - pathStart; + + double subpathStart = 0.0; + + double subpathEnd = subpathStart + pathContainer.length(); + + if (!isInRange(startCutLength, subpathStart, subpathEnd) && + !isInRange(endCutLength, subpathStart, subpathEnd)) + { + // The whole path is included. Add + // S|==============================|E + result.appendPath(pathContainer); + } else if (isInRange(startCutLength, subpathStart, subpathEnd) && + !isInRange(endCutLength, subpathStart, subpathEnd)) { + /// The start of the path needs to be trimmed + // |-------S======================|E + double cutLength = startCutLength - subpathStart; + CompoundBezierPath tempPath; + tempPath.appendPath(pathContainer); + auto newPaths = tempPath.trim(cutLength / pathContainer.length(), 1, 0); + for (const auto &newPath : newPaths->paths) { + result.appendPath(newPath); + } + } else if (!isInRange(startCutLength, subpathStart, subpathEnd) && + isInRange(endCutLength, subpathStart, subpathEnd)) { + // S|=======E----------------------| + double cutLength = endCutLength - subpathStart; + CompoundBezierPath tempPath; + tempPath.appendPath(pathContainer); + auto newPaths = tempPath.trim(0, cutLength / pathContainer.length(), 0); + for (const auto &newPath : newPaths->paths) { + result.appendPath(newPath); + } + } else if (isInRange(startCutLength, subpathStart, subpathEnd) && + isInRange(endCutLength, subpathStart, subpathEnd)) { + // |-------S============E---------| + double cutFromLength = startCutLength - subpathStart; + double cutToLength = endCutLength - subpathStart; + CompoundBezierPath tempPath; + tempPath.appendPath(pathContainer); + auto newPaths = tempPath.trim( + cutFromLength / pathContainer.length(), + cutToLength / pathContainer.length(), + 0 + ); + for (const auto &newPath : newPaths->paths) { + result.appendPath(newPath); + } + } + } else if ((endLength <= pathStart && pathEnd <= startLength) || + (startLength <= pathStart && endLength <= pathStart) || + (pathEnd <= startLength && pathEnd <= endLength)) { + /// The Path needs to be cleared + } else { + result.appendPath(pathContainer); + } + + pathStart = pathEnd; + } + + return result; +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp new file mode 100644 index 00000000000..9d9d27b6122 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp @@ -0,0 +1,39 @@ +#ifndef BezierPaths_h +#define BezierPaths_h + +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include "Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp" +#include "Lottie/Private/Model/ShapeItems/Trim.hpp" + +namespace lottie { + +BezierPath makeEllipseBezierPath( + Vector2D const &size, + Vector2D const ¢er, + PathDirection direction +); + +BezierPath makeRectangleBezierPath( + Vector2D const &position, + Vector2D const &inputSize, + double cornerRadius, + PathDirection direction +); + +BezierPath makeStarBezierPath( + Vector2D const &position, + double outerRadius, + double innerRadius, + double inputOuterRoundedness, + double inputInnerRoundedness, + double numberOfPoints, + double rotation, + PathDirection direction +); + +CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, double end, double offset, TrimType type); + +} + +#endif /* BezierPaths_h */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/SolidCompositionLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/SolidCompositionLayer.swift new file mode 100644 index 00000000000..f1972d14d01 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/SolidCompositionLayer.swift @@ -0,0 +1,60 @@ +// +// SolidCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import QuartzCore + +final class LottieCAShapeLayer: CAShapeLayer { +} + +final class SolidCompositionLayer: CompositionLayer { + + // MARK: Lifecycle + + init(solid: SolidLayerModel) { + let components = solid.colorHex.hexColorComponents() + colorProperty = + NodeProperty(provider: SingleValueProvider(Color( + r: Double(components.red), + g: Double(components.green), + b: Double(components.blue), + a: 1))) + + super.init(layer: solid, size: .zero) + solidShape.path = CGPath(rect: CGRect(x: 0, y: 0, width: solid.width, height: solid.height), transform: nil) + contentsLayer.addSublayer(solidShape) + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? SolidCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + colorProperty = layer.colorProperty + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let colorProperty: NodeProperty? + let solidShape = LottieCAShapeLayer() + + override var keypathProperties: [String: AnyNodeProperty] { + guard let colorProperty = colorProperty else { return super.keypathProperties } + return [PropertyName.color.rawValue : colorProperty] + } + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates _: Bool) { + guard let colorProperty = colorProperty else { return } + colorProperty.update(frame: frame) + solidShape.fillColor = colorProperty.value.cgColorValue + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.cpp new file mode 100644 index 00000000000..5e773d361ba --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "TextCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp new file mode 100644 index 00000000000..57e05453457 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp @@ -0,0 +1,124 @@ +#ifndef TextCompositionLayer_hpp +#define TextCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Layers/TextLayerModel.hpp" +#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp" +#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp" + +namespace lottie { + +class TextCompositionLayer: public CompositionLayer { +public: + TextCompositionLayer(std::shared_ptr const &textLayer, std::shared_ptr textProvider, std::shared_ptr fontProvider) : + CompositionLayer(textLayer, Vector2D::Zero()) { + std::shared_ptr rootNode; + for (const auto &animator : textLayer->animators) { + rootNode = std::make_shared(rootNode, animator); + } + _rootNode = rootNode; + _textDocument = std::make_shared>(textLayer->text.keyframes); + + _textProvider = textProvider; + _fontProvider = fontProvider; + + //_contentsLayer->addSublayer(_textLayer); + + assert(false); + //self.textLayer.masksToBounds = false + //self.textLayer.isGeometryFlipped = true + + if (_rootNode) { + _childKeypaths.push_back(rootNode); + } + } + + std::shared_ptr const &textProvider() const { + return _textProvider; + } + void setTextProvider(std::shared_ptr const &textProvider) { + _textProvider = textProvider; + } + + std::shared_ptr const &fontProvider() const { + return _fontProvider; + } + void setFontProvider(std::shared_ptr const &fontProvider) { + _fontProvider = fontProvider; + } + + virtual void displayContentsWithFrame(double frame, bool forceUpdates) override { + if (!_textDocument) { + return; + } + + bool documentUpdate = _textDocument->hasUpdate(frame); + + bool animatorUpdate = false; + if (_rootNode) { + animatorUpdate = _rootNode->updateContents(frame, forceUpdates); + } + + if (!(documentUpdate || animatorUpdate)) { + return; + } + + if (_rootNode) { + _rootNode->rebuildOutputs(frame); + } + + assert(false); + /*// Get Text Attributes + let text = textDocument.value(frame: frame) as! TextDocument + let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue + let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0) + let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0 + let matrix = rootNode?.textOutputNode.xform ?? CATransform3DIdentity + let textString = textProvider.textFor(keypathName: keypathName, sourceText: text.text) + let ctFont = fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize)) + + // Set all of the text layer options + textLayer.text = textString + textLayer.font = ctFont + textLayer.alignment = text.justification.textAlignment + textLayer.lineHeight = CGFloat(text.lineHeight) + textLayer.tracking = tracking + + if let fillColor = rootNode?.textOutputNode.fillColor { + textLayer.fillColor = fillColor + } else if let fillColor = text.fillColorData?.cgColorValue { + textLayer.fillColor = fillColor + } else { + textLayer.fillColor = nil + } + + textLayer.preferredSize = text.textFrameSize?.sizeValue + textLayer.strokeOnTop = text.strokeOverFill ?? false + textLayer.strokeWidth = strokeWidth + textLayer.strokeColor = strokeColor + textLayer.sizeToFit() + + textLayer.opacity = Float(rootNode?.textOutputNode.opacity ?? 1) + textLayer.transform = CATransform3DIdentity + textLayer.position = text.textFramePosition?.pointValue ?? CGPoint.zero + textLayer.transform = matrix*/ + } + +public: + virtual bool isTextCompositionLayer() const override { + return true; + } + +private: + std::shared_ptr _rootNode; + std::shared_ptr> _textDocument; + + //std::shared_ptr _textLayer; + std::shared_ptr _textProvider; + std::shared_ptr _fontProvider; +}; + +} + +#endif /* TextCompositionLayer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift new file mode 100644 index 00000000000..0c219958b82 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift @@ -0,0 +1,149 @@ +// +// TextCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import CoreGraphics +import CoreText +import Foundation +import QuartzCore + +/// Needed for NSMutableParagraphStyle... +#if os(OSX) +import AppKit +#else +import UIKit +#endif + +extension TextJustification { + var textAlignment: NSTextAlignment { + switch self { + case .left: + return .left + case .right: + return .right + case .center: + return .center + } + } + + var caTextAlignement: CATextLayerAlignmentMode { + switch self { + case .left: + return .left + case .right: + return .right + case .center: + return .center + } + } +} + +// MARK: - TextCompositionLayer + +final class TextCompositionLayer: CompositionLayer { + + // MARK: Lifecycle + + init(textLayer: TextLayerModel, textProvider: AnimationTextProvider, fontProvider: AnimationFontProvider) { + var rootNode: TextAnimatorNode? + for animator in textLayer.animators { + rootNode = TextAnimatorNode(parentNode: rootNode, textAnimator: animator) + } + self.rootNode = rootNode + textDocument = KeyframeInterpolator(keyframes: textLayer.text.keyframes) + + self.textProvider = textProvider + self.fontProvider = fontProvider + + super.init(layer: textLayer, size: .zero) + contentsLayer.addSublayer(self.textLayer) + self.textLayer.masksToBounds = false + self.textLayer.isGeometryFlipped = true + + if let rootNode = rootNode { + childKeypaths.append(rootNode) + } + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? TextCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + rootNode = nil + textDocument = nil + + textProvider = DefaultTextProvider() + fontProvider = DefaultFontProvider() + + super.init(layer: layer) + } + + // MARK: Internal + + let rootNode: TextAnimatorNode? + let textDocument: KeyframeInterpolator? + + let textLayer = CoreTextRenderLayer() + var textProvider: AnimationTextProvider + var fontProvider: AnimationFontProvider + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + guard let textDocument = textDocument else { return } + + textLayer.contentsScale = renderScale + + let documentUpdate = textDocument.hasUpdate(frame: frame) + let animatorUpdate = rootNode?.updateContents(frame, forceLocalUpdate: forceUpdates) ?? false + guard documentUpdate == true || animatorUpdate == true else { return } + + rootNode?.rebuildOutputs(frame: frame) + + // Get Text Attributes + let text = textDocument.value(frame: frame) as! TextDocument + let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue + let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0) + let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0 + let matrix = rootNode?.textOutputNode.xform ?? CATransform3DIdentity + let textString = textProvider.textFor(keypathName: keypathName, sourceText: text.text) + let ctFont = fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize)) + + // Set all of the text layer options + textLayer.text = textString + textLayer.font = ctFont + textLayer.alignment = text.justification.textAlignment + textLayer.lineHeight = CGFloat(text.lineHeight) + textLayer.tracking = tracking + + if let fillColor = rootNode?.textOutputNode.fillColor { + textLayer.fillColor = fillColor + } else if let fillColor = text.fillColorData?.cgColorValue { + textLayer.fillColor = fillColor + } else { + textLayer.fillColor = nil + } + + textLayer.preferredSize = text.textFrameSize?.sizeValue + textLayer.strokeOnTop = text.strokeOverFill ?? false + textLayer.strokeWidth = strokeWidth + textLayer.strokeColor = strokeColor + textLayer.sizeToFit() + + textLayer.opacity = Float(rootNode?.textOutputNode.opacity ?? 1) + textLayer.transform = CATransform3DIdentity + textLayer.position = text.textFramePosition?.pointValue ?? CGPoint.zero + textLayer.transform = matrix + } + + override func updateRenderScale() { + super.updateRenderScale() + textLayer.contentsScale = renderScale + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.cpp new file mode 100644 index 00000000000..a205f16eba9 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.cpp @@ -0,0 +1,5 @@ +#include "MainThreadAnimationLayer.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp new file mode 100644 index 00000000000..f1e512c4d7f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp @@ -0,0 +1,272 @@ +#ifndef MainThreadAnimationLayer_hpp +#define MainThreadAnimationLayer_hpp + +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Public/ImageProvider/AnimationImageProvider.hpp" +#include "Lottie/Private/Model/Animation.hpp" +#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp" +#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp" +#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp" +#include "Lottie/Public/DynamicProperties/AnimationKeypath.hpp" + +namespace lottie { + +class BlankImageProvider: public AnimationImageProvider { +public: + std::shared_ptr imageForAsset(ImageAsset const &asset) { + return nullptr; + } +}; + +class MainThreadAnimationLayer: public CALayer { +public: + MainThreadAnimationLayer( + Animation const &animation, + std::shared_ptr const &imageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider + ) { + if (animation.assetLibrary) { + _layerImageProvider = std::make_shared(imageProvider, animation.assetLibrary->imageAssets); + } else { + std::map> imageAssets; + _layerImageProvider = std::make_shared(imageProvider, imageAssets); + } + + _layerTextProvider = std::make_shared(textProvider); + _layerFontProvider = std::make_shared(fontProvider); + + setBounds(CGRect(0.0, 0.0, animation.width, animation.height)); + + auto layers = initializeCompositionLayers( + animation.layers, + animation.assetLibrary, + _layerImageProvider, + textProvider, + fontProvider, + animation.framerate + ); + + std::vector> imageLayers; + std::vector> textLayers; + + std::shared_ptr mattedLayer; + + for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) { + std::shared_ptr const &layer = *layerIt; + layer->setBounds(bounds()); + _animationLayers.push_back(layer); + + if (layer->isImageCompositionLayer()) { + imageLayers.push_back(std::static_pointer_cast(layer)); + } + if (layer->isTextCompositionLayer()) { + textLayers.push_back(std::static_pointer_cast(layer)); + } + + if (mattedLayer) { + /// The previous layer requires this layer to be its matte + mattedLayer->setMatteLayer(layer); + mattedLayer = nullptr; + continue; + } + if (layer->matteType().has_value() && (layer->matteType() == MatteType::Add || layer->matteType() == MatteType::Invert)) { + /// We have a layer that requires a matte. + mattedLayer = layer; + } + addSublayer(layer); + } + + _layerImageProvider->addImageLayers(imageLayers); + _layerImageProvider->reloadImages(); + _layerTextProvider->addTextLayers(textLayers); + _layerTextProvider->reloadTexts(); + _layerFontProvider->addTextLayers(textLayers); + _layerFontProvider->reloadTexts(); + + setNeedsDisplay(true); + } + + void setRespectAnimationFrameRate(bool respectAnimationFrameRate) { + _respectAnimationFrameRate = respectAnimationFrameRate; + } + + void display() { + double newFrame = currentFrame(); + if (_respectAnimationFrameRate) { + newFrame = floor(newFrame); + } + for (const auto &layer : _animationLayers) { + layer->displayWithFrame(newFrame, false); + } + } + + std::vector> const &animationLayers() const { + return _animationLayers; + } + + void reloadImages() { + _layerImageProvider->reloadImages(); + } + + /// Forces the view to update its drawing. + void forceDisplayUpdate() { + for (const auto &layer : _animationLayers) { + layer->displayWithFrame(currentFrame(), true); + } + } + + void logHierarchyKeypaths() { + printf("Lottie: Logging Animation Keypaths\n"); + assert(false); + //animationLayers.forEach({ $0.logKeypaths(for: nil) }) + } + + void setValueProvider(std::shared_ptr const &valueProvider, AnimationKeypath const &keypath) { + for (const auto &layer : _animationLayers) { + assert(false); + /*if let foundProperties = layer.nodeProperties(for: keypath) { + for property in foundProperties { + property.setProvider(provider: valueProvider) + } + layer.displayWithFrame(frame: presentation()?.currentFrame ?? currentFrame, forceUpdates: true) + }*/ + } + } + + std::optional getValue(AnimationKeypath const &keypath, std::optional atFrame) { + for (const auto &layer : _animationLayers) { + assert(false); + /*if + let foundProperties = layer.nodeProperties(for: keypath), + let first = foundProperties.first + { + return first.valueProvider.value(frame: atFrame ?? currentFrame) + }*/ + } + return std::nullopt; + } + + std::optional getOriginalValue(AnimationKeypath const &keypath, std::optional atFrame) { + for (const auto &layer : _animationLayers) { + assert(false); + /*if + let foundProperties = layer.nodeProperties(for: keypath), + let first = foundProperties.first + { + return first.originalValueProvider.value(frame: atFrame ?? currentFrame) + }*/ + } + return std::nullopt; + } + + std::shared_ptr layerForKeypath(AnimationKeypath const &keyPath) { + assert(false); + /*for layer in animationLayers { + if let foundLayer = layer.layer(for: keypath) { + return foundLayer + } + }*/ + return nullptr; + } + + std::vector> animatorNodesForKeypath(AnimationKeypath const &keypath) { + std::vector> results; + /*for (const auto &layer : _animationLayers) { + if let nodes = layer.animatorNodes(for: keypath) { + results.append(contentsOf: nodes) + } + }*/ + return results; + } + + double currentFrame() const { + return _currentFrame; + } + void setCurrentFrame(double currentFrame) { + _currentFrame = currentFrame; + + for (size_t i = 0; i < _animationLayers.size(); i++) { + _animationLayers[i]->displayWithFrame(_currentFrame, false); + } + } + + std::shared_ptr imageProvider() const { + return _layerImageProvider->imageProvider(); + } + void setImageProvider(std::shared_ptr const &imageProvider) { + _layerImageProvider->setImageProvider(imageProvider); + } + + std::shared_ptr textProvider() const { + return _layerTextProvider->textProvider(); + } + void setTextProvider(std::shared_ptr const &textProvider) { + _layerTextProvider->setTextProvider(textProvider); + } + + std::shared_ptr fontProvider() const { + return _layerFontProvider->fontProvider(); + } + void setFontProvider(std::shared_ptr const &fontProvider) { + _layerFontProvider->setFontProvider(fontProvider); + } + + virtual std::shared_ptr renderTreeNode() { + std::vector> subnodes; + for (const auto &animationLayer : _animationLayers) { + bool found = false; + for (const auto &sublayer : sublayers()) { + if (animationLayer == sublayer) { + found = true; + break; + } + } + if (found) { + auto node = animationLayer->renderTreeNode(); + if (node) { + subnodes.push_back(node); + } + } + } + + return std::make_shared( + bounds(), + position(), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + subnodes, + nullptr, + false + ); + } + +private: + // MARK: Internal + + /// The animatable Current Frame Property + double _currentFrame = 0.0; + + std::shared_ptr _imageProvider; + std::shared_ptr _textProvider; + std::shared_ptr _fontProvider; + + bool _respectAnimationFrameRate = true; + + std::vector> _animationLayers; + + std::shared_ptr _layerImageProvider; + std::shared_ptr _layerTextProvider; + std::shared_ptr _layerFontProvider; +}; + +} + +#endif /* MainThreadAnimationLayer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift new file mode 100644 index 00000000000..32a133f8463 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift @@ -0,0 +1,279 @@ +// +// MainThreadAnimationLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/24/19. +// + +import Foundation +import QuartzCore + +// MARK: - MainThreadAnimationLayer + +/// The base `CALayer` for the Main Thread rendering engine +/// +/// This layer holds a single composition container and allows for animation of +/// the currentFrame property. +public final class MainThreadAnimationLayer: CALayer, RootAnimationLayer { + + // MARK: Lifecycle + +public init( + animation: Animation, + imageProvider: AnimationImageProvider, + textProvider: AnimationTextProvider, + fontProvider: AnimationFontProvider) + { + layerImageProvider = LayerImageProvider(imageProvider: imageProvider, assets: animation.assetLibrary?.imageAssets) + layerTextProvider = LayerTextProvider(textProvider: textProvider) + layerFontProvider = LayerFontProvider(fontProvider: fontProvider) + animationLayers = [] + super.init() + bounds = animation.bounds + let layers = animation.layers.initializeCompositionLayers( + assetLibrary: animation.assetLibrary, + layerImageProvider: layerImageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + frameRate: CGFloat(animation.framerate)) + + var imageLayers = [ImageCompositionLayer]() + var textLayers = [TextCompositionLayer]() + + var mattedLayer: CompositionLayer? = nil + + for layer in layers.reversed() { + layer.bounds = bounds + animationLayers.append(layer) + if let imageLayer = layer as? ImageCompositionLayer { + imageLayers.append(imageLayer) + } + if let textLayer = layer as? TextCompositionLayer { + textLayers.append(textLayer) + } + if let matte = mattedLayer { + /// The previous layer requires this layer to be its matte + matte.matteLayer = layer + mattedLayer = nil + continue + } + if + let matte = layer.matteType, + matte == .add || matte == .invert + { + /// We have a layer that requires a matte. + mattedLayer = layer + } + addSublayer(layer) + } + + layerImageProvider.addImageLayers(imageLayers) + layerImageProvider.reloadImages() + layerTextProvider.addTextLayers(textLayers) + layerTextProvider.reloadTexts() + layerFontProvider.addTextLayers(textLayers) + layerFontProvider.reloadTexts() + setNeedsDisplay() + } + + /// For CAAnimation Use + public override init(layer: Any) { + animationLayers = [] + layerImageProvider = LayerImageProvider(imageProvider: BlankImageProvider(), assets: nil) + layerTextProvider = LayerTextProvider(textProvider: DefaultTextProvider()) + layerFontProvider = LayerFontProvider(fontProvider: DefaultFontProvider()) + super.init(layer: layer) + + guard let animationLayer = layer as? MainThreadAnimationLayer else { return } + + currentFrame = animationLayer.currentFrame + + } + + required public init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Public + + public var respectAnimationFrameRate = false + + // MARK: CALayer Animations + + override public class func needsDisplay(forKey key: String) -> Bool { + if key == "currentFrame" { + return true + } + return super.needsDisplay(forKey: key) + } + + override public func action(forKey event: String) -> CAAction? { + if event == "currentFrame" { + let animation = CABasicAnimation(keyPath: event) + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + animation.fromValue = presentation()?.currentFrame + return animation + } + return super.action(forKey: event) + } + + public override func display() { + guard Thread.isMainThread else { return } + var newFrame: CGFloat + if + let animationKeys = animationKeys(), + !animationKeys.isEmpty + { + newFrame = presentation()?.currentFrame ?? currentFrame + } else { + // We ignore the presentation's frame if there's no animation in the layer. + newFrame = currentFrame + } + if respectAnimationFrameRate { + newFrame = floor(newFrame) + } + animationLayers.forEach { $0.displayWithFrame(frame: newFrame, forceUpdates: false) } + } + + // MARK: Internal + + /// The animatable Current Frame Property + @NSManaged public var currentFrame: CGFloat + + var animationLayers: ContiguousArray + + var primaryAnimationKey: AnimationKey { + .managed + } + + var isAnimationPlaying: Bool? { + nil // this state is managed by `AnimationView` + } + + var _animationLayers: [CALayer] { + Array(animationLayers) + } + + var imageProvider: AnimationImageProvider { + get { + layerImageProvider.imageProvider + } + set { + layerImageProvider.imageProvider = newValue + } + } + + var renderScale: CGFloat = 1 { + didSet { + animationLayers.forEach({ $0.renderScale = renderScale }) + } + } + + var textProvider: AnimationTextProvider { + get { layerTextProvider.textProvider } + set { layerTextProvider.textProvider = newValue } + } + + var fontProvider: AnimationFontProvider { + get { layerFontProvider.fontProvider } + set { layerFontProvider.fontProvider = newValue } + } + + func reloadImages() { + layerImageProvider.reloadImages() + } + + func removeAnimations() { + // no-op, since the primary animation is managed by the `AnimationView`. + } + + /// Forces the view to update its drawing. + func forceDisplayUpdate() { + animationLayers.forEach( { $0.displayWithFrame(frame: currentFrame, forceUpdates: true) }) + } + + public func displayUpdate() { + for i in 0 ..< animationLayers.count { + animationLayers[i].displayWithFrame(frame: currentFrame, forceUpdates: false) + } + } + + func logHierarchyKeypaths() { + print("Lottie: Logging Animation Keypaths") + animationLayers.forEach({ $0.logKeypaths(for: nil) }) + } + + func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + for layer in animationLayers { + if let foundProperties = layer.nodeProperties(for: keypath) { + for property in foundProperties { + property.setProvider(provider: valueProvider) + } + layer.displayWithFrame(frame: presentation()?.currentFrame ?? currentFrame, forceUpdates: true) + } + } + } + + func getValue(for keypath: AnimationKeypath, atFrame: CGFloat?) -> Any? { + for layer in animationLayers { + if + let foundProperties = layer.nodeProperties(for: keypath), + let first = foundProperties.first + { + return first.valueProvider.value(frame: atFrame ?? currentFrame) + } + } + return nil + } + + func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? { + for layer in animationLayers { + if + let foundProperties = layer.nodeProperties(for: keypath), + let first = foundProperties.first + { + return first.originalValueProvider.value(frame: atFrame ?? currentFrame) + } + } + return nil + } + + func layer(for keypath: AnimationKeypath) -> CALayer? { + for layer in animationLayers { + if let foundLayer = layer.layer(for: keypath) { + return foundLayer + } + } + return nil + } + + func animatorNodes(for keypath: AnimationKeypath) -> [AnimatorNode]? { + var results = [AnimatorNode]() + for layer in animationLayers { + if let nodes = layer.animatorNodes(for: keypath) { + results.append(contentsOf: nodes) + } + } + if results.count == 0 { + return nil + } + return results + } + + // MARK: Fileprivate + + fileprivate let layerImageProvider: LayerImageProvider + fileprivate let layerTextProvider: LayerTextProvider + fileprivate let layerFontProvider: LayerFontProvider +} + +// MARK: - BlankImageProvider + +public class BlankImageProvider: AnimationImageProvider { + public init() { + } + + public func imageForAsset(asset _: ImageAsset) -> CGImage? { + nil + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift new file mode 100644 index 00000000000..d2fa02cd97a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift @@ -0,0 +1,47 @@ +// Created by Jianjun Wu on 2022/5/12. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import CoreGraphics +import Foundation + +// MARK: - CachedImageProvider + +private final class CachedImageProvider: AnimationImageProvider { + + // MARK: Lifecycle + + /// Initializes an image provider with an image provider + /// + /// - Parameter imageProvider: The provider to load image from asset + /// + public init(imageProvider: AnimationImageProvider) { + self.imageProvider = imageProvider + } + + // MARK: Public + + public func imageForAsset(asset: ImageAsset) -> CGImage? { + if let image = imageCache.object(forKey: asset.id as NSString) { + return image + } + if let image = imageProvider.imageForAsset(asset: asset) { + imageCache.setObject(image, forKey: asset.id as NSString) + return image + } + return nil + } + + // MARK: Internal + + let imageCache: NSCache = .init() + let imageProvider: AnimationImageProvider +} + +extension AnimationImageProvider { + /// Create a cache enabled image provider which will reuse the asset image with the same asset id + /// It wraps the current provider as image loader, and uses `NSCache` to cache the images for resue. + /// The cache will be reset when the `animation` is reset. + var cachedImageProvider: AnimationImageProvider { + CachedImageProvider(imageProvider: self) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp new file mode 100644 index 00000000000..bec737cb0ac --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp @@ -0,0 +1,112 @@ +#include "CompositionLayersInitializer.hpp" + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp" + +namespace lottie { + +std::vector> initializeCompositionLayers( + std::vector> const &layers, + std::shared_ptr const &assetLibrary, + std::shared_ptr const &layerImageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider, + double frameRate +) { + std::vector> compositionLayers; + std::map> layerMap; + + /// Organize the assets into a dictionary of [ID : ImageAsset] + std::vector> childLayers; + + for (const auto &layer : layers) { + if (layer->hidden) { + auto genericLayer = std::make_shared(layer); + compositionLayers.push_back(genericLayer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), genericLayer)); + } + } else if (layer->type == LayerType::Shape) { + auto shapeContainer = std::make_shared(std::static_pointer_cast(layer)); + compositionLayers.push_back(shapeContainer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), shapeContainer)); + } + } else if (layer->type == LayerType::Solid) { + auto shapeContainer = std::make_shared(std::static_pointer_cast(layer)); + compositionLayers.push_back(shapeContainer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), shapeContainer)); + } + } else if (layer->type == LayerType::Precomp && assetLibrary) { + auto precompLayer = std::static_pointer_cast(layer); + auto precompAssetIt = assetLibrary->precompAssets.find(precompLayer->referenceID); + if (precompAssetIt != assetLibrary->precompAssets.end()) { + auto precompContainer = std::make_shared( + precompLayer, + *(precompAssetIt->second), + layerImageProvider, + textProvider, + fontProvider, + assetLibrary, + frameRate + ); + compositionLayers.push_back(precompContainer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), precompContainer)); + } + } + } else if (layer->type == LayerType::Image && assetLibrary) { + auto imageLayer = std::static_pointer_cast(layer); + auto imageAssetIt = assetLibrary->imageAssets.find(imageLayer->referenceID); + if (imageAssetIt != assetLibrary->imageAssets.end()) { + auto imageContainer = std::make_shared( + imageLayer, + Vector2D((*imageAssetIt->second).width, (*imageAssetIt->second).height) + ); + compositionLayers.push_back(imageContainer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), imageContainer)); + } + } + } else if (layer->type == LayerType::Text) { + auto textContainer = std::make_shared(std::static_pointer_cast(layer), textProvider, fontProvider); + compositionLayers.push_back(textContainer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), textContainer)); + } + } else { + auto genericLayer = std::make_shared(layer); + compositionLayers.push_back(genericLayer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), genericLayer)); + } + } + if (layer->parent) { + childLayers.push_back(layer); + } + } + + /// Now link children with their parents + for (const auto &layerModel : childLayers) { + if (!layerModel->index.has_value()) { + continue; + } + if (const auto parentID = layerModel->parent) { + auto childLayerIt = layerMap.find(layerModel->index.value()); + if (childLayerIt != layerMap.end()) { + auto parentLayerIt = layerMap.find(parentID.value()); + if (parentLayerIt != layerMap.end()) { + childLayerIt->second->transformNode()->setParentNode(parentLayerIt->second->transformNode()); + } + } + } + } + + return compositionLayers; +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp new file mode 100644 index 00000000000..247d8d56312 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp @@ -0,0 +1,23 @@ +#ifndef CompositionLayersInitializer_hpp +#define CompositionLayersInitializer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Assets/AssetLibrary.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp" +#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp" +#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp" + +namespace lottie { + +std::vector> initializeCompositionLayers( + std::vector> const &layers, + std::shared_ptr const &assetLibrary, + std::shared_ptr const &layerImageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider, + double frameRate +); + +} + +#endif /* CompositionLayersInitializer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift new file mode 100644 index 00000000000..fb4aa2b3875 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift @@ -0,0 +1,90 @@ +// +// CompositionLayersInitializer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import CoreGraphics +import Foundation + +extension Array where Element == LayerModel { + + func initializeCompositionLayers( + assetLibrary: AssetLibrary?, + layerImageProvider: LayerImageProvider, + textProvider: AnimationTextProvider, + fontProvider: AnimationFontProvider, + frameRate: CGFloat) -> [CompositionLayer] + { + var compositionLayers = [CompositionLayer]() + var layerMap = [Int : CompositionLayer]() + + /// Organize the assets into a dictionary of [ID : ImageAsset] + var childLayers = [LayerModel]() + + for layer in self { + if layer.hidden == true { + let genericLayer = NullCompositionLayer(layer: layer) + compositionLayers.append(genericLayer) + layerMap[layer.index] = genericLayer + } else if let shapeLayer = layer as? ShapeLayerModel { + let shapeContainer = ShapeCompositionLayer(shapeLayer: shapeLayer) + compositionLayers.append(shapeContainer) + layerMap[layer.index] = shapeContainer + } else if let solidLayer = layer as? SolidLayerModel { + let solidContainer = SolidCompositionLayer(solid: solidLayer) + compositionLayers.append(solidContainer) + layerMap[layer.index] = solidContainer + } else if + let precompLayer = layer as? PreCompLayerModel, + let assetLibrary = assetLibrary, + let precompAsset = assetLibrary.precompAssets[precompLayer.referenceID] + { + let precompContainer = PreCompositionLayer( + precomp: precompLayer, + asset: precompAsset, + layerImageProvider: layerImageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + assetLibrary: assetLibrary, + frameRate: frameRate) + compositionLayers.append(precompContainer) + layerMap[layer.index] = precompContainer + } else if + let imageLayer = layer as? ImageLayerModel, + let assetLibrary = assetLibrary, + let imageAsset = assetLibrary.imageAssets[imageLayer.referenceID] + { + let imageContainer = ImageCompositionLayer( + imageLayer: imageLayer, + size: CGSize(width: imageAsset.width, height: imageAsset.height)) + compositionLayers.append(imageContainer) + layerMap[layer.index] = imageContainer + } else if let textLayer = layer as? TextLayerModel { + let textContainer = TextCompositionLayer(textLayer: textLayer, textProvider: textProvider, fontProvider: fontProvider) + compositionLayers.append(textContainer) + layerMap[layer.index] = textContainer + } else { + let genericLayer = NullCompositionLayer(layer: layer) + compositionLayers.append(genericLayer) + layerMap[layer.index] = genericLayer + } + if layer.parent != nil { + childLayers.append(layer) + } + } + + /// Now link children with their parents + for layerModel in childLayers { + if let parentID = layerModel.parent { + let childLayer = layerMap[layerModel.index] + let parentLayer = layerMap[parentID] + childLayer?.transformNode.parentNode = parentLayer?.transformNode + } + } + + return compositionLayers + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift new file mode 100644 index 00000000000..63a3a6b81cc --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift @@ -0,0 +1,320 @@ +// +// TextLayer.swift +// Pods +// +// Created by Brandon Withrow on 8/3/20. +// + +import CoreGraphics +import CoreText +import Foundation +import QuartzCore +/// Needed for NSMutableParagraphStyle... +#if os(OSX) +import AppKit +#else +import UIKit +#endif + +// MARK: - CoreTextRenderLayer + +/// A CALayer subclass that renders text content using CoreText +final class CoreTextRenderLayer: CALayer, LottieDrawingLayer { + + // MARK: Public + + public var text: String? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var font: CTFont? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var alignment: NSTextAlignment = .left { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var lineHeight: CGFloat = 0 { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var tracking: CGFloat = 0 { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var fillColor: CGColor? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var strokeColor: CGColor? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var strokeWidth: CGFloat = 0 { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var strokeOnTop = false { + didSet { + setNeedsLayout() + setNeedsDisplay() + } + } + + public var preferredSize: CGSize? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public func sizeToFit() { + updateTextContent() + bounds = drawingRect + anchorPoint = drawingAnchor + setNeedsLayout() + setNeedsDisplay() + } + + // MARK: Internal + + override func action(forKey _: String) -> CAAction? { + nil + } + + override func draw(in ctx: CGContext) { + guard let attributedString = attributedString else { return } + updateTextContent() + guard fillFrameSetter != nil || strokeFrameSetter != nil else { return } + + ctx.textMatrix = .identity + ctx.setAllowsAntialiasing(true) + ctx.setAllowsFontSubpixelPositioning(true) + ctx.setAllowsFontSubpixelQuantization(true) + + ctx.setShouldAntialias(true) + ctx.setShouldSubpixelPositionFonts(true) + ctx.setShouldSubpixelQuantizeFonts(true) + + if contentsAreFlipped() { + ctx.translateBy(x: 0, y: drawingRect.height) + ctx.scaleBy(x: 1.0, y: -1.0) + } + + let drawingPath = CGPath(rect: drawingRect, transform: nil) + + let fillFrame: CTFrame? + if let setter = fillFrameSetter { + fillFrame = CTFramesetterCreateFrame(setter, CFRangeMake(0, attributedString.length), drawingPath, nil) + } else { + fillFrame = nil + } + + let strokeFrame: CTFrame? + if let setter = strokeFrameSetter { + strokeFrame = CTFramesetterCreateFrame(setter, CFRangeMake(0, attributedString.length), drawingPath, nil) + } else { + strokeFrame = nil + } + + if !strokeOnTop, let strokeFrame = strokeFrame { + CTFrameDraw(strokeFrame, ctx) + } + + if let fillFrame = fillFrame { + CTFrameDraw(fillFrame, ctx) + } + + if strokeOnTop, let strokeFrame = strokeFrame { + CTFrameDraw(strokeFrame, ctx) + } + } + + // MARK: Private + + private var drawingRect: CGRect = .zero + private var drawingAnchor: CGPoint = .zero + private var fillFrameSetter: CTFramesetter? + private var attributedString: NSAttributedString? + private var strokeFrameSetter: CTFramesetter? + private var needsContentUpdate = false + + // Draws Debug colors for the font alignment. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + private func drawDebug(_ ctx: CGContext) { + if let font = font { + let ascent = CTFontGetAscent(font) + let descent = CTFontGetDescent(font) + let capHeight = CTFontGetCapHeight(font) + let leading = CTFontGetLeading(font) + + // Ascent Red + ctx.setFillColor(CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 0.5)) + ctx.fill(CGRect(x: 0, y: 0, width: drawingRect.width, height: ascent)) + + // Descent Blue + ctx.setFillColor(CGColor(srgbRed: 0, green: 0, blue: 1, alpha: 0.5)) + ctx.fill(CGRect(x: 0, y: ascent, width: drawingRect.width, height: descent)) + + // Leading Yellow + ctx.setFillColor(CGColor(srgbRed: 1, green: 1, blue: 0, alpha: 0.5)) + ctx.fill(CGRect(x: 0, y: ascent + descent, width: drawingRect.width, height: leading)) + + // Cap height Green + ctx.setFillColor(CGColor(srgbRed: 0, green: 1, blue: 0, alpha: 0.5)) + ctx.fill(CGRect(x: 0, y: ascent - capHeight, width: drawingRect.width, height: capHeight)) + + if drawingRect.height - ascent + descent + leading > 0 { + // Remainder + ctx.setFillColor(CGColor(srgbRed: 0, green: 1, blue: 1, alpha: 0.5)) + ctx + .fill(CGRect( + x: 0, + y: ascent + descent + leading, + width: drawingRect.width, + height: drawingRect.height - ascent + descent + leading)) + } + } + } + + private func updateTextContent() { + guard needsContentUpdate else { return } + needsContentUpdate = false + guard let font = font, let text = text, text.count > 0, fillColor != nil || strokeColor != nil else { + drawingRect = .zero + drawingAnchor = .zero + attributedString = nil + fillFrameSetter = nil + strokeFrameSetter = nil + return + } + + // Get Font properties + let ascent = CTFontGetAscent(font) + let descent = CTFontGetDescent(font) + let capHeight = CTFontGetCapHeight(font) + let leading = CTFontGetLeading(font) + let minLineHeight = -(ascent + descent + leading) + + // Calculate line spacing + let lineSpacing = max(CGFloat(minLineHeight) + lineHeight, CGFloat(minLineHeight)) + // Build Attributes + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = lineSpacing + paragraphStyle.lineHeightMultiple = 1 + paragraphStyle.maximumLineHeight = ascent + descent + leading + paragraphStyle.alignment = alignment + paragraphStyle.lineBreakMode = NSLineBreakMode.byWordWrapping + var attributes: [NSAttributedString.Key: Any] = [ + NSAttributedString.Key.ligature: 0, + NSAttributedString.Key.font: font, + NSAttributedString.Key.kern: tracking, + NSAttributedString.Key.paragraphStyle: paragraphStyle, + ] + + if let fillColor = fillColor { + attributes[NSAttributedString.Key.foregroundColor] = fillColor + } + + let attrString = NSAttributedString(string: text, attributes: attributes) + attributedString = attrString + + if fillColor != nil { + let setter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString) + fillFrameSetter = setter + } else { + fillFrameSetter = nil + } + + if let strokeColor = strokeColor { + attributes[NSAttributedString.Key.foregroundColor] = nil + attributes[NSAttributedString.Key.strokeWidth] = strokeWidth + attributes[NSAttributedString.Key.strokeColor] = strokeColor + let strokeAttributedString = NSAttributedString(string: text, attributes: attributes) + strokeFrameSetter = CTFramesetterCreateWithAttributedString(strokeAttributedString as CFAttributedString) + } else { + strokeFrameSetter = nil + strokeWidth = 0 + } + + guard let setter = fillFrameSetter ?? strokeFrameSetter else { + return + } + + // Calculate drawing size and anchor offset + let textAnchor: CGPoint + if let preferredSize = preferredSize { + drawingRect = CGRect(origin: .zero, size: preferredSize) + drawingRect.size.height += (ascent - capHeight) + drawingRect.size.height += descent + textAnchor = CGPoint(x: 0, y: ascent - capHeight) + } else { + let size = CTFramesetterSuggestFrameSizeWithConstraints( + setter, + CFRange(location: 0, length: attrString.length), + nil, + CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), + nil) + switch alignment { + case .left: + textAnchor = CGPoint(x: 0, y: ascent) + case .right: + textAnchor = CGPoint(x: size.width, y: ascent) + case .center: + textAnchor = CGPoint(x: size.width * 0.5, y: ascent) + default: + textAnchor = .zero + } + drawingRect = CGRect( + x: 0, + y: 0, + width: ceil(size.width), + height: ceil(size.height)) + } + + // Now Calculate Anchor + drawingAnchor = CGPoint( + x: textAnchor.x.remap(fromLow: 0, fromHigh: drawingRect.size.width, toLow: 0, toHigh: 1), + y: textAnchor.y.remap(fromLow: 0, fromHigh: drawingRect.size.height, toLow: 0, toHigh: 1)) + + if fillFrameSetter != nil && strokeFrameSetter != nil { + drawingRect.size.width += strokeWidth + drawingRect.size.height += strokeWidth + } + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/InvertedMatteLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/InvertedMatteLayer.swift new file mode 100644 index 00000000000..7e1bc387aac --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/InvertedMatteLayer.swift @@ -0,0 +1,59 @@ +// +// InvertedMatteLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/28/19. +// + +import Foundation +import QuartzCore + +/// A layer that inverses the alpha output of its input layer. +/// +/// WARNING: This is experimental and probably not very performant. +final class InvertedMatteLayer: CALayer, CompositionLayerDelegate, LottieDrawingLayer { + + // MARK: Lifecycle + + init(inputMatte: CompositionLayer) { + self.inputMatte = inputMatte + super.init() + inputMatte.layerDelegate = self + anchorPoint = .zero + bounds = inputMatte.bounds + setNeedsDisplay() + } + + override init(layer: Any) { + guard let layer = layer as? InvertedMatteLayer else { + fatalError("init(layer:) wrong class.") + } + inputMatte = nil + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let inputMatte: CompositionLayer? + let wrapperLayer = CALayer() + + func frameUpdated(frame _: CGFloat) { + setNeedsDisplay() + displayIfNeeded() + } + + override func draw(in ctx: CGContext) { + guard let inputMatte = inputMatte else { return } + guard let fillColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 1]) + else { return } + ctx.setFillColor(fillColor) + ctx.fill(bounds) + ctx.setBlendMode(.destinationOut) + inputMatte.render(in: ctx) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.cpp new file mode 100644 index 00000000000..d6123ee219d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.cpp @@ -0,0 +1,5 @@ +#include "LayerFontProvider.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp new file mode 100644 index 00000000000..ff9eaae19cf --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp @@ -0,0 +1,45 @@ +#ifndef LayerFontProvider_hpp +#define LayerFontProvider_hpp + +#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp" + +namespace lottie { + +/// Connects a LottieFontProvider to a group of text layers +class LayerFontProvider { +public: + LayerFontProvider(std::shared_ptr const &fontProvider) { + _fontProvider = fontProvider; + reloadTexts(); + } + + std::shared_ptr const &fontProvider() const { + return _fontProvider; + } + void setFontProvider(std::shared_ptr const &fontProvider) { + _fontProvider = fontProvider; + reloadTexts(); + } + + void addTextLayers(std::vector> const &layers) { + for (const auto &layer : layers) { + _textLayers.push_back(layer); + } + } + + void reloadTexts() { + for (const auto &layer : _textLayers) { + layer->setFontProvider(_fontProvider); + } + } + +private: + std::vector> _textLayers; + + std::shared_ptr _fontProvider; +}; + +} + +#endif /* LayerFontProvider_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift new file mode 100644 index 00000000000..902f438dc96 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift @@ -0,0 +1,41 @@ +// +// LayerFontProvider.swift +// Lottie +// +// Created by Brandon Withrow on 8/5/20. +// Copyright © 2020 YurtvilleProds. All rights reserved. +// + +import Foundation + +/// Connects a LottieFontProvider to a group of text layers +final class LayerFontProvider { + + // MARK: Lifecycle + + init(fontProvider: AnimationFontProvider) { + self.fontProvider = fontProvider + textLayers = [] + reloadTexts() + } + + // MARK: Internal + + private(set) var textLayers: [TextCompositionLayer] + + var fontProvider: AnimationFontProvider { + didSet { + reloadTexts() + } + } + + func addTextLayers(_ layers: [TextCompositionLayer]) { + textLayers += layers + } + + func reloadTexts() { + textLayers.forEach { + $0.fontProvider = fontProvider + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.cpp new file mode 100644 index 00000000000..536c0437493 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.cpp @@ -0,0 +1,5 @@ +#include "LayerImageProvider.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp new file mode 100644 index 00000000000..d9affbd1cf1 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp @@ -0,0 +1,58 @@ +#ifndef LayerImageProvider_hpp +#define LayerImageProvider_hpp + +#include "Lottie/Public/ImageProvider/AnimationImageProvider.hpp" +#include "Lottie/Private/Model/Assets/ImageAsset.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp" + +namespace lottie { + +/// Connects a LottieImageProvider to a group of image layers +class LayerImageProvider { +public: + LayerImageProvider(std::shared_ptr const &imageProvider, std::map> const &assets) : + _imageProvider(imageProvider), + _imageAssets(assets) { + reloadImages(); + } + + std::shared_ptr imageProvider() const { + return _imageProvider; + } + void setImageProvider(std::shared_ptr const &imageProvider) { + _imageProvider = imageProvider; + reloadImages(); + } + + std::vector> const &imageLayers() const { + return _imageLayers; + } + + void addImageLayers(std::vector> const &layers) { + for (const auto &layer : layers) { + auto it = _imageAssets.find(layer->imageReferenceID()); + if (it != _imageAssets.end()) { + _imageLayers.push_back(layer); + } + } + } + + void reloadImages() { + for (const auto &imageLayer : imageLayers()) { + auto it = _imageAssets.find(imageLayer->imageReferenceID()); + if (it != _imageAssets.end()) { + imageLayer->setImage(_imageProvider->imageForAsset(*it->second)); + } + } + } + +private: + std::shared_ptr _imageProvider; + std::vector> _imageLayers; + + std::map> _imageAssets; +}; + +} + +#endif /* LayerImageProvider_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift new file mode 100644 index 00000000000..d943ffeb4c1 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift @@ -0,0 +1,53 @@ +// +// LayerImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation + +/// Connects a LottieImageProvider to a group of image layers +final class LayerImageProvider { + + // MARK: Lifecycle + + init(imageProvider: AnimationImageProvider, assets: [String: ImageAsset]?) { + self.imageProvider = imageProvider + imageLayers = [ImageCompositionLayer]() + if let assets = assets { + imageAssets = assets + } else { + imageAssets = [:] + } + reloadImages() + } + + // MARK: Internal + + private(set) var imageLayers: [ImageCompositionLayer] + let imageAssets: [String: ImageAsset] + + var imageProvider: AnimationImageProvider { + didSet { + reloadImages() + } + } + + func addImageLayers(_ layers: [ImageCompositionLayer]) { + for layer in layers { + if imageAssets[layer.imageReferenceID] != nil { + /// Found a linking asset in our asset library. Add layer + imageLayers.append(layer) + } + } + } + + func reloadImages() { + for imageLayer in imageLayers { + if let asset = imageAssets[imageLayer.imageReferenceID] { + imageLayer.image = imageProvider.imageForAsset(asset: asset) + } + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.cpp new file mode 100644 index 00000000000..22da907bd36 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.cpp @@ -0,0 +1,5 @@ +#include "LayerTextProvider.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp new file mode 100644 index 00000000000..82c96ec8f39 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp @@ -0,0 +1,45 @@ +#ifndef LayerTextProvider_hpp +#define LayerTextProvider_hpp + +#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp" + +namespace lottie { + +/// Connects a LottieTextProvider to a group of text layers +class LayerTextProvider { +public: + LayerTextProvider(std::shared_ptr const &textProvider) { + _textProvider = textProvider; + reloadTexts(); + } + + std::shared_ptr const &textProvider() const { + return _textProvider; + } + void setTextProvider(std::shared_ptr const &textProvider) { + _textProvider = textProvider; + reloadTexts(); + } + + void addTextLayers(std::vector> const &layers) { + for (const auto &layer : layers) { + _textLayers.push_back(layer); + } + } + + void reloadTexts() { + for (const auto &layer : _textLayers) { + layer->setTextProvider(_textProvider); + } + } + +private: + std::vector> _textLayers; + + std::shared_ptr _textProvider; +}; + +} + +#endif /* LayerTextProvider_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift new file mode 100644 index 00000000000..53806d57c0f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift @@ -0,0 +1,40 @@ +// +// LayerTextProvider.swift +// lottie-ios-iOS +// +// Created by Alexandr Goncharov on 07/06/2019. +// + +import Foundation + +/// Connects a LottieTextProvider to a group of text layers +final class LayerTextProvider { + + // MARK: Lifecycle + + init(textProvider: AnimationTextProvider) { + self.textProvider = textProvider + textLayers = [] + reloadTexts() + } + + // MARK: Internal + + private(set) var textLayers: [TextCompositionLayer] + + var textProvider: AnimationTextProvider { + didSet { + reloadTexts() + } + } + + func addTextLayers(_ layers: [TextCompositionLayer]) { + textLayers += layers + } + + func reloadTexts() { + textLayers.forEach { + $0.textProvider = textProvider + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.cpp new file mode 100644 index 00000000000..8f751a571f3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.cpp @@ -0,0 +1,5 @@ +#include "LayerTransformNode.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp new file mode 100644 index 00000000000..b2d9ad7e195 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp @@ -0,0 +1,201 @@ +#ifndef LayerTransformNode_hpp +#define LayerTransformNode_hpp + +#include "Lottie/Private/Model/Objects/Transform.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp" + +namespace lottie { + +class LayerTransformProperties: public KeypathSearchableNodePropertyMap { +public: + LayerTransformProperties(std::shared_ptr transform) { + _anchor = std::make_shared>(std::make_shared>(transform->anchorPoint().keyframes)); + _scale = std::make_shared>(std::make_shared>(transform->scale().keyframes)); + _rotation = std::make_shared>(std::make_shared>(transform->rotation().keyframes)); + _opacity = std::make_shared>(std::make_shared>(transform->opacity().keyframes)); + + std::map> propertyMap; + _keypathProperties.insert(std::make_pair("Anchor Point", _anchor)); + _keypathProperties.insert(std::make_pair("Scale", _scale)); + _keypathProperties.insert(std::make_pair("Rotation", _rotation)); + _keypathProperties.insert(std::make_pair("Opacity", _opacity)); + + if (transform->positionX().has_value() && transform->positionY().has_value()) { + auto xPosition = std::make_shared>(std::make_shared>(transform->positionX()->keyframes)); + auto yPosition = std::make_shared>(std::make_shared>(transform->positionY()->keyframes)); + _keypathProperties.insert(std::make_pair("X Position", xPosition)); + _keypathProperties.insert(std::make_pair("Y Position", yPosition)); + + _positionX = xPosition; + _positionY = yPosition; + _position = nullptr; + } else if (transform->position().has_value()) { + auto position = std::make_shared>(std::make_shared>(transform->position()->keyframes)); + _keypathProperties.insert(std::make_pair("Position", position)); + + _position = position; + _positionX = nullptr; + _positionY = nullptr; + } else { + _position = nullptr; + _positionX = nullptr; + _positionY = nullptr; + } + + for (const auto &it : _keypathProperties) { + _properties.push_back(it.second); + } + } + + virtual std::vector> &properties() override { + return _properties; + } + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + virtual std::string keypathName() const override { + return "Transform"; + } + + virtual std::map> keypathProperties() const override { + return _keypathProperties; + } + + virtual std::shared_ptr keypathLayer() const override { + return nullptr; + } + + std::shared_ptr> const &anchor() { + return _anchor; + } + + std::shared_ptr> const &scale() { + return _scale; + } + + std::shared_ptr> const &rotation() { + return _rotation; + } + + std::shared_ptr> const &position() { + return _position; + } + + std::shared_ptr> const &positionX() { + return _positionX; + } + + std::shared_ptr> const &positionY() { + return _positionY; + } + + std::shared_ptr> const &opacity() { + return _opacity; + } + +private: + std::map> _keypathProperties; + std::vector> _childKeypaths; + + std::vector> _properties; + + std::shared_ptr> _anchor; + std::shared_ptr> _scale; + std::shared_ptr> _rotation; + std::shared_ptr> _position; + std::shared_ptr> _positionX; + std::shared_ptr> _positionY; + std::shared_ptr> _opacity; +}; + +class LayerTransformNode: public AnimatorNode { +public: + LayerTransformNode(std::shared_ptr transform) : + AnimatorNode(nullptr), + _transformProperties(std::make_shared(transform)) { + _outputNode = std::make_shared(nullptr); + } + + virtual std::shared_ptr outputNode() override { + return _outputNode; + } + + virtual std::shared_ptr propertyMap() const override { + return _transformProperties; + } + + virtual bool shouldRebuildOutputs(double frame) override { + return hasLocalUpdates() || hasUpstreamUpdates(); + } + + virtual void rebuildOutputs(double frame) override { + _opacity = ((float)_transformProperties->opacity()->value().value) * 0.01f; + + Vector2D position(0.0, 0.0); + if (_transformProperties->position()) { + auto position3d = _transformProperties->position()->value(); + position.x = position3d.x; + position.y = position3d.y; + } else if (_transformProperties->positionX() && _transformProperties->positionY()) { + position = Vector2D( + _transformProperties->positionX()->value().value, + _transformProperties->positionY()->value().value + ); + } + + Vector3D anchor = _transformProperties->anchor()->value(); + Vector3D scale = _transformProperties->scale()->value(); + _localTransform = CATransform3D::makeTransform( + Vector2D(anchor.x, anchor.y), + position, + Vector2D(scale.x, scale.y), + _transformProperties->rotation()->value().value, + std::nullopt, + std::nullopt + ); + + if (parentNode() && parentNode()->asLayerTransformNode()) { + _globalTransform = _localTransform * parentNode()->asLayerTransformNode()->_globalTransform; + } else { + _globalTransform = _localTransform; + } + } + + std::shared_ptr const &transformProperties() { + return _transformProperties; + } + + float opacity() { + return _opacity; + } + + CATransform3D const &globalTransform() { + return _globalTransform; + } + +private: + std::shared_ptr _outputNode; + + std::shared_ptr _transformProperties; + + float _opacity = 1.0; + CATransform3D _localTransform = CATransform3D::identity(); + CATransform3D _globalTransform = CATransform3D::identity(); + +public: + virtual LayerTransformNode *asLayerTransformNode() override { + return this; + } +}; + +} + +#endif /* LayerTransformNode_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift new file mode 100644 index 00000000000..a2981f8e4d2 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift @@ -0,0 +1,144 @@ +// +// LayerTransformPropertyMap.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import CoreGraphics +import Foundation +import QuartzCore + +// MARK: - LayerTransformProperties + +final class LayerTransformProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(transform: Transform) { + + anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.anchorPoint.keyframes)) + scale = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.scale.keyframes)) + rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotation.keyframes)) + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.opacity.keyframes)) + + var propertyMap: [String: AnyNodeProperty] = [ + "Anchor Point" : anchor, + "Scale" : scale, + "Rotation" : rotation, + "Opacity" : opacity, + ] + + if + let positionKeyframesX = transform.positionX?.keyframes, + let positionKeyframesY = transform.positionY?.keyframes + { + let xPosition: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframesX)) + let yPosition: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframesY)) + propertyMap["X Position"] = xPosition + propertyMap["Y Position"] = yPosition + positionX = xPosition + positionY = yPosition + position = nil + } else if let positionKeyframes = transform.position?.keyframes { + let position: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframes)) + propertyMap["Position"] = position + self.position = position + positionX = nil + positionY = nil + } else { + position = nil + positionY = nil + positionX = nil + } + + keypathProperties = propertyMap + properties = Array(propertyMap.values) + } + + // MARK: Internal + + let keypathProperties: [String: AnyNodeProperty] + var keypathName = "Transform" + + let properties: [AnyNodeProperty] + + let anchor: NodeProperty + let scale: NodeProperty + let rotation: NodeProperty + let position: NodeProperty? + let positionX: NodeProperty? + let positionY: NodeProperty? + let opacity: NodeProperty + + var childKeypaths: [KeypathSearchable] { + [] + } +} + +// MARK: - LayerTransformNode + +class LayerTransformNode: AnimatorNode { + + // MARK: Lifecycle + + init(transform: Transform) { + transformProperties = LayerTransformProperties(transform: transform) + } + + // MARK: Internal + + let outputNode: NodeOutput = PassThroughOutputNode(parent: nil) + + let transformProperties: LayerTransformProperties + + var parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled = true + + var opacity: Float = 1 + var localTransform: CATransform3D = CATransform3DIdentity + var globalTransform: CATransform3D = CATransform3DIdentity + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + transformProperties + } + + func shouldRebuildOutputs(frame _: CGFloat) -> Bool { + hasLocalUpdates || hasUpstreamUpdates + } + + func rebuildOutputs(frame _: CGFloat) { + opacity = Float(transformProperties.opacity.value.cgFloatValue) * 0.01 + + let position: CGPoint + if let point = transformProperties.position?.value.pointValue { + position = point + } else if + let xPos = transformProperties.positionX?.value.cgFloatValue, + let yPos = transformProperties.positionY?.value.cgFloatValue + { + position = CGPoint(x: xPos, y: yPos) + } else { + position = .zero + } + + localTransform = CATransform3D.makeTransform( + anchor: transformProperties.anchor.value.pointValue, + position: position, + scale: transformProperties.scale.value.sizeValue, + rotation: transformProperties.rotation.value.cgFloatValue, + skew: nil, + skewAxis: nil) + + if let parentNode = parentNode as? LayerTransformNode { + globalTransform = CATransform3DConcat(localTransform, parentNode.globalTransform) + } else { + globalTransform = localTransform + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Extensions/ItemsExtension.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Extensions/ItemsExtension.swift new file mode 100644 index 00000000000..2df59082038 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Extensions/ItemsExtension.swift @@ -0,0 +1,97 @@ +// +// ItemsExtension.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/18/19. +// + +import Foundation + +// MARK: - NodeTree + +final class NodeTree { + var rootNode: AnimatorNode? = nil + var transform: ShapeTransform? = nil + var renderContainers: [ShapeContainerLayer] = [] + var paths: [PathOutputNode] = [] + var childrenNodes: [AnimatorNode] = [] +} + +extension Array where Element == ShapeItem { + func initializeNodeTree() -> NodeTree { + + let nodeTree = NodeTree() + + for item in self { + guard item.hidden == false, item.type != .unknown else { continue } + if let fill = item as? Fill { + let node = FillNode(parentNode: nodeTree.rootNode, fill: fill) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let stroke = item as? Stroke { + let node = StrokeNode(parentNode: nodeTree.rootNode, stroke: stroke) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let gradientFill = item as? GradientFill { + let node = GradientFillNode(parentNode: nodeTree.rootNode, gradientFill: gradientFill) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let gradientStroke = item as? GradientStroke { + let node = GradientStrokeNode(parentNode: nodeTree.rootNode, gradientStroke: gradientStroke) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let ellipse = item as? Ellipse { + let node = EllipseNode(parentNode: nodeTree.rootNode, ellipse: ellipse) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let rect = item as? Rectangle { + let node = RectangleNode(parentNode: nodeTree.rootNode, rectangle: rect) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let star = item as? Star { + switch star.starType { + case .none: + continue + case .polygon: + let node = PolygonNode(parentNode: nodeTree.rootNode, star: star) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + case .star: + let node = StarNode(parentNode: nodeTree.rootNode, star: star) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } + } else if let shape = item as? Shape { + let node = ShapeNode(parentNode: nodeTree.rootNode, shape: shape) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let trim = item as? Trim { + let node = TrimPathNode(parentNode: nodeTree.rootNode, trim: trim, upstreamPaths: nodeTree.paths) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let xform = item as? ShapeTransform { + nodeTree.transform = xform + continue + } else if let group = item as? Group { + + let tree = group.items.initializeNodeTree() + let node = GroupNode(name: group.name, parentNode: nodeTree.rootNode, tree: tree) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + /// Now add all child paths to current tree + nodeTree.paths.append(contentsOf: tree.paths) + nodeTree.renderContainers.append(node.container) + } + + if let pathNode = nodeTree.rootNode as? PathNode { + //// Add path container to the node tree + nodeTree.paths.append(pathNode.pathOutput) + } + + if let renderNode = nodeTree.rootNode as? RenderNode { + nodeTree.renderContainers.append(ShapeRenderLayer(renderer: renderNode.renderer)) + } + } + return nodeTree + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.cpp new file mode 100644 index 00000000000..eb9e3a58374 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.cpp @@ -0,0 +1,5 @@ +#include "NodeProperty.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp new file mode 100644 index 00000000000..f7eece88e62 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp @@ -0,0 +1,55 @@ +#ifndef NodeProperty_hpp +#define NodeProperty_hpp + +#include "Lottie/Public/Primitives/AnyValue.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp" +#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp" + +namespace lottie { + +/// A node property that holds a reference to a T ValueProvider and a T ValueContainer. +template +class NodeProperty: public AnyNodeProperty { +public: + NodeProperty(std::shared_ptr> provider) : + _valueProvider(provider), + //_originalValueProvider(provider), + _typedContainer(provider->value(0.0)) { + _typedContainer.setNeedsUpdate(); + } + +public: + virtual AnyValue::Type valueType() const override { + return AnyValueType::type(); + } + + virtual T value() { + return _typedContainer.outputValue(); + } + + virtual bool needsUpdate(double frame) const override { + return _typedContainer.needsUpdate() || _valueProvider->hasUpdate(frame); + } + + virtual void setProvider(std::shared_ptr provider) override { + /*if (provider->valueType() != valueType()) { + return; + } + _valueProvider = provider; + _typedContainer.setNeedsUpdate();*/ + } + + virtual void update(double frame) override { + _typedContainer.setValue(_valueProvider->value(frame), frame); + } + +private: + ValueContainer _typedContainer; + std::shared_ptr> _valueProvider; + //std::shared_ptr _originalValueProvider; +}; + +} + +#endif /* NodeProperty_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.swift new file mode 100644 index 00000000000..8702f2c59c1 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.swift @@ -0,0 +1,55 @@ +// +// NodeProperty.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +/// A node property that holds a reference to a T ValueProvider and a T ValueContainer. +class NodeProperty: AnyNodeProperty { + + // MARK: Lifecycle + + init(provider: AnyValueProvider) { + valueProvider = provider + originalValueProvider = valueProvider + typedContainer = ValueContainer(provider.value(frame: 0) as! T) + typedContainer.setNeedsUpdate() + } + + // MARK: Internal + + var valueProvider: AnyValueProvider + var originalValueProvider: AnyValueProvider + + var valueType: Any.Type { T.self } + + var value: T { + typedContainer.outputValue + } + + var valueContainer: AnyValueContainer { + typedContainer + } + + func needsUpdate(frame: CGFloat) -> Bool { + valueContainer.needsUpdate || valueProvider.hasUpdate(frame: frame) + } + + func setProvider(provider: AnyValueProvider) { + guard provider.valueType == valueType else { return } + valueProvider = provider + valueContainer.setNeedsUpdate() + } + + func update(frame: CGFloat) { + typedContainer.setValue(valueProvider.value(frame: frame), forFrame: frame) + } + + // MARK: Fileprivate + + fileprivate var typedContainer: ValueContainer +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.cpp new file mode 100644 index 00000000000..8609641d496 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.cpp @@ -0,0 +1,5 @@ +#include "AnyNodeProperty.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp new file mode 100644 index 00000000000..f317e68b9fc --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp @@ -0,0 +1,33 @@ +#ifndef AnyNodeProperty_hpp +#define AnyNodeProperty_hpp + +#include "Lottie/Public/Primitives/AnyValue.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp" +#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp" + +#include + +namespace lottie { + +/// A property of a node. The node property holds a provider and a container +class AnyNodeProperty { +public: + virtual ~AnyNodeProperty() = default; + +public: + /// Returns true if the property needs to recompute its stored value + virtual bool needsUpdate(double frame) const = 0; + + /// Updates the property for the frame + virtual void update(double frame) = 0; + + /// The Type of the value provider + virtual AnyValue::Type valueType() const = 0; + + /// Sets the value provider for the property. + virtual void setProvider(std::shared_ptr provider) = 0; +}; + +} + +#endif /* AnyNodeProperty_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift new file mode 100644 index 00000000000..132d96a8940 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift @@ -0,0 +1,50 @@ +// +// AnyNodeProperty.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +// MARK: - AnyNodeProperty + +/// A property of a node. The node property holds a provider and a container +protocol AnyNodeProperty { + + /// Returns true if the property needs to recompute its stored value + func needsUpdate(frame: CGFloat) -> Bool + + /// Updates the property for the frame + func update(frame: CGFloat) + + /// The stored value container for the property + var valueContainer: AnyValueContainer { get } + + /// The value provider for the property + var valueProvider: AnyValueProvider { get } + + /// The original value provider for the property + var originalValueProvider: AnyValueProvider { get } + + /// The Type of the value provider + var valueType: Any.Type { get } + + /// Sets the value provider for the property. + func setProvider(provider: AnyValueProvider) +} + +extension AnyNodeProperty { + + /// Returns the most recently computed value for the keypath, returns nil if property wasn't found + func getValueOfType() -> T? { + valueContainer.value as? T + } + + /// Returns the most recently computed value for the keypath, returns nil if property wasn't found + func getValue() -> Any? { + valueContainer.value + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.cpp new file mode 100644 index 00000000000..b186f2e2f49 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.cpp @@ -0,0 +1,5 @@ +#include "AnyValueContainer.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp new file mode 100644 index 00000000000..55e6dac2e22 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp @@ -0,0 +1,25 @@ +#ifndef AnyValueContainer_hpp +#define AnyValueContainer_hpp + +#include "Lottie/Public/Primitives/AnyValue.hpp" + +namespace lottie { + +class AnyValueContainer { +public: + /// The stored value of the container + virtual AnyValue value() const = 0; + + /// Notifies the provider that it should update its container + virtual void setNeedsUpdate() = 0; + + /// When true the container needs to have its value updated by its provider + virtual bool needsUpdate() const = 0; + + /// The frame time of the last provided update + virtual double lastUpdateFrame() const = 0; +}; + +} + +#endif /* AnyValueContainer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift new file mode 100644 index 00000000000..769d51cb1f1 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift @@ -0,0 +1,26 @@ +// +// AnyValueContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +/// The container for the value of a property. +protocol AnyValueContainer: AnyObject { + + /// The stored value of the container + var value: Any { get } + + /// Notifies the provider that it should update its container + func setNeedsUpdate() + + /// When true the container needs to have its value updated by its provider + var needsUpdate: Bool { get } + + /// The frame time of the last provided update + var lastUpdateFrame: CGFloat { get } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp new file mode 100644 index 00000000000..cf30fec7694 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp @@ -0,0 +1,13 @@ +#ifndef HasRenderUpdates_hpp +#define HasRenderUpdates_hpp + +namespace lottie { + +class HasRenderUpdates { +public: + virtual bool hasRenderUpdates(double forFrame) = 0; +}; + +} + +#endif /* HasRenderUpdates_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp new file mode 100644 index 00000000000..f0c35b05308 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp @@ -0,0 +1,14 @@ +#ifndef HasUpdate_hpp +#define HasUpdate_hpp + +namespace lottie { + +class HasUpdate { +public: + /// The last frame in which this node was updated. + virtual bool hasUpdate() = 0; +}; + +} + +#endif /* HasUpdate_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.cpp new file mode 100644 index 00000000000..62c08facbad --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.cpp @@ -0,0 +1,5 @@ +#include "KeypathSearchable.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp new file mode 100644 index 00000000000..4586c7ddc24 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp @@ -0,0 +1,36 @@ +#ifndef KeypathSearchable_hpp +#define KeypathSearchable_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp" +#include "Lottie/Public/Primitives/CALayer.hpp" + +#include +#include +#include +#include + +namespace lottie { + +class KeypathSearchable; + +class HasChildKeypaths { +public: + /// Children Keypaths + virtual std::vector> const &childKeypaths() const = 0; +}; + +/// Protocol that provides keypath search functionality. Returns all node properties associated with a keypath. +class KeypathSearchable: virtual public HasChildKeypaths { +public: + /// The name of the Keypath + virtual std::string keypathName() const = 0; + + /// A list of properties belonging to the keypath. + virtual std::map> keypathProperties() const = 0; + + virtual std::shared_ptr keypathLayer() const = 0; +}; + +} + +#endif /* KeypathSearchable_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift new file mode 100644 index 00000000000..b46f524f815 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift @@ -0,0 +1,24 @@ +// +// KeypathSettable.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +import QuartzCore + +/// Protocol that provides keypath search functionality. Returns all node properties associated with a keypath. +protocol KeypathSearchable { + + /// The name of the Keypath + var keypathName: String { get } + + /// A list of properties belonging to the keypath. + var keypathProperties: [String: AnyNodeProperty] { get } + + /// Children Keypaths + var childKeypaths: [KeypathSearchable] { get } + + var keypathLayer: CALayer? { get } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.cpp new file mode 100644 index 00000000000..100edba8df9 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.cpp @@ -0,0 +1,5 @@ +#include "NodePropertyMap.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp new file mode 100644 index 00000000000..d02d02ff0af --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp @@ -0,0 +1,41 @@ +#ifndef NodePropertyMap_hpp +#define NodePropertyMap_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp" +#include "Lottie/Public/Primitives/CALayer.hpp" + +#include + +namespace lottie { + +class NodePropertyMap: virtual public HasChildKeypaths { +public: + virtual std::vector> &properties() = 0; + + bool needsLocalUpdate(double frame) { + for (auto &property : properties()) { + if (property->needsUpdate(frame)) { + return true; + } + } + return false; + } + + void updateNodeProperties(double frame) { + for (auto &property : properties()) { + property->update(frame); + } + } +}; + +class KeypathSearchableNodePropertyMap: virtual public NodePropertyMap, virtual public KeypathSearchable { +public: + virtual std::shared_ptr keypathLayer() { + return nullptr; + } +}; + +} + +#endif /* NodePropertyMap_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift new file mode 100644 index 00000000000..07b101f77d8 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift @@ -0,0 +1,44 @@ +// +// NodePropertyMap.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/21/19. +// + +import Foundation +import QuartzCore + +// MARK: - NodePropertyMap + +protocol NodePropertyMap { + var properties: [AnyNodeProperty] { get } +} + +extension NodePropertyMap { + + var childKeypaths: [KeypathSearchable] { + [] + } + + var keypathLayer: CALayer? { + nil + } + + /// Checks if the node's local contents need to be rebuilt. + func needsLocalUpdate(frame: CGFloat) -> Bool { + for property in properties { + if property.needsUpdate(frame: frame) { + return true + } + } + return false + } + + /// Rebuilds only the local nodes that have an update for the frame + func updateNodeProperties(frame: CGFloat) { + properties.forEach { property in + property.update(frame: frame) + } + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.cpp new file mode 100644 index 00000000000..2ead9de1edc --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.cpp @@ -0,0 +1,5 @@ +#include "ValueContainer.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp new file mode 100644 index 00000000000..54f1548b140 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp @@ -0,0 +1,58 @@ +#ifndef ValueContainer_hpp +#define ValueContainer_hpp + +#include "Lottie/Public/Primitives/AnyValue.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp" + +namespace lottie { + +/// A container for a node value that is Typed to T. +template +class ValueContainer: public AnyValueContainer { +public: + ValueContainer(T value) : + _outputValue(value) { + } + +public: + double _lastUpdateFrame = std::numeric_limits::infinity(); + bool _needsUpdate = true; + + virtual AnyValue value() const override { + return AnyValue(_outputValue); + } + + virtual bool needsUpdate() const override { + return _needsUpdate; + } + + virtual double lastUpdateFrame() const override { + return _lastUpdateFrame; + } + + T _outputValue; + + T outputValue() { + return _outputValue; + } + void setOutputValue(T value) { + _outputValue = value; + _needsUpdate = false; + } + + void setValue(AnyValue value, double forFrame) { + if (value.type() == AnyValueType::type()) { + _needsUpdate = false; + _lastUpdateFrame = forFrame; + _outputValue = value.get(); + } + } + + virtual void setNeedsUpdate() override { + _needsUpdate = true; + } +}; + +} + +#endif /* ValueContainer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.swift new file mode 100644 index 00000000000..7717090966c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.swift @@ -0,0 +1,47 @@ +// +// ValueContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +/// A container for a node value that is Typed to T. +class ValueContainer: AnyValueContainer { + + // MARK: Lifecycle + + init(_ value: T) { + outputValue = value + } + + // MARK: Internal + + private(set) var lastUpdateFrame = CGFloat.infinity + + fileprivate(set) var needsUpdate = true + + var value: Any { + outputValue as Any + } + + var outputValue: T { + didSet { + needsUpdate = false + } + } + + func setValue(_ value: Any, forFrame: CGFloat) { + if let typedValue = value as? T { + needsUpdate = false + lastUpdateFrame = forFrame + outputValue = typedValue + } + } + + func setNeedsUpdate() { + needsUpdate = true + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.cpp new file mode 100644 index 00000000000..b3f91f556b3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.cpp @@ -0,0 +1,5 @@ +#include "DashPatternInterpolator.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp new file mode 100644 index 00000000000..d714fc90447 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp @@ -0,0 +1,46 @@ +#ifndef DashPatternInterpolator_hpp +#define DashPatternInterpolator_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" +#include "Lottie/Public/Primitives/DashPattern.hpp" + +namespace lottie { + +/// A value provider that produces an array of values from an array of Keyframe Interpolators +class DashPatternInterpolator: public ValueProvider, public std::enable_shared_from_this { +public: + /// Initialize with an array of array of keyframes. + DashPatternInterpolator(std::vector>> const &keyframeGroups) { + for (const auto &keyframeGroup : keyframeGroups) { + _keyframeInterpolators.push_back(std::make_shared>(keyframeGroup)); + } + } + + virtual AnyValue::Type valueType() const override { + return AnyValueType::type(); + } + + virtual DashPattern value(AnimationFrameTime frame) override { + std::vector values; + for (const auto &interpolator : _keyframeInterpolators) { + values.push_back(interpolator->value(frame).value); + } + return DashPattern(std::move(values)); + } + + virtual bool hasUpdate(double frame) const override { + for (const auto &interpolator : _keyframeInterpolators) { + if (interpolator->hasUpdate(frame)) { + return true; + } + } + return false; + } + +private: + std::vector>> _keyframeInterpolators; +}; + +} + +#endif /* DashPatternInterpolator_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift new file mode 100644 index 00000000000..8be2dc23639 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift @@ -0,0 +1,39 @@ +// +// KeyframeGroupInterpolator.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import CoreGraphics +import Foundation + +/// A value provider that produces an array of values from an array of Keyframe Interpolators +final class GroupInterpolator: ValueProvider where ValueType: Interpolatable { + + // MARK: Lifecycle + + /// Initialize with an array of array of keyframes. + init(keyframeGroups: ContiguousArray>>) { + keyframeInterpolators = ContiguousArray(keyframeGroups.map({ KeyframeInterpolator(keyframes: $0) })) + } + + // MARK: Internal + + let keyframeInterpolators: ContiguousArray> + + var valueType: Any.Type { + [ValueType].self + } + + var storage: ValueProviderStorage<[ValueType]> { + .closure { frame in + self.keyframeInterpolators.map({ $0.value(frame: frame) as! ValueType }) + } + } + + func hasUpdate(frame: CGFloat) -> Bool { + let updated = keyframeInterpolators.first(where: { $0.hasUpdate(frame: frame) }) + return updated != nil + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.cpp new file mode 100644 index 00000000000..8ec23647627 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.cpp @@ -0,0 +1,5 @@ +#include "KeyframeInterpolator.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp new file mode 100644 index 00000000000..7e99ae34828 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp @@ -0,0 +1,449 @@ +#ifndef KeyframeInterpolator_hpp +#define KeyframeInterpolator_hpp + +#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp" + +namespace lottie { + +/// A value provider that produces a value at Time from a group of keyframes +template +class KeyframeInterpolator: public ValueProvider, public std::enable_shared_from_this> { +public: + KeyframeInterpolator(std::vector> const &keyframes_) : + keyframes(keyframes_) { + assert(!keyframes.empty()); + } + +public: + std::vector> keyframes; + + virtual AnyValue::Type valueType() const override { + return AnyValueType::type(); + } + + virtual T value(AnimationFrameTime frame) override { + // First set the keyframe span for the frame. + updateSpanIndices(frame); + lastUpdatedFrame = frame; + // If only one keyframe return its value + + if (leadingKeyframe.has_value() && + trailingKeyframe.has_value()) + { + /// We have leading and trailing keyframe. + auto progress = leadingKeyframe->interpolatedProgress(trailingKeyframe.value(), frame); + return leadingKeyframe->interpolate(trailingKeyframe.value(), progress); + } else if (leadingKeyframe.has_value()) { + return leadingKeyframe->value; + } else if (trailingKeyframe.has_value()) { + return trailingKeyframe->value; + } else { + /// Satisfy the compiler. + return keyframes[0].value; + } + } + + /// Returns true to trigger a frame update for this interpolator. + /// + /// An interpolator will be asked if it needs to update every frame. + /// If the interpolator needs updating it will be asked to compute its value for + /// the given frame. + /// + /// Cases a keyframe should not be updated: + /// - If time is in span and leading keyframe is hold + /// - If time is after the last keyframe. + /// - If time is before the first keyframe + /// + /// Cases for updating a keyframe: + /// - If time is in the span, and is not a hold + /// - If time is outside of the span, and there are more keyframes + /// - If a value delegate is set + /// - If leading and trailing are both nil. + virtual bool hasUpdate(double frame) const override { + if (!lastUpdatedFrame.has_value()) { + return true; + } + + if (leadingKeyframe.has_value() && + !trailingKeyframe.has_value() && + leadingKeyframe->time < frame) + { + /// Frame is after bounds of keyframes + return false; + } + if (trailingKeyframe.has_value() && + !leadingKeyframe.has_value() && + frame < trailingKeyframe->time) + { + /// Frame is before bounds of keyframes + return false; + } + if (leadingKeyframe.has_value() && + trailingKeyframe.has_value() && + leadingKeyframe->isHold && + leadingKeyframe->time < frame && + frame < trailingKeyframe->time) + { + return false; + } + return true; + } + + // MARK: Fileprivate + + std::optional lastUpdatedFrame; + + std::optional leadingIndex; + std::optional trailingIndex; + std::optional> leadingKeyframe; + std::optional> trailingKeyframe; + + /// Finds the appropriate Leading and Trailing keyframe index for the given time. + void updateSpanIndices(double frame) { + if (keyframes.empty()) { + leadingIndex = std::nullopt; + trailingIndex = std::nullopt; + leadingKeyframe = std::nullopt; + trailingKeyframe = std::nullopt; + return; + } + + // This function searches through the array to find the span of two keyframes + // that contain the current time. + // + // We could use Array.first(where:) but that would search through the entire array + // each frame. + // Instead we track the last used index and search either forwards or + // backwards from there. This reduces the iterations and complexity from + // + // O(n), where n is the length of the sequence to + // O(n), where n is the number of items after or before the last used index. + // + + if (keyframes.size() == 1) { + /// Only one keyframe. Set it as first and move on. + leadingIndex = 0; + trailingIndex = std::nullopt; + leadingKeyframe = keyframes[0]; + trailingKeyframe = std::nullopt; + return; + } + + /// Sets the initial keyframes. This is often only needed for the first check. + if + (!leadingIndex.has_value() && + !trailingIndex.has_value()) + { + if (frame < keyframes[0].time) { + /// Time is before the first keyframe. Set it as the trailing. + trailingIndex = 0; + } else { + /// Time is after the first keyframe. Set the keyframe and the trailing. + leadingIndex = 0; + trailingIndex = 1; + } + } + + if + (trailingIndex.has_value() && + keyframes[trailingIndex.value()].time <= frame) + { + /// Time is after the current span. Iterate forward. + auto newLeading = trailingIndex.value(); + bool keyframeFound = false; + while (!keyframeFound) { + leadingIndex = newLeading; + if (newLeading + 1 >= 0 && newLeading + 1 < keyframes.size()) { + trailingIndex = newLeading + 1; + } else { + trailingIndex = std::nullopt; + } + + if (!trailingIndex.has_value()) { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true; + continue; + } + + if (frame < keyframes[trailingIndex.value()].time) { + /// Keyframe in current span. + keyframeFound = true; + continue; + } + /// Advance the array. + newLeading = trailingIndex.value(); + } + + } else if + (leadingIndex.has_value() && + frame < keyframes[leadingIndex.value()].time) + { + + /// Time is before the current span. Iterate backwards + auto newTrailing = leadingIndex.value(); + + bool keyframeFound = false; + while (!keyframeFound) { + if (newTrailing - 1 >= 0 && newTrailing - 1 < keyframes.size()) { + leadingIndex = newTrailing - 1; + } else { + leadingIndex = std::nullopt; + } + trailingIndex = newTrailing; + + if (!leadingIndex.has_value()) { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true; + continue; + } + if (keyframes[leadingIndex.value()].time <= frame) { + /// Keyframe in current span. + keyframeFound = true; + continue; + } + /// Step back + newTrailing = leadingIndex.value(); + } + } + if (const auto keyFrame = leadingIndex) { + leadingKeyframe = keyframes[keyFrame.value()]; + } else { + leadingKeyframe = std::nullopt; + } + + if (const auto keyFrame = trailingIndex) { + trailingKeyframe = keyframes[keyFrame.value()]; + } else { + trailingKeyframe = std::nullopt; + } + } +}; + +class BezierPathKeyframeInterpolator { +public: + BezierPathKeyframeInterpolator(std::vector> const &keyframes_) : + keyframes(keyframes_) { + assert(!keyframes.empty()); + } + +public: + std::vector> keyframes; + + void update(AnimationFrameTime frame, BezierPath &outPath) { + // First set the keyframe span for the frame. + updateSpanIndices(frame); + lastUpdatedFrame = frame; + // If only one keyframe return its value + + if (leadingKeyframe.has_value() && + trailingKeyframe.has_value()) + { + /// We have leading and trailing keyframe. + auto progress = leadingKeyframe->interpolatedProgress(trailingKeyframe.value(), frame); + interpolateInplace(leadingKeyframe.value(), trailingKeyframe.value(), progress, outPath); + } else if (leadingKeyframe.has_value()) { + setInplace(leadingKeyframe.value(), outPath); + } else if (trailingKeyframe.has_value()) { + setInplace(trailingKeyframe.value(), outPath); + } else { + /// Satisfy the compiler. + setInplace(keyframes[0], outPath); + } + } + + /// Returns true to trigger a frame update for this interpolator. + /// + /// An interpolator will be asked if it needs to update every frame. + /// If the interpolator needs updating it will be asked to compute its value for + /// the given frame. + /// + /// Cases a keyframe should not be updated: + /// - If time is in span and leading keyframe is hold + /// - If time is after the last keyframe. + /// - If time is before the first keyframe + /// + /// Cases for updating a keyframe: + /// - If time is in the span, and is not a hold + /// - If time is outside of the span, and there are more keyframes + /// - If a value delegate is set + /// - If leading and trailing are both nil. + bool hasUpdate(double frame) const { + if (!lastUpdatedFrame.has_value()) { + return true; + } + + if (leadingKeyframe.has_value() && + !trailingKeyframe.has_value() && + leadingKeyframe->time < frame) + { + /// Frame is after bounds of keyframes + return false; + } + if (trailingKeyframe.has_value() && + !leadingKeyframe.has_value() && + frame < trailingKeyframe->time) + { + /// Frame is before bounds of keyframes + return false; + } + if (leadingKeyframe.has_value() && + trailingKeyframe.has_value() && + leadingKeyframe->isHold && + leadingKeyframe->time < frame && + frame < trailingKeyframe->time) + { + return false; + } + return true; + } + + // MARK: Fileprivate + + std::optional lastUpdatedFrame; + + std::optional leadingIndex; + std::optional trailingIndex; + std::optional> leadingKeyframe; + std::optional> trailingKeyframe; + + /// Finds the appropriate Leading and Trailing keyframe index for the given time. + void updateSpanIndices(double frame) { + if (keyframes.empty()) { + leadingIndex = std::nullopt; + trailingIndex = std::nullopt; + leadingKeyframe = std::nullopt; + trailingKeyframe = std::nullopt; + return; + } + + // This function searches through the array to find the span of two keyframes + // that contain the current time. + // + // We could use Array.first(where:) but that would search through the entire array + // each frame. + // Instead we track the last used index and search either forwards or + // backwards from there. This reduces the iterations and complexity from + // + // O(n), where n is the length of the sequence to + // O(n), where n is the number of items after or before the last used index. + // + + if (keyframes.size() == 1) { + /// Only one keyframe. Set it as first and move on. + leadingIndex = 0; + trailingIndex = std::nullopt; + leadingKeyframe = keyframes[0]; + trailingKeyframe = std::nullopt; + return; + } + + /// Sets the initial keyframes. This is often only needed for the first check. + if + (!leadingIndex.has_value() && + !trailingIndex.has_value()) + { + if (frame < keyframes[0].time) { + /// Time is before the first keyframe. Set it as the trailing. + trailingIndex = 0; + } else { + /// Time is after the first keyframe. Set the keyframe and the trailing. + leadingIndex = 0; + trailingIndex = 1; + } + } + + if + (trailingIndex.has_value() && + keyframes[trailingIndex.value()].time <= frame) + { + /// Time is after the current span. Iterate forward. + auto newLeading = trailingIndex.value(); + bool keyframeFound = false; + while (!keyframeFound) { + leadingIndex = newLeading; + if (newLeading + 1 >= 0 && newLeading + 1 < keyframes.size()) { + trailingIndex = newLeading + 1; + } else { + trailingIndex = std::nullopt; + } + + if (!trailingIndex.has_value()) { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true; + continue; + } + + if (frame < keyframes[trailingIndex.value()].time) { + /// Keyframe in current span. + keyframeFound = true; + continue; + } + /// Advance the array. + newLeading = trailingIndex.value(); + } + + } else if + (leadingIndex.has_value() && + frame < keyframes[leadingIndex.value()].time) + { + + /// Time is before the current span. Iterate backwards + auto newTrailing = leadingIndex.value(); + + bool keyframeFound = false; + while (!keyframeFound) { + if (newTrailing - 1 >= 0 && newTrailing - 1 < keyframes.size()) { + leadingIndex = newTrailing - 1; + } else { + leadingIndex = std::nullopt; + } + trailingIndex = newTrailing; + + if (!leadingIndex.has_value()) { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true; + continue; + } + if (keyframes[leadingIndex.value()].time <= frame) { + /// Keyframe in current span. + keyframeFound = true; + continue; + } + /// Step back + newTrailing = leadingIndex.value(); + } + } + if (const auto keyFrame = leadingIndex) { + leadingKeyframe = keyframes[keyFrame.value()]; + } else { + leadingKeyframe = std::nullopt; + } + + if (const auto keyFrame = trailingIndex) { + trailingKeyframe = keyframes[keyFrame.value()]; + } else { + trailingKeyframe = std::nullopt; + } + } + +private: + void setInplace(Keyframe const &from, BezierPath &outPath) { + ValueInterpolator::setInplace(from.value, outPath); + } + + void interpolateInplace(Keyframe const &from, Keyframe const &to, double progress, BezierPath &outPath) { + std::optional spatialOutTangent2d; + if (from.spatialOutTangent) { + spatialOutTangent2d = Vector2D(from.spatialOutTangent->x, from.spatialOutTangent->y); + } + std::optional spatialInTangent2d; + if (to.spatialInTangent) { + spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y); + } + ValueInterpolator::interpolateInplace(from.value, to.value, progress, spatialOutTangent2d, spatialInTangent2d, outPath); + } +}; + +} + +#endif /* KeyframeInterpolator_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift new file mode 100644 index 00000000000..f008b96f034 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift @@ -0,0 +1,253 @@ +// +// KeyframeInterpolator.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/15/19. +// + +import CoreGraphics +import Foundation + +// MARK: - KeyframeInterpolator + +/// A value provider that produces a value at Time from a group of keyframes +final class KeyframeInterpolator: ValueProvider where ValueType: AnyInterpolatable { + + // MARK: Lifecycle + + init(keyframes: ContiguousArray>) { + self.keyframes = keyframes + } + + // MARK: Internal + + let keyframes: ContiguousArray> + + var valueType: Any.Type { + ValueType.self + } + + var storage: ValueProviderStorage { + .closure { [self] frame in + // First set the keyframe span for the frame. + updateSpanIndices(frame: frame) + lastUpdatedFrame = frame + // If only one keyframe return its value + let progress: CGFloat + let value: ValueType + + if + let leading = leadingKeyframe, + let trailing = trailingKeyframe + { + /// We have leading and trailing keyframe. + progress = leading.interpolatedProgress(trailing, keyTime: frame) + value = leading.interpolate(to: trailing, progress: progress) + } else if let leading = leadingKeyframe { + progress = 0 + value = leading.value + } else if let trailing = trailingKeyframe { + progress = 1 + value = trailing.value + } else { + /// Satisfy the compiler. + progress = 0 + value = keyframes[0].value + } + return value + } + } + + /// Returns true to trigger a frame update for this interpolator. + /// + /// An interpolator will be asked if it needs to update every frame. + /// If the interpolator needs updating it will be asked to compute its value for + /// the given frame. + /// + /// Cases a keyframe should not be updated: + /// - If time is in span and leading keyframe is hold + /// - If time is after the last keyframe. + /// - If time is before the first keyframe + /// + /// Cases for updating a keyframe: + /// - If time is in the span, and is not a hold + /// - If time is outside of the span, and there are more keyframes + /// - If a value delegate is set + /// - If leading and trailing are both nil. + func hasUpdate(frame: CGFloat) -> Bool { + if lastUpdatedFrame == nil { + return true + } + + if + let leading = leadingKeyframe, + trailingKeyframe == nil, + leading.time < frame + { + /// Frame is after bounds of keyframes + return false + } + if + let trailing = trailingKeyframe, + leadingKeyframe == nil, + frame < trailing.time + { + /// Frame is before bounds of keyframes + return false + } + if + let leading = leadingKeyframe, + let trailing = trailingKeyframe, + leading.isHold, + leading.time < frame, + frame < trailing.time + { + return false + } + return true + } + + // MARK: Fileprivate + + fileprivate var lastUpdatedFrame: CGFloat? + + fileprivate var leadingIndex: Int? = nil + fileprivate var trailingIndex: Int? = nil + fileprivate var leadingKeyframe: Keyframe? = nil + fileprivate var trailingKeyframe: Keyframe? = nil + + /// Finds the appropriate Leading and Trailing keyframe index for the given time. + fileprivate func updateSpanIndices(frame: CGFloat) { + guard keyframes.count > 0 else { + leadingIndex = nil + trailingIndex = nil + leadingKeyframe = nil + trailingKeyframe = nil + return + } + + // This function searches through the array to find the span of two keyframes + // that contain the current time. + // + // We could use Array.first(where:) but that would search through the entire array + // each frame. + // Instead we track the last used index and search either forwards or + // backwards from there. This reduces the iterations and complexity from + // + // O(n), where n is the length of the sequence to + // O(n), where n is the number of items after or before the last used index. + // + + if keyframes.count == 1 { + /// Only one keyframe. Set it as first and move on. + leadingIndex = 0 + trailingIndex = nil + leadingKeyframe = keyframes[0] + trailingKeyframe = nil + return + } + + /// Sets the initial keyframes. This is often only needed for the first check. + if + leadingIndex == nil && + trailingIndex == nil + { + if frame < keyframes[0].time { + /// Time is before the first keyframe. Set it as the trailing. + trailingIndex = 0 + } else { + /// Time is after the first keyframe. Set the keyframe and the trailing. + leadingIndex = 0 + trailingIndex = 1 + } + } + + if + let currentTrailing = trailingIndex, + keyframes[currentTrailing].time <= frame + { + /// Time is after the current span. Iterate forward. + var newLeading = currentTrailing + var keyframeFound = false + while !keyframeFound { + + leadingIndex = newLeading + trailingIndex = keyframes.validIndex(newLeading + 1) + + guard let trailing = trailingIndex else { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true + continue + } + if frame < keyframes[trailing].time { + /// Keyframe in current span. + keyframeFound = true + continue + } + /// Advance the array. + newLeading = trailing + } + + } else if + let currentLeading = leadingIndex, + frame < keyframes[currentLeading].time + { + + /// Time is before the current span. Iterate backwards + var newTrailing = currentLeading + + var keyframeFound = false + while !keyframeFound { + + leadingIndex = keyframes.validIndex(newTrailing - 1) + trailingIndex = newTrailing + + guard let leading = leadingIndex else { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true + continue + } + if keyframes[leading].time <= frame { + /// Keyframe in current span. + keyframeFound = true + continue + } + /// Step back + newTrailing = leading + } + } + if let keyFrame = leadingIndex { + leadingKeyframe = keyframes[keyFrame] + } else { + leadingKeyframe = nil + } + + if let keyFrame = trailingIndex { + trailingKeyframe = keyframes[keyFrame] + } else { + trailingKeyframe = nil + } + } +} + +extension Array { + + fileprivate func validIndex(_ index: Int) -> Int? { + if 0 <= index, index < endIndex { + return index + } + return nil + } + +} + +extension ContiguousArray { + + fileprivate func validIndex(_ index: Int) -> Int? { + if 0 <= index, index < endIndex { + return index + } + return nil + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.cpp new file mode 100644 index 00000000000..face3214996 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.cpp @@ -0,0 +1,5 @@ +#include "SingleValueProvider.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp new file mode 100644 index 00000000000..73456a595a7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp @@ -0,0 +1,40 @@ +#ifndef SingleValueProvider_hpp +#define SingleValueProvider_hpp + +#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp" + +namespace lottie { + +/// Returns a value for every frame. +template +class SingleValueProvider: public ValueProvider { +public: + SingleValueProvider(T const &value) : + _value(value) { + } + + void setValue(T const &value) { + _value = value; + _hasUpdate = true; + } + + virtual T value(AnimationFrameTime frame) override { + return _value; + } + + virtual AnyValue::Type valueType() const override { + return AnyValueType::type(); + } + + virtual bool hasUpdate(double frame) const override { + return _hasUpdate; + } + +private: + T _value; + bool _hasUpdate = true; +}; + +} + +#endif /* SingleValueProvider_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift new file mode 100644 index 00000000000..b4bff3aac3c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift @@ -0,0 +1,43 @@ +// +// SingleValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +/// Returns a value for every frame. +final class SingleValueProvider: ValueProvider { + + // MARK: Lifecycle + + init(_ value: ValueType) { + self.value = value + } + + // MARK: Internal + + var value: ValueType { + didSet { + hasUpdate = true + } + } + + var storage: ValueProviderStorage { + .singleValue(value) + } + + var valueType: Any.Type { + ValueType.self + } + + func hasUpdate(frame _: CGFloat) -> Bool { + hasUpdate + } + + // MARK: Private + + private var hasUpdate = true +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift new file mode 100644 index 00000000000..1278e4e3af1 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift @@ -0,0 +1,281 @@ +// +// TrimPathNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/23/19. +// + +import Foundation +import QuartzCore + +// MARK: - TrimPathProperties + +final class TrimPathProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(trim: Trim) { + keypathName = trim.name + start = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.start.keyframes)) + end = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.end.keyframes)) + offset = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.offset.keyframes)) + type = trim.trimType + keypathProperties = [ + "Start" : start, + "End" : end, + "Offset" : offset, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + let keypathName: String + + let start: NodeProperty + let end: NodeProperty + let offset: NodeProperty + let type: TrimType +} + +// MARK: - TrimPathNode + +final class TrimPathNode: AnimatorNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, trim: Trim, upstreamPaths: [PathOutputNode]) { + outputNode = PassThroughOutputNode(parent: parentNode?.outputNode) + self.parentNode = parentNode + properties = TrimPathProperties(trim: trim) + self.upstreamPaths = upstreamPaths + } + + // MARK: Internal + + let properties: TrimPathProperties + + let parentNode: AnimatorNode? + let outputNode: NodeOutput + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled = true + + // MARK: Animator Node + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + func forceUpstreamOutputUpdates() -> Bool { + hasLocalUpdates || hasUpstreamUpdates + } + + func rebuildOutputs(frame: CGFloat) { + /// Make sure there is a trim. + let startValue = properties.start.value.cgFloatValue * 0.01 + let endValue = properties.end.value.cgFloatValue * 0.01 + let start = min(startValue, endValue) + let end = max(startValue, endValue) + + let offset = properties.offset.value.cgFloatValue.truncatingRemainder(dividingBy: 360) / 360 + + /// No need to trim, it's a full path + if start == 0, end == 1 { + return + } + + /// All paths are empty. + if start == end { + for pathContainer in upstreamPaths { + pathContainer.removePaths(updateFrame: frame) + } + return + } + + if properties.type == .simultaneously { + /// Just trim each path + for pathContainer in upstreamPaths { + let pathObjects = pathContainer.removePaths(updateFrame: frame) + for path in pathObjects { + // We are treating each compount path as an individual path. Its subpaths are treated as a whole. + pathContainer.appendPath( + path.trim(fromPosition: start, toPosition: end, offset: offset, trimSimultaneously: false), + updateFrame: frame) + } + } + return + } + + /// Individual path trimming. + + /// Brace yourself for the below code. + + /// Normalize lengths with offset. + var startPosition = (start + offset).truncatingRemainder(dividingBy: 1) + var endPosition = (end + offset).truncatingRemainder(dividingBy: 1) + + if startPosition < 0 { + startPosition = 1 + startPosition + } + + if endPosition < 0 { + endPosition = 1 + endPosition + } + if startPosition == 1 { + startPosition = 0 + } + if endPosition == 0 { + endPosition = 1 + } + + /// First get the total length of all paths. + var totalLength: CGFloat = 0 + upstreamPaths.forEach({ totalLength = totalLength + $0.totalLength }) + + /// Now determine the start and end cut lengths + let startLength = startPosition * totalLength + let endLength = endPosition * totalLength + var pathStart: CGFloat = 0 + + /// Now loop through all path containers + for pathContainer in upstreamPaths { + + let pathEnd = pathStart + pathContainer.totalLength + + if + !startLength.isInRange(pathStart, pathEnd) && + endLength.isInRange(pathStart, pathEnd) + { + // pathStart|=======E----------------------|pathEnd + // Cut path components, removing after end. + + let pathCutLength = endLength - pathStart + let subpaths = pathContainer.removePaths(updateFrame: frame) + var subpathStart: CGFloat = 0 + for path in subpaths { + let subpathEnd = subpathStart + path.length + if pathCutLength < subpathEnd { + /// This is the subpath that needs to be cut. + let cutLength = pathCutLength - subpathStart + let newPath = path.trim(fromPosition: 0, toPosition: cutLength / path.length, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + break + } else { + /// Add to container and move on + pathContainer.appendPath(path, updateFrame: frame) + } + if pathCutLength == subpathEnd { + /// Right on the end. The next subpath is not included. Break. + break + } + subpathStart = subpathEnd + } + + } else if + !endLength.isInRange(pathStart, pathEnd) && + startLength.isInRange(pathStart, pathEnd) + { + // pathStart|-------S======================|pathEnd + // + + // Cut path components, removing before beginning. + let pathCutLength = startLength - pathStart + // Clear paths from container + let subpaths = pathContainer.removePaths(updateFrame: frame) + var subpathStart: CGFloat = 0 + for path in subpaths { + let subpathEnd = subpathStart + path.length + + if subpathStart < pathCutLength, pathCutLength < subpathEnd { + /// This is the subpath that needs to be cut. + let cutLength = pathCutLength - subpathStart + let newPath = path.trim(fromPosition: cutLength / path.length, toPosition: 1, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + } else if pathCutLength <= subpathStart { + pathContainer.appendPath(path, updateFrame: frame) + } + subpathStart = subpathEnd + } + } else if + endLength.isInRange(pathStart, pathEnd) && + startLength.isInRange(pathStart, pathEnd) + { + // pathStart|-------S============E---------|endLength + // pathStart|=====E----------------S=======|endLength + // trim from path beginning to endLength. + + // Cut path components, removing before beginnings. + let startCutLength = startLength - pathStart + let endCutLength = endLength - pathStart + // Clear paths from container + let subpaths = pathContainer.removePaths(updateFrame: frame) + var subpathStart: CGFloat = 0 + for path in subpaths { + + let subpathEnd = subpathStart + path.length + + if + !startCutLength.isInRange(subpathStart, subpathEnd) && + !endCutLength.isInRange(subpathStart, subpathEnd) + { + // The whole path is included. Add + // S|==============================|E + pathContainer.appendPath(path, updateFrame: frame) + + } else if + startCutLength.isInRange(subpathStart, subpathEnd) && + !endCutLength.isInRange(subpathStart, subpathEnd) + { + /// The start of the path needs to be trimmed + // |-------S======================|E + let cutLength = startCutLength - subpathStart + let newPath = path.trim(fromPosition: cutLength / path.length, toPosition: 1, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + } else if + !startCutLength.isInRange(subpathStart, subpathEnd) && + endCutLength.isInRange(subpathStart, subpathEnd) + { + // S|=======E----------------------| + let cutLength = endCutLength - subpathStart + let newPath = path.trim(fromPosition: 0, toPosition: cutLength / path.length, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + break + } else if + startCutLength.isInRange(subpathStart, subpathEnd) && + endCutLength.isInRange(subpathStart, subpathEnd) + { + // |-------S============E---------| + let cutFromLength = startCutLength - subpathStart + let cutToLength = endCutLength - subpathStart + let newPath = path.trim( + fromPosition: cutFromLength / path.length, + toPosition: cutToLength / path.length, + offset: 0, + trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + break + } + + subpathStart = subpathEnd + } + } else if + (endLength <= pathStart && pathEnd <= startLength) || + (startLength <= pathStart && endLength <= pathStart) || + (pathEnd <= startLength && pathEnd <= endLength) + { + /// The Path needs to be cleared + pathContainer.removePaths(updateFrame: frame) + } + + pathStart = pathEnd + } + + } + + // MARK: Fileprivate + + fileprivate let upstreamPaths: [PathOutputNode] +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift new file mode 100644 index 00000000000..dc2b692516b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift @@ -0,0 +1,77 @@ +// +// TransformNodeOutput.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation +import QuartzCore + +class GroupOutputNode: NodeOutput { + + // MARK: Lifecycle + + init(parent: NodeOutput?, rootNode: NodeOutput?) { + self.parent = parent + self.rootNode = rootNode + } + + // MARK: Internal + + let parent: NodeOutput? + let rootNode: NodeOutput? + var isEnabled = true + + private(set) var outputPath: CGPath? = nil + private(set) var transform: CATransform3D = CATransform3DIdentity + + func setTransform(_ xform: CATransform3D, forFrame _: CGFloat) { + transform = xform + outputPath = nil + } + + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + guard isEnabled else { + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + outputPath = parent?.outputPath + return upstreamUpdates + } + + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + if upstreamUpdates { + outputPath = nil + } + let rootUpdates = rootNode?.hasOutputUpdates(forFrame) ?? false + if rootUpdates { + outputPath = nil + } + + var localUpdates = false + if outputPath == nil { + localUpdates = true + + let newPath = CGMutablePath() + if let parentNode = parent, let parentPath = parentNode.outputPath { + /// First add parent path. + newPath.addPath(parentPath) + } + var xform = CATransform3DGetAffineTransform(transform) + if + let rootNode = rootNode, + let rootPath = rootNode.outputPath + { + if let xformedPath = rootPath.copy(using: &xform) { + /// Now add root path. Note root path is transformed. + newPath.addPath(xformedPath) + } + } + + outputPath = newPath + } + + return upstreamUpdates || localUpdates + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp new file mode 100644 index 00000000000..c909c7b7900 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp @@ -0,0 +1,70 @@ +#ifndef PassThroughOutputNode_hpp +#define PassThroughOutputNode_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" + +namespace lottie { + +class PassThroughOutputNode: virtual public NodeOutput, virtual public HasRenderUpdates, virtual public HasUpdate { +public: + PassThroughOutputNode(std::shared_ptr parent) : + _parent(parent) { + } + + virtual std::shared_ptr parent() override { + return _parent; + } + + virtual bool isEnabled() const override { + return _isEnabled; + } + virtual void setIsEnabled(bool isEnabled) override { + _isEnabled = isEnabled; + } + + virtual bool hasUpdate() override { + return _hasUpdate; + } + void setHasUpdate(bool hasUpdate) { + _hasUpdate = hasUpdate; + } + + virtual std::shared_ptr outputPath() override { + if (_parent) { + return _parent->outputPath(); + } + return nullptr; + } + + virtual bool hasOutputUpdates(double forFrame) override { + /// Changes to this node do not affect downstream nodes. + bool parentUpdate = false; + if (_parent) { + parentUpdate = _parent->hasOutputUpdates(forFrame); + } + /// Changes to upstream nodes do, however, affect this nodes state. + _hasUpdate = _hasUpdate || parentUpdate; + return parentUpdate; + } + + virtual bool hasRenderUpdates(double forFrame) override { + /// Return true if there are upstream updates or if this node has updates + bool upstreamUpdates = false; + if (_parent) { + upstreamUpdates = _parent->hasOutputUpdates(forFrame); + } + _hasUpdate = _hasUpdate || upstreamUpdates; + return _hasUpdate; + } + +private: + std::shared_ptr _parent; + bool _hasUpdate = false; + bool _isEnabled = true; +}; + +} + +#endif /* PassThroughOutputNode_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift new file mode 100644 index 00000000000..6518d9c32ac --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift @@ -0,0 +1,47 @@ +// +// PassThroughOutputNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +class PassThroughOutputNode: NodeOutput { + + // MARK: Lifecycle + + init(parent: NodeOutput?) { + self.parent = parent + } + + // MARK: Internal + + let parent: NodeOutput? + + var hasUpdate = false + var isEnabled = true + + var outputPath: CGPath? { + if let parent = parent { + return parent.outputPath + } + return nil + } + + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + /// Changes to this node do not affect downstream nodes. + let parentUpdate = parent?.hasOutputUpdates(forFrame) ?? false + /// Changes to upstream nodes do, however, affect this nodes state. + hasUpdate = hasUpdate || parentUpdate + return parentUpdate + } + + func hasRenderUpdates(_ forFrame: CGFloat) -> Bool { + /// Return true if there are upstream updates or if this node has updates + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + hasUpdate = hasUpdate || upstreamUpdates + return hasUpdate + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift new file mode 100644 index 00000000000..74d153f4afc --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift @@ -0,0 +1,90 @@ +// +// PathNodeOutput.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +/// A node that has an output of a BezierPath +class PathOutputNode: NodeOutput { + + // MARK: Lifecycle + + init(parent: NodeOutput?) { + self.parent = parent + } + + // MARK: Internal + + let parent: NodeOutput? + + fileprivate(set) var outputPath: CGPath? = nil + + var lastUpdateFrame: CGFloat? = nil + var lastPathBuildFrame: CGFloat? = nil + var isEnabled = true + fileprivate(set) var totalLength: CGFloat = 0 + fileprivate(set) var pathObjects: [CompoundBezierPath] = [] + + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + guard isEnabled else { + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + outputPath = parent?.outputPath + return upstreamUpdates + } + + /// Ask if parent was updated + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + + /// If parent was updated and the path hasn't been built for this frame, clear the path. + if upstreamUpdates && lastPathBuildFrame != forFrame { + outputPath = nil + } + + if outputPath == nil { + /// If the path is clear, build the new path. + lastPathBuildFrame = forFrame + let newPath = CGMutablePath() + if let parentNode = parent, let parentPath = parentNode.outputPath { + newPath.addPath(parentPath) + } + for path in pathObjects { + for subPath in path.paths { + newPath.addPath(subPath.cgPath()) + } + } + outputPath = newPath + } + + /// Return true if there were upstream updates or if this node was updated. + return upstreamUpdates || (lastUpdateFrame == forFrame) + } + + @discardableResult + func removePaths(updateFrame: CGFloat?) -> [CompoundBezierPath] { + lastUpdateFrame = updateFrame + let returnPaths = pathObjects + outputPath = nil + totalLength = 0 + pathObjects = [] + return returnPaths + } + + func setPath(_ path: BezierPath, updateFrame: CGFloat) { + lastUpdateFrame = updateFrame + outputPath = nil + totalLength = path.length + pathObjects = [CompoundBezierPath(path: path)] + } + + func appendPath(_ path: CompoundBezierPath, updateFrame: CGFloat) { + lastUpdateFrame = updateFrame + outputPath = nil + totalLength = totalLength + path.length + pathObjects.append(path) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift new file mode 100644 index 00000000000..1631ea9dd08 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift @@ -0,0 +1,71 @@ +// +// FillRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation +import QuartzCore + +extension FillRule { + var cgFillRule: CGPathFillRule { + switch self { + case .evenOdd: + return .evenOdd + default: + return .winding + } + } + + var caFillRule: CAShapeLayerFillRule { + switch self { + case .evenOdd: + return CAShapeLayerFillRule.evenOdd + default: + return CAShapeLayerFillRule.nonZero + } + } +} + +// MARK: - FillRenderer + +/// A rendered for a Path Fill +final class FillRenderer: PassThroughOutputNode, Renderable { + var shouldRenderInContext = false + + var color: CGColor? { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var fillRule: FillRule = .none { + didSet { + hasUpdate = true + } + } + + func render(_: CGContext) { + // do nothing + } + + func setupSublayers(layer _: CAShapeLayer) { + // do nothing + } + + func updateShapeLayer(layer: CAShapeLayer) { + layer.fillColor = color + layer.opacity = Float(opacity) + layer.fillRule = fillRule.caFillRule + hasUpdate = false + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift new file mode 100644 index 00000000000..1986c11a693 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift @@ -0,0 +1,311 @@ +// +// GradientFillRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +public var lottieSwift_getPathNativeBoundingBox: ((CGPath) -> CGRect)? + +// MARK: - GradientFillLayer + +extension CGPath { + var stringRepresentation: String { + var result = "" + + var indent = 1 + self.applyWithBlock { element in + let indentString = Array(repeating: " ", count: indent * 2).joined(separator: "") + switch element.pointee.type { + case .moveToPoint: + let point = element.pointee.points.advanced(by: 0).pointee + result += indentString + (NSString(format: "moveto (%10.15f, %10.15f)\n", point.x, point.y) as String) + indent += 1 + case .addLineToPoint: + let point = element.pointee.points.advanced(by: 0).pointee + result += indentString + (NSString(format: "lineto (%10.15f, %10.15f)\n", point.x, point.y) as String) + case .addCurveToPoint: + let cp1 = element.pointee.points.advanced(by: 0).pointee + let cp2 = element.pointee.points.advanced(by: 1).pointee + let point = element.pointee.points.advanced(by: 2).pointee + result += indentString + (NSString(format: "curveto (%10.15f, %10.15f) (%10.15f, %10.15f) (%10.15f, %10.15f)\n", cp1.x, cp1.y, cp2.x, cp2.y, point.x, point.y) as String) + case .addQuadCurveToPoint: + let cp = element.pointee.points.advanced(by: 0).pointee + let point = element.pointee.points.advanced(by: 1).pointee + result += indentString + (NSString(format: "quadcurveto (%10.15f, %10.15f) (%10.15f, %10.15f)\n", cp.x, cp.y, point.x, point.y) as String) + case .closeSubpath: + result += indentString + "closepath\n" + indent -= 1 + @unknown default: + break + } + } + + return result + } +} + +private final class GradientFillLayer: CALayer, LottieDrawingLayer { + + var start: CGPoint = .zero { + didSet { + setNeedsDisplay() + } + } + + var numberOfColors = 0 { + didSet { + setNeedsDisplay() + } + } + + var colors: [CGFloat] = [] { + didSet { + setNeedsDisplay() + } + } + + var end: CGPoint = .zero { + didSet { + setNeedsDisplay() + } + } + + var type: GradientType = .none { + didSet { + setNeedsDisplay() + } + } + + override func draw(in ctx: CGContext) { + var alphaValues = [CGFloat]() + var alphaLocations = [CGFloat]() + + var gradientColors = [Color]() + var colorLocations = [CGFloat]() + let colorSpace = ctx.colorSpace ?? CGColorSpaceCreateDeviceRGB() + for i in 0.. ix { + gradientColors.append(Color(r: colors[ix + 1], g: colors[ix + 2], b: colors[ix + 3], a: 1.0)) + colorLocations.append(colors[ix]) + } + } + + var drawMask = false + for i in stride(from: numberOfColors * 4, to: colors.endIndex, by: 2) { + let alpha = colors[i + 1] + if alpha < 1 { + drawMask = true + } + alphaLocations.append(colors[i]) + alphaValues.append(alpha) + } + + if drawMask { + var locations: [CGFloat] = [] + for i in 0 ..< min(gradientColors.count, colorLocations.count) { + if !locations.contains(colorLocations[i]) { + locations.append(colorLocations[i]) + } + } + for i in 0 ..< min(alphaValues.count, alphaLocations.count) { + if !locations.contains(alphaLocations[i]) { + locations.append(alphaLocations[i]) + } + } + + locations.sort() + if locations[0] != 0.0 { + locations.insert(0.0, at: 0) + } + if locations[locations.count - 1] != 1.0 { + locations.append(1.0) + } + + var colors: [Color] = [] + + for location in locations { + var color: Color? + for i in 0 ..< min(gradientColors.count, colorLocations.count) - 1 { + if location >= colorLocations[i] && location <= colorLocations[i + 1] { + let localLocation: Double + if colorLocations[i] != colorLocations[i + 1] { + localLocation = location.remap(fromLow: colorLocations[i], fromHigh: colorLocations[i + 1], toLow: 0.0, toHigh: 1.0) + } else { + localLocation = 0.0 + } + let fromColor = gradientColors[i] + let toColor = gradientColors[i + 1] + color = fromColor.interpolate(to: toColor, amount: localLocation) + + break + } + } + + var alpha: CGFloat? + for i in 0 ..< min(alphaValues.count, alphaLocations.count) - 1 { + if location >= alphaLocations[i] && location <= alphaLocations[i + 1] { + let localLocation: Double + if alphaLocations[i] != alphaLocations[i + 1] { + localLocation = location.remap(fromLow: alphaLocations[i], fromHigh: alphaLocations[i + 1], toLow: 0.0, toHigh: 1.0) + } else { + localLocation = 0.0 + } + let fromAlpha = alphaValues[i] + let toAlpha = alphaValues[i + 1] + alpha = fromAlpha.interpolate(to: toAlpha, amount: localLocation) + + break + } + } + + var resultColor = color ?? gradientColors[0] + resultColor.a = alpha ?? 1.0 + + /*resultColor.r = 1.0 + resultColor.g = 0.0 + resultColor.b = 0.0 + resultColor.a = 1.0*/ + + colors.append(resultColor) + } + + gradientColors = colors + colorLocations = locations + } + + let cgGradientColors: [CGColor] = gradientColors.map { color -> CGColor in + return color.cgColorValue(colorSpace: colorSpace) + } + + guard let gradient = CGGradient(colorsSpace: colorSpace, colors: cgGradientColors as CFArray, locations: colorLocations) + else { return } + if type == .linear { + ctx.drawLinearGradient(gradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } else { + ctx.drawRadialGradient( + gradient, + startCenter: start, + startRadius: 0, + endCenter: start, + endRadius: start.distanceTo(end), + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } + } + +} + +// MARK: - GradientFillRenderer + +/// A rendered for a Path Fill +final class GradientFillRenderer: PassThroughOutputNode, Renderable { + + // MARK: Lifecycle + + override init(parent: NodeOutput?) { + super.init(parent: parent) + + maskLayer.fillColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 1, 1, 1]) + gradientLayer.mask = maskLayer + + maskLayer.actions = [ + "startPoint" : NSNull(), + "endPoint" : NSNull(), + "opacity" : NSNull(), + "locations" : NSNull(), + "colors" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "isRadial" : NSNull(), + "path" : NSNull(), + ] + gradientLayer.actions = maskLayer.actions + } + + // MARK: Internal + + var shouldRenderInContext = false + + var start: CGPoint = .zero { + didSet { + hasUpdate = true + } + } + + var numberOfColors = 0 { + didSet { + hasUpdate = true + } + } + + var colors: [CGFloat] = [] { + didSet { + hasUpdate = true + } + } + + var end: CGPoint = .zero { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var type: GradientType = .none { + didSet { + hasUpdate = true + } + } + + func render(_: CGContext) { + // do nothing + } + + func setupSublayers(layer: CAShapeLayer) { + layer.addSublayer(gradientLayer) + layer.fillColor = nil + } + + func updateShapeLayer(layer: CAShapeLayer) { + hasUpdate = false + + guard let path = layer.path else { + return + } + + let frame = lottieSwift_getPathNativeBoundingBox!(path) + + let anchor = (frame.size.width.isZero || frame.size.height.isZero) ? CGPoint() : CGPoint( + x: -frame.origin.x / frame.size.width, + y: -frame.origin.y / frame.size.height) + maskLayer.path = path + maskLayer.bounds = frame + maskLayer.anchorPoint = anchor + + gradientLayer.bounds = maskLayer.bounds + gradientLayer.anchorPoint = anchor + + // setup gradient properties + gradientLayer.start = start + gradientLayer.end = end + gradientLayer.numberOfColors = numberOfColors + gradientLayer.colors = colors + gradientLayer.opacity = Float(opacity) + gradientLayer.type = type + } + + // MARK: Private + + private let gradientLayer = GradientFillLayer() + private let maskLayer = LottieCAShapeLayer() + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift new file mode 100644 index 00000000000..526b452a37b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift @@ -0,0 +1,66 @@ +// +// GradientStrokeRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +// MARK: - Renderer + +final class GradientStrokeRenderer: PassThroughOutputNode, Renderable { + + // MARK: Lifecycle + + override init(parent: NodeOutput?) { + strokeRender = StrokeRenderer(parent: nil) + gradientRender = GradientFillRenderer(parent: nil) + strokeRender.color = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 1, 1, 1]) + super.init(parent: parent) + } + + // MARK: Internal + + var shouldRenderInContext = true + + let strokeRender: StrokeRenderer + let gradientRender: GradientFillRenderer + + override func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + let updates = super.hasOutputUpdates(forFrame) + return updates || strokeRender.hasUpdate || gradientRender.hasUpdate + } + + func updateShapeLayer(layer _: CAShapeLayer) { + /// Not Applicable + } + + func setupSublayers(layer _: CAShapeLayer) { + /// Not Applicable + } + + func render(_ inContext: CGContext) { + guard inContext.path != nil && inContext.path!.isEmpty == false else { + return + } + + strokeRender.hasUpdate = false + hasUpdate = false + gradientRender.hasUpdate = false + + strokeRender.setupForStroke(inContext) + + inContext.replacePathWithStrokedPath() + + /// Now draw the gradient. + gradientRender.render(inContext) + + } + + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { + strokeRender.renderBoundsFor(boundingBox) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift new file mode 100644 index 00000000000..64783d86b3a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift @@ -0,0 +1,153 @@ +// +// LegacyGradientFillRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +/// A rendered for a Path Fill +final class LegacyGradientFillRenderer: PassThroughOutputNode, Renderable { + + var shouldRenderInContext = true + + var start: CGPoint = .zero { + didSet { + hasUpdate = true + } + } + + var numberOfColors = 0 { + didSet { + hasUpdate = true + } + } + + var colors: [CGFloat] = [] { + didSet { + hasUpdate = true + } + } + + var end: CGPoint = .zero { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var type: GradientType = .none { + didSet { + hasUpdate = true + } + } + + func updateShapeLayer(layer _: CAShapeLayer) { + // Not applicable + } + + func setupSublayers(layer _: CAShapeLayer) { + // Not applicable + } + + func render(_ inContext: CGContext) { + guard inContext.path != nil && inContext.path!.isEmpty == false else { + return + } + hasUpdate = false + var alphaColors = [CGColor]() + var alphaLocations = [CGFloat]() + + var gradientColors = [CGColor]() + var colorLocations = [CGFloat]() + let colorSpace = CGColorSpaceCreateDeviceRGB() + let maskColorSpace = CGColorSpaceCreateDeviceGray() + for i in 0.. ix, let color = CGColor( + colorSpace: colorSpace, + components: [colors[ix + 1], colors[ix + 2], colors[ix + 3], 1]) + { + gradientColors.append(color) + colorLocations.append(colors[ix]) + } + } + + var drawMask = false + for i in stride(from: numberOfColors * 4, to: colors.endIndex, by: 2) { + let alpha = colors[i + 1] + if alpha < 1 { + drawMask = true + } + if let color = CGColor(colorSpace: maskColorSpace, components: [alpha, 1]) { + alphaLocations.append(colors[i]) + alphaColors.append(color) + } + } + + inContext.setAlpha(opacity) + inContext.clip() + + /// First draw a mask is necessary. + if drawMask { + guard + let maskGradient = CGGradient( + colorsSpace: maskColorSpace, + colors: alphaColors as CFArray, + locations: alphaLocations), + let maskContext = CGContext( + data: nil, + width: inContext.width, + height: inContext.height, + bitsPerComponent: 8, + bytesPerRow: inContext.width, + space: maskColorSpace, + bitmapInfo: 0) else { return } + let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(maskContext.height)) + maskContext.concatenate(flipVertical) + maskContext.concatenate(inContext.ctm) + if type == .linear { + maskContext.drawLinearGradient( + maskGradient, + start: start, + end: end, + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } else { + maskContext.drawRadialGradient( + maskGradient, + startCenter: start, + startRadius: 0, + endCenter: start, + endRadius: start.distanceTo(end), + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } + /// Clips the gradient + if let alphaMask = maskContext.makeImage() { + inContext.clip(to: inContext.boundingBoxOfClipPath, mask: alphaMask) + } + } + + /// Now draw the gradient + guard let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors as CFArray, locations: colorLocations) + else { return } + if type == .linear { + inContext.drawLinearGradient(gradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } else { + inContext.drawRadialGradient( + gradient, + startCenter: start, + startRadius: 0, + endCenter: start, + endRadius: start.distanceTo(end), + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift new file mode 100644 index 00000000000..77f152b50c8 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift @@ -0,0 +1,166 @@ +// +// StrokeRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +extension LineJoin { + var cgLineJoin: CGLineJoin { + switch self { + case .bevel: + return .bevel + case .none: + return .miter + case .miter: + return .miter + case .round: + return .round + } + } + + var caLineJoin: CAShapeLayerLineJoin { + switch self { + case .none: + return CAShapeLayerLineJoin.miter + case .miter: + return CAShapeLayerLineJoin.miter + case .round: + return CAShapeLayerLineJoin.round + case .bevel: + return CAShapeLayerLineJoin.bevel + } + } +} + +extension LineCap { + var cgLineCap: CGLineCap { + switch self { + case .none: + return .butt + case .butt: + return .butt + case .round: + return .round + case .square: + return .square + } + } + + var caLineCap: CAShapeLayerLineCap { + switch self { + case .none: + return CAShapeLayerLineCap.butt + case .butt: + return CAShapeLayerLineCap.butt + case .round: + return CAShapeLayerLineCap.round + case .square: + return CAShapeLayerLineCap.square + } + } +} + +// MARK: - StrokeRenderer + +/// A rendered that renders a stroke on a path. +final class StrokeRenderer: PassThroughOutputNode, Renderable { + + var shouldRenderInContext = false + + var color: CGColor? { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var width: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var miterLimit: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var lineCap: LineCap = .none { + didSet { + hasUpdate = true + } + } + + var lineJoin: LineJoin = .none { + didSet { + hasUpdate = true + } + } + + var dashPhase: CGFloat? { + didSet { + hasUpdate = true + } + } + + var dashLengths: [CGFloat]? { + didSet { + hasUpdate = true + } + } + + func setupSublayers(layer _: CAShapeLayer) { + // empty + } + + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { + boundingBox.insetBy(dx: -width, dy: -width) + } + + func setupForStroke(_ inContext: CGContext) { + inContext.setLineWidth(width) + inContext.setMiterLimit(miterLimit) + inContext.setLineCap(lineCap.cgLineCap) + inContext.setLineJoin(lineJoin.cgLineJoin) + if let dashPhase = dashPhase, let lengths = dashLengths { + inContext.setLineDash(phase: dashPhase, lengths: lengths) + } else { + inContext.setLineDash(phase: 0, lengths: []) + } + } + + func render(_ inContext: CGContext) { + guard inContext.path != nil && inContext.path!.isEmpty == false else { + return + } + guard let color = color else { return } + hasUpdate = false + setupForStroke(inContext) + inContext.setAlpha(opacity) + inContext.setStrokeColor(color) + inContext.strokePath() + } + + func updateShapeLayer(layer: CAShapeLayer) { + layer.strokeColor = color + layer.opacity = Float(opacity) + layer.lineWidth = width + layer.lineJoin = lineJoin.caLineJoin + layer.lineCap = lineCap.caLineCap + layer.lineDashPhase = dashPhase ?? 0 + layer.fillColor = nil + if let dashPattern = dashLengths { + layer.lineDashPattern = dashPattern.map({ NSNumber(value: Double($0)) }) + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift new file mode 100644 index 00000000000..89ffffbbb2d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift @@ -0,0 +1,139 @@ +// +// EllipseNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import Foundation +import QuartzCore + +// MARK: - EllipseNodeProperties + +final class EllipseNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(ellipse: Ellipse) { + keypathName = ellipse.name + direction = ellipse.direction + position = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.position.keyframes)) + size = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.size.keyframes)) + keypathProperties = [ + "Position" : position, + "Size" : size, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let direction: PathDirection + let position: NodeProperty + let size: NodeProperty + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] +} + +// MARK: - EllipseNode + +final class EllipseNode: AnimatorNode, PathNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, ellipse: Ellipse) { + pathOutput = PathOutputNode(parent: parentNode?.outputNode) + properties = EllipseNodeProperties(ellipse: ellipse) + self.parentNode = parentNode + } + + // MARK: Internal + + static let ControlPointConstant: CGFloat = 0.55228 + + let pathOutput: PathOutputNode + + let properties: EllipseNodeProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + // MARK: Animator Node + + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var isEnabled = true { + didSet { + pathOutput.isEnabled = isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + pathOutput.setPath( + .ellipse( + size: properties.size.value.sizeValue, + center: properties.position.value.pointValue, + direction: properties.direction), + updateFrame: frame) + } + +} + +extension BezierPath { + /// Constructs a `BezierPath` in the shape of an ellipse + static func ellipse( + size: CGSize, + center: CGPoint, + direction: PathDirection) + -> BezierPath + { + // Unfortunately we HAVE to manually build out the ellipse. + // Every Apple method constructs an ellipse from the 3 o-clock position + // After effects constructs from the Noon position. + // After effects does clockwise, but also has a flag for reversed. + var half = size * 0.5 + if direction == .counterClockwise { + half.width = half.width * -1 + } + + let q1 = CGPoint(x: center.x, y: center.y - half.height) + let q2 = CGPoint(x: center.x + half.width, y: center.y) + let q3 = CGPoint(x: center.x, y: center.y + half.height) + let q4 = CGPoint(x: center.x - half.width, y: center.y) + + let cp = half * EllipseNode.ControlPointConstant + + var path = BezierPath(startPoint: CurveVertex( + point: q1, + inTangentRelative: CGPoint(x: -cp.width, y: 0), + outTangentRelative: CGPoint(x: cp.width, y: 0))) + path.addVertex(CurveVertex( + point: q2, + inTangentRelative: CGPoint(x: 0, y: -cp.height), + outTangentRelative: CGPoint(x: 0, y: cp.height))) + + path.addVertex(CurveVertex( + point: q3, + inTangentRelative: CGPoint(x: cp.width, y: 0), + outTangentRelative: CGPoint(x: -cp.width, y: 0))) + + path.addVertex(CurveVertex( + point: q4, + inTangentRelative: CGPoint(x: 0, y: cp.height), + outTangentRelative: CGPoint(x: 0, y: -cp.height))) + + path.addVertex(CurveVertex( + point: q1, + inTangentRelative: CGPoint(x: -cp.width, y: 0), + outTangentRelative: CGPoint(x: cp.width, y: 0))) + path.close() + return path + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift new file mode 100644 index 00000000000..57af7df6ab3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift @@ -0,0 +1,170 @@ +// +// PolygonNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/21/19. +// + +import Foundation +import QuartzCore + +// MARK: - PolygonNodeProperties + +final class PolygonNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(star: Star) { + keypathName = star.name + direction = star.direction + position = NodeProperty(provider: KeyframeInterpolator(keyframes: star.position.keyframes)) + outerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRadius.keyframes)) + outerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRoundness.keyframes)) + rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes)) + points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes)) + keypathProperties = [ + "Position" : position, + "Outer Radius" : outerRadius, + "Outer Roundedness" : outerRoundedness, + "Rotation" : rotation, + "Points" : points, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + var childKeypaths: [KeypathSearchable] = [] + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + + let direction: PathDirection + let position: NodeProperty + let outerRadius: NodeProperty + let outerRoundedness: NodeProperty + let rotation: NodeProperty + let points: NodeProperty +} + +// MARK: - PolygonNode + +final class PolygonNode: AnimatorNode, PathNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, star: Star) { + pathOutput = PathOutputNode(parent: parentNode?.outputNode) + properties = PolygonNodeProperties(star: star) + self.parentNode = parentNode + } + + // MARK: Internal + + /// Magic number needed for constructing path. + static let PolygonConstant: CGFloat = 0.25 + + let properties: PolygonNodeProperties + + let pathOutput: PathOutputNode + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + // MARK: Animator Node + + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var isEnabled = true { + didSet { + pathOutput.isEnabled = isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + let path = BezierPath.polygon( + position: properties.position.value.pointValue, + numberOfPoints: properties.points.value.cgFloatValue, + outerRadius: properties.outerRadius.value.cgFloatValue, + outerRoundedness: properties.outerRoundedness.value.cgFloatValue, + rotation: properties.rotation.value.cgFloatValue, + direction: properties.direction) + + pathOutput.setPath(path, updateFrame: frame) + } + +} + +extension BezierPath { + /// Creates a `BezierPath` in the shape of a polygon + static func polygon( + position: CGPoint, + numberOfPoints: CGFloat, + outerRadius: CGFloat, + outerRoundedness inputOuterRoundedness: CGFloat, + rotation: CGFloat, + direction: PathDirection) + -> BezierPath + { + var currentAngle = (rotation - 90).toRadians() + let anglePerPoint = ((2 * CGFloat.pi) / numberOfPoints) + let outerRoundedness = inputOuterRoundedness * 0.01 + + var point = CGPoint( + x: outerRadius * cos(currentAngle), + y: outerRadius * sin(currentAngle)) + var vertices = [CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)] + + var previousPoint = point + currentAngle += anglePerPoint; + for _ in 0.. + let size: NodeProperty + let cornerRadius: NodeProperty + +} + +// MARK: - RectangleNode + +final class RectangleNode: AnimatorNode, PathNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, rectangle: Rectangle) { + properties = RectNodeProperties(rectangle: rectangle) + pathOutput = PathOutputNode(parent: parentNode?.outputNode) + self.parentNode = parentNode + } + + // MARK: Internal + + let properties: RectNodeProperties + + let pathOutput: PathOutputNode + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + // MARK: Animator Node + + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var isEnabled = true { + didSet { + pathOutput.isEnabled = isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + pathOutput.setPath( + .rectangle( + position: properties.position.value.pointValue, + size: properties.size.value.sizeValue, + cornerRadius: properties.cornerRadius.value.cgFloatValue, + direction: properties.direction), + updateFrame: frame) + } + +} + +// MARK: - BezierPath + rectangle + +extension BezierPath { + /// Constructs a `BezierPath` in the shape of a rectangle, optionally with rounded corners + static func rectangle( + position: CGPoint, + size inputSize: CGSize, + cornerRadius: CGFloat, + direction: PathDirection) + -> BezierPath + { + let size = inputSize * 0.5 + let radius = min(min(cornerRadius, size.width) , size.height) + + var bezierPath = BezierPath() + let points: [CurveVertex] + + if radius <= 0 { + /// No Corners + points = [ + /// Lead In + CurveVertex( + point: CGPoint(x: size.width, y: -size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 1 + CurveVertex( + point: CGPoint(x: size.width, y: size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 2 + CurveVertex( + point: CGPoint(x: -size.width, y: size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 3 + CurveVertex( + point: CGPoint(x: -size.width, y: -size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 4 + CurveVertex( + point: CGPoint(x: size.width, y: -size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + ] + } else { + let controlPoint = radius * EllipseNode.ControlPointConstant + points = [ + /// Lead In + CurveVertex( + CGPoint(x: radius, y: 0), + CGPoint(x: radius, y: 0), + CGPoint(x: radius, y: 0)) + .translated(CGPoint(x: -radius, y: radius)) + .translated(CGPoint(x: size.width, y: -size.height)) + .translated(position), + /// Corner 1 + CurveVertex( + CGPoint(x: radius, y: 0), // In tangent + CGPoint(x: radius, y: 0), // Point + CGPoint(x: radius, y: controlPoint)) + .translated(CGPoint(x: -radius, y: -radius)) + .translated(CGPoint(x: size.width, y: size.height)) + .translated(position), + CurveVertex( + CGPoint(x: controlPoint, y: radius), // In tangent + CGPoint(x: 0, y: radius), // Point + CGPoint(x: 0, y: radius)) // Out Tangent + .translated(CGPoint(x: -radius, y: -radius)) + .translated(CGPoint(x: size.width, y: size.height)) + .translated(position), + /// Corner 2 + CurveVertex( + CGPoint(x: 0, y: radius), // In tangent + CGPoint(x: 0, y: radius), // Point + CGPoint(x: -controlPoint, y: radius))// Out tangent + .translated(CGPoint(x: radius, y: -radius)) + .translated(CGPoint(x: -size.width, y: size.height)) + .translated(position), + CurveVertex( + CGPoint(x: -radius, y: controlPoint), // In tangent + CGPoint(x: -radius, y: 0), // Point + CGPoint(x: -radius, y: 0)) // Out tangent + .translated(CGPoint(x: radius, y: -radius)) + .translated(CGPoint(x: -size.width, y: size.height)) + .translated(position), + /// Corner 3 + CurveVertex( + CGPoint(x: -radius, y: 0), // In tangent + CGPoint(x: -radius, y: 0), // Point + CGPoint(x: -radius, y: -controlPoint)) // Out tangent + .translated(CGPoint(x: radius, y: radius)) + .translated(CGPoint(x: -size.width, y: -size.height)) + .translated(position), + CurveVertex( + CGPoint(x: -controlPoint, y: -radius), // In tangent + CGPoint(x: 0, y: -radius), // Point + CGPoint(x: 0, y: -radius)) // Out tangent + .translated(CGPoint(x: radius, y: radius)) + .translated(CGPoint(x: -size.width, y: -size.height)) + .translated(position), + /// Corner 4 + CurveVertex( + CGPoint(x: 0, y: -radius), // In tangent + CGPoint(x: 0, y: -radius), // Point + CGPoint(x: controlPoint, y: -radius)) // Out tangent + .translated(CGPoint(x: -radius, y: radius)) + .translated(CGPoint(x: size.width, y: -size.height)) + .translated(position), + CurveVertex( + CGPoint(x: radius, y: -controlPoint), // In tangent + CGPoint(x: radius, y: 0), // Point + CGPoint(x: radius, y: 0)) // Out tangent + .translated(CGPoint(x: -radius, y: radius)) + .translated(CGPoint(x: size.width, y: -size.height)) + .translated(position), + ] + } + let reversed = direction == .counterClockwise + let pathPoints = reversed ? points.reversed() : points + for point in pathPoints { + bezierPath.addVertex(reversed ? point.reversed() : point) + } + bezierPath.close() + return bezierPath + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift new file mode 100644 index 00000000000..7bc7d9055d2 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift @@ -0,0 +1,74 @@ +// +// PathNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/16/19. +// + +import CoreGraphics +import Foundation + +// MARK: - ShapeNodeProperties + +final class ShapeNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(shape: Shape) { + keypathName = shape.name + path = NodeProperty(provider: KeyframeInterpolator(keyframes: shape.path.keyframes)) + keypathProperties = [ + "Path" : path, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let path: NodeProperty + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +// MARK: - ShapeNode + +final class ShapeNode: AnimatorNode, PathNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, shape: Shape) { + pathOutput = PathOutputNode(parent: parentNode?.outputNode) + properties = ShapeNodeProperties(shape: shape) + self.parentNode = parentNode + } + + // MARK: Internal + + let properties: ShapeNodeProperties + + let pathOutput: PathOutputNode + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + // MARK: Animator Node + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var isEnabled = true { + didSet { + pathOutput.isEnabled = isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + pathOutput.setPath(properties.path.value, updateFrame: frame) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift new file mode 100644 index 00000000000..11ef751d6df --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift @@ -0,0 +1,222 @@ +// +// StarNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/21/19. +// + +import Foundation +import QuartzCore + +// MARK: - StarNodeProperties + +final class StarNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(star: Star) { + keypathName = star.name + direction = star.direction + position = NodeProperty(provider: KeyframeInterpolator(keyframes: star.position.keyframes)) + outerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRadius.keyframes)) + outerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRoundness.keyframes)) + if let innerRadiusKeyframes = star.innerRadius?.keyframes { + innerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: innerRadiusKeyframes)) + } else { + innerRadius = NodeProperty(provider: SingleValueProvider(Vector1D(0))) + } + if let innderRoundedness = star.innerRoundness?.keyframes { + innerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: innderRoundedness)) + } else { + innerRoundedness = NodeProperty(provider: SingleValueProvider(Vector1D(0))) + } + rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes)) + points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes)) + keypathProperties = [ + "Position" : position, + "Outer Radius" : outerRadius, + "Outer Roundedness" : outerRoundedness, + "Inner Radius" : innerRadius, + "Inner Roundedness" : innerRoundedness, + "Rotation" : rotation, + "Points" : points, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + + let direction: PathDirection + let position: NodeProperty + let outerRadius: NodeProperty + let outerRoundedness: NodeProperty + let innerRadius: NodeProperty + let innerRoundedness: NodeProperty + let rotation: NodeProperty + let points: NodeProperty +} + +// MARK: - StarNode + +final class StarNode: AnimatorNode, PathNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, star: Star) { + pathOutput = PathOutputNode(parent: parentNode?.outputNode) + properties = StarNodeProperties(star: star) + self.parentNode = parentNode + } + + // MARK: Internal + + /// Magic number needed for building path data + static let PolystarConstant: CGFloat = 0.47829 + + let properties: StarNodeProperties + + let pathOutput: PathOutputNode + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + // MARK: Animator Node + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var isEnabled = true { + didSet { + pathOutput.isEnabled = isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + let path = BezierPath.star( + position: properties.position.value.pointValue, + outerRadius: properties.outerRadius.value.cgFloatValue, + innerRadius: properties.innerRadius.value.cgFloatValue, + outerRoundedness: properties.outerRoundedness.value.cgFloatValue, + innerRoundedness: properties.innerRoundedness.value.cgFloatValue, + numberOfPoints: properties.points.value.cgFloatValue, + rotation: properties.rotation.value.cgFloatValue, + direction: properties.direction) + + pathOutput.setPath(path, updateFrame: frame) + } + +} + +extension BezierPath { + /// Constructs a `BezierPath` in the shape of a star + static func star( + position: CGPoint, + outerRadius: CGFloat, + innerRadius: CGFloat, + outerRoundedness inoutOuterRoundedness: CGFloat, + innerRoundedness inputInnerRoundedness: CGFloat, + numberOfPoints: CGFloat, + rotation: CGFloat, + direction: PathDirection) + -> BezierPath + { + var currentAngle = (rotation - 90).toRadians() + let anglePerPoint = (2 * CGFloat.pi) / numberOfPoints + let halfAnglePerPoint = anglePerPoint / 2.0 + let partialPointAmount = numberOfPoints - floor(numberOfPoints) + let outerRoundedness = inoutOuterRoundedness * 0.01 + let innerRoundedness = inputInnerRoundedness * 0.01 + + var point: CGPoint = .zero + + var partialPointRadius: CGFloat = 0 + if partialPointAmount != 0 { + currentAngle += halfAnglePerPoint * (1 - partialPointAmount) + partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius) + point.x = (partialPointRadius * cos(currentAngle)) + point.y = (partialPointRadius * sin(currentAngle)) + currentAngle += anglePerPoint * partialPointAmount / 2 + } else { + point.x = (outerRadius * cos(currentAngle)) + point.y = (outerRadius * sin(currentAngle)) + currentAngle += halfAnglePerPoint + } + + var vertices = [CurveVertex]() + vertices.append(CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)) + + var previousPoint = point + var longSegment = false + let numPoints = Int(ceil(numberOfPoints) * 2) + for i in 0.. + let position: NodeProperty + let scale: NodeProperty + let rotation: NodeProperty + let opacity: NodeProperty + let skew: NodeProperty + let skewAxis: NodeProperty + + var caTransform: CATransform3D { + let result = CATransform3D.makeTransform( + anchor: anchor.value.pointValue, + position: position.value.pointValue, + scale: scale.value.sizeValue, + rotation: rotation.value.cgFloatValue, + skew: skew.value.cgFloatValue, + skewAxis: skewAxis.value.cgFloatValue) + return result + } +} + +// MARK: - GroupNode + +final class GroupNode: AnimatorNode { + + // MARK: Lifecycle + + // MARK: Initializer + init(name: String, parentNode: AnimatorNode?, tree: NodeTree) { + self.parentNode = parentNode + keypathName = name + rootNode = tree.rootNode + properties = GroupNodeProperties(transform: tree.transform) + groupOutput = GroupOutputNode(parent: parentNode?.outputNode, rootNode: rootNode?.outputNode) + var childKeypaths: [KeypathSearchable] = tree.childrenNodes + childKeypaths.append(properties) + self.childKeypaths = childKeypaths + + for childContainer in tree.renderContainers { + container.insertRenderLayer(childContainer) + } + } + + // MARK: Internal + + // MARK: Properties + let groupOutput: GroupOutputNode + + let properties: GroupNodeProperties + + let rootNode: AnimatorNode? + + var container = ShapeContainerLayer() + + // MARK: Keypath Searchable + + let keypathName: String + + let childKeypaths: [KeypathSearchable] + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + var keypathLayer: CALayer? { + container + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var outputNode: NodeOutput { + groupOutput + } + + var isEnabled = true { + didSet { + container.isHidden = !isEnabled + } + } + + func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool { + rootNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false + } + + func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) { + rootNode?.updateOutputs(frame, forceOutputUpdate: forceOutputUpdate) + } + + func rebuildOutputs(frame: CGFloat) { + container.opacity = Float(properties.opacity.value.cgFloatValue) * 0.01 + container.transform = properties.caTransform + groupOutput.setTransform(container.transform, forFrame: frame) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift new file mode 100644 index 00000000000..bb00759175c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift @@ -0,0 +1,90 @@ +// +// FillNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import CoreGraphics +import Foundation + +// MARK: - FillNodeProperties + +final class FillNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(fill: Fill) { + keypathName = fill.name + color = NodeProperty(provider: KeyframeInterpolator(keyframes: fill.color.keyframes)) + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: fill.opacity.keyframes)) + type = fill.fillRule + keypathProperties = [ + "Opacity" : opacity, + PropertyName.color.rawValue : color, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let opacity: NodeProperty + let color: NodeProperty + let type: FillRule + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +// MARK: - FillNode + +final class FillNode: AnimatorNode, RenderNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, fill: Fill) { + fillRender = FillRenderer(parent: parentNode?.outputNode) + fillProperties = FillNodeProperties(fill: fill) + self.parentNode = parentNode + } + + // MARK: Internal + + let fillRender: FillRenderer + + let fillProperties: FillNodeProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + var renderer: NodeOutput & Renderable { + fillRender + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + fillProperties + } + + var isEnabled = true { + didSet { + fillRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + false + } + + func rebuildOutputs(frame _: CGFloat) { + fillRender.color = fillProperties.color.value.cgColorValue + fillRender.opacity = fillProperties.opacity.value.cgFloatValue * 0.01 + fillRender.fillRule = fillProperties.type + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift new file mode 100644 index 00000000000..be05c07408d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift @@ -0,0 +1,102 @@ +// +// GradientFillNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation +import QuartzCore + +// MARK: - GradientFillProperties + +final class GradientFillProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(gradientfill: GradientFill) { + keypathName = gradientfill.name + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.opacity.keyframes)) + startPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.startPoint.keyframes)) + endPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.endPoint.keyframes)) + colors = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.colors.keyframes)) + gradientType = gradientfill.gradientType + numberOfColors = gradientfill.numberOfColors + keypathProperties = [ + "Opacity" : opacity, + "Start Point" : startPoint, + "End Point" : endPoint, + "Colors" : colors, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let opacity: NodeProperty + let startPoint: NodeProperty + let endPoint: NodeProperty + let colors: NodeProperty<[Double]> + + let gradientType: GradientType + let numberOfColors: Int + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +// MARK: - GradientFillNode + +final class GradientFillNode: AnimatorNode, RenderNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, gradientFill: GradientFill) { + fillRender = GradientFillRenderer(parent: parentNode?.outputNode) + fillProperties = GradientFillProperties(gradientfill: gradientFill) + self.parentNode = parentNode + } + + // MARK: Internal + + let fillRender: GradientFillRenderer + + let fillProperties: GradientFillProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + var renderer: NodeOutput & Renderable { + fillRender + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + fillProperties + } + + var isEnabled = true { + didSet { + fillRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + false + } + + func rebuildOutputs(frame _: CGFloat) { + fillRender.start = fillProperties.startPoint.value.pointValue + fillRender.end = fillProperties.endPoint.value.pointValue + fillRender.opacity = fillProperties.opacity.value.cgFloatValue * 0.01 + fillRender.colors = fillProperties.colors.value.map { CGFloat($0) } + fillRender.type = fillProperties.gradientType + fillRender.numberOfColors = fillProperties.numberOfColors + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift new file mode 100644 index 00000000000..08ac1477675 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift @@ -0,0 +1,151 @@ +// +// GradientStrokeNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/23/19. +// + +import CoreGraphics +import Foundation + +// MARK: - GradientStrokeProperties + +final class GradientStrokeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(gradientStroke: GradientStroke) { + keypathName = gradientStroke.name + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.opacity.keyframes)) + startPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.startPoint.keyframes)) + endPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.endPoint.keyframes)) + colors = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.colors.keyframes)) + gradientType = gradientStroke.gradientType + numberOfColors = gradientStroke.numberOfColors + width = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.width.keyframes)) + miterLimit = CGFloat(gradientStroke.miterLimit) + lineCap = gradientStroke.lineCap + lineJoin = gradientStroke.lineJoin + + if let dashes = gradientStroke.dashPattern { + var dashPatterns = ContiguousArray>>() + var dashPhase = ContiguousArray>() + for dash in dashes { + if dash.type == .offset { + dashPhase = dash.value.keyframes + } else { + dashPatterns.append(dash.value.keyframes) + } + } + dashPattern = NodeProperty(provider: GroupInterpolator(keyframeGroups: dashPatterns)) + self.dashPhase = NodeProperty(provider: KeyframeInterpolator(keyframes: dashPhase)) + } else { + dashPattern = NodeProperty(provider: SingleValueProvider([Vector1D]())) + dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) + } + keypathProperties = [ + "Opacity" : opacity, + "Start Point" : startPoint, + "End Point" : endPoint, + "Colors" : colors, + "Stroke Width" : width, + "Dashes" : dashPattern, + "Dash Phase" : dashPhase, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let opacity: NodeProperty + let startPoint: NodeProperty + let endPoint: NodeProperty + let colors: NodeProperty<[Double]> + let width: NodeProperty + + let dashPattern: NodeProperty<[Vector1D]> + let dashPhase: NodeProperty + + let lineCap: LineCap + let lineJoin: LineJoin + let miterLimit: CGFloat + let gradientType: GradientType + let numberOfColors: Int + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +// MARK: - GradientStrokeNode + +final class GradientStrokeNode: AnimatorNode, RenderNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, gradientStroke: GradientStroke) { + strokeRender = GradientStrokeRenderer(parent: parentNode?.outputNode) + strokeProperties = GradientStrokeProperties(gradientStroke: gradientStroke) + self.parentNode = parentNode + } + + // MARK: Internal + + let strokeRender: GradientStrokeRenderer + + let strokeProperties: GradientStrokeProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + var renderer: NodeOutput & Renderable { + strokeRender + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + strokeProperties + } + + var isEnabled = true { + didSet { + strokeRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + false + } + + func rebuildOutputs(frame _: CGFloat) { + /// Update gradient properties + strokeRender.gradientRender.start = strokeProperties.startPoint.value.pointValue + strokeRender.gradientRender.end = strokeProperties.endPoint.value.pointValue + strokeRender.gradientRender.opacity = strokeProperties.opacity.value.cgFloatValue + strokeRender.gradientRender.colors = strokeProperties.colors.value.map { CGFloat($0) } + strokeRender.gradientRender.type = strokeProperties.gradientType + strokeRender.gradientRender.numberOfColors = strokeProperties.numberOfColors + + /// Now update stroke properties + strokeRender.strokeRender.opacity = strokeProperties.opacity.value.cgFloatValue + strokeRender.strokeRender.width = strokeProperties.width.value.cgFloatValue + strokeRender.strokeRender.miterLimit = strokeProperties.miterLimit + strokeRender.strokeRender.lineCap = strokeProperties.lineCap + strokeRender.strokeRender.lineJoin = strokeProperties.lineJoin + + /// Get dash lengths + let dashLengths = strokeProperties.dashPattern.value.map { $0.cgFloatValue } + if dashLengths.count > 0 { + strokeRender.strokeRender.dashPhase = strokeProperties.dashPhase.value.cgFloatValue + strokeRender.strokeRender.dashLengths = dashLengths + } else { + strokeRender.strokeRender.dashLengths = nil + strokeRender.strokeRender.dashPhase = nil + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp new file mode 100644 index 00000000000..731a81149c2 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp @@ -0,0 +1,36 @@ +#ifndef StrokeNode_hpp +#define StrokeNode_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp" +#include "Lottie/Private/Model/ShapeItems/Stroke.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp" + +namespace lottie { + +class StrokeShapeDashConfiguration { +public: + StrokeShapeDashConfiguration(std::vector const &elements) { + /// Converts the `[DashElement]` data model into `lineDashPattern` and `lineDashPhase` + /// representations usable in a `CAShapeLayer` + for (const auto &dash : elements) { + if (dash.type == DashElementType::Offset) { + dashPhase = dash.value.keyframes; + } else { + dashPatterns.push_back(dash.value.keyframes); + } + } + } + +public: + std::vector>> dashPatterns; + std::vector> dashPhase; +}; + +} + +#endif /* StrokeNode_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift new file mode 100644 index 00000000000..387dfafffb4 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift @@ -0,0 +1,153 @@ +// +// StrokeNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation +import QuartzCore + +// MARK: - StrokeNodeProperties + +final class StrokeNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(stroke: Stroke) { + keypathName = stroke.name + color = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.color.keyframes)) + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.opacity.keyframes)) + width = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.width.keyframes)) + miterLimit = CGFloat(stroke.miterLimit) + lineCap = stroke.lineCap + lineJoin = stroke.lineJoin + + if let dashes = stroke.dashPattern { + let (dashPatterns, dashPhase) = dashes.shapeLayerConfiguration + dashPattern = NodeProperty(provider: GroupInterpolator(keyframeGroups: dashPatterns)) + if dashPhase.count == 0 { + self.dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) + } else { + self.dashPhase = NodeProperty(provider: KeyframeInterpolator(keyframes: dashPhase)) + } + } else { + dashPattern = NodeProperty(provider: SingleValueProvider([Vector1D]())) + dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) + } + keypathProperties = [ + "Opacity" : opacity, + PropertyName.color.rawValue : color, + "Stroke Width" : width, + "Dashes" : dashPattern, + "Dash Phase" : dashPhase, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + let keypathName: String + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + + let opacity: NodeProperty + let color: NodeProperty + let width: NodeProperty + + let dashPattern: NodeProperty<[Vector1D]> + let dashPhase: NodeProperty + + let lineCap: LineCap + let lineJoin: LineJoin + let miterLimit: CGFloat + +} + +// MARK: - StrokeNode + +/// Node that manages stroking a path +final class StrokeNode: AnimatorNode, RenderNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, stroke: Stroke) { + strokeRender = StrokeRenderer(parent: parentNode?.outputNode) + strokeProperties = StrokeNodeProperties(stroke: stroke) + self.parentNode = parentNode + } + + // MARK: Internal + + let strokeRender: StrokeRenderer + + let strokeProperties: StrokeNodeProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + var renderer: NodeOutput & Renderable { + strokeRender + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + strokeProperties + } + + var isEnabled = true { + didSet { + strokeRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + false + } + + func rebuildOutputs(frame _: CGFloat) { + strokeRender.color = strokeProperties.color.value.cgColorValue + strokeRender.opacity = strokeProperties.opacity.value.cgFloatValue * 0.01 + strokeRender.width = strokeProperties.width.value.cgFloatValue + strokeRender.miterLimit = strokeProperties.miterLimit + strokeRender.lineCap = strokeProperties.lineCap + strokeRender.lineJoin = strokeProperties.lineJoin + + /// Get dash lengths + let dashLengths = strokeProperties.dashPattern.value.map { $0.cgFloatValue } + if dashLengths.count > 0 { + strokeRender.dashPhase = strokeProperties.dashPhase.value.cgFloatValue + strokeRender.dashLengths = dashLengths + } else { + strokeRender.dashLengths = nil + strokeRender.dashPhase = nil + } + } + +} + +// MARK: - [DashElement] + shapeLayerConfiguration + +extension Array where Element == DashElement { + typealias ShapeLayerConfiguration = ( + dashPatterns: ContiguousArray>>, + dashPhase: ContiguousArray>) + + /// Converts the `[DashElement]` data model into `lineDashPattern` and `lineDashPhase` + /// representations usable in a `CAShapeLayer` + var shapeLayerConfiguration: ShapeLayerConfiguration { + var dashPatterns = ContiguousArray>>() + var dashPhase = ContiguousArray>() + for dash in self { + if dash.type == .offset { + dashPhase = dash.value.keyframes + } else { + dashPatterns.append(dash.value.keyframes) + } + } + return (dashPatterns, dashPhase) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp new file mode 100644 index 00000000000..3a3386e1da7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp @@ -0,0 +1,361 @@ +#ifndef TextAnimatorNode_hpp +#define TextAnimatorNode_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp" +#include "Lottie/Private/Model/Text/TextAnimator.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" + +namespace lottie { + +class TextAnimatorNodeProperties: public KeypathSearchableNodePropertyMap { +public: + TextAnimatorNodeProperties(std::shared_ptr const &textAnimator) { + _keypathName = textAnimator->name.value_or(""); + + if (textAnimator->anchor) { + _anchor = std::make_shared>(std::make_shared>(textAnimator->anchor->keyframes)); + _keypathProperties.insert(std::make_pair("Anchor", _anchor)); + } + + if (textAnimator->position) { + _position = std::make_shared>(std::make_shared>(textAnimator->position->keyframes)); + _keypathProperties.insert(std::make_pair("Position", _position)); + } + + if (textAnimator->scale) { + _scale = std::make_shared>(std::make_shared>(textAnimator->scale->keyframes)); + _keypathProperties.insert(std::make_pair("Scale", _scale)); + } + + if (textAnimator->skew) { + _skew = std::make_shared>(std::make_shared>(textAnimator->skew->keyframes)); + _keypathProperties.insert(std::make_pair("Skew", _skew)); + } + + if (textAnimator->skewAxis) { + _skewAxis = std::make_shared>(std::make_shared>(textAnimator->skewAxis->keyframes)); + _keypathProperties.insert(std::make_pair("Skew Axis", _skewAxis)); + } + + if (textAnimator->rotation) { + _rotation = std::make_shared>(std::make_shared>(textAnimator->rotation->keyframes)); + _keypathProperties.insert(std::make_pair("Rotation", _rotation)); + } + + if (textAnimator->rotation) { + _opacity = std::make_shared>(std::make_shared>(textAnimator->opacity->keyframes)); + _keypathProperties.insert(std::make_pair("Opacity", _opacity)); + } + + if (textAnimator->strokeColor) { + _strokeColor = std::make_shared>(std::make_shared>(textAnimator->strokeColor->keyframes)); + _keypathProperties.insert(std::make_pair("Stroke Color", _strokeColor)); + } + + if (textAnimator->fillColor) { + _fillColor = std::make_shared>(std::make_shared>(textAnimator->fillColor->keyframes)); + _keypathProperties.insert(std::make_pair("Fill Color", _fillColor)); + } + + if (textAnimator->strokeWidth) { + _strokeWidth = std::make_shared>(std::make_shared>(textAnimator->strokeWidth->keyframes)); + _keypathProperties.insert(std::make_pair("Stroke Width", _strokeWidth)); + } + + if (textAnimator->tracking) { + _tracking = std::make_shared>(std::make_shared>(textAnimator->tracking->keyframes)); + _keypathProperties.insert(std::make_pair("Tracking", _tracking)); + } + + for (const auto &it : _keypathProperties) { + _properties.push_back(it.second); + } + } + + virtual std::string keypathName() const override { + return _keypathName; + } + + virtual std::map> keypathProperties() const override { + return _keypathProperties; + } + + virtual std::vector> &properties() override { + return _properties; + } + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + CATransform3D caTransform() { + Vector2D anchor = Vector2D::Zero(); + if (_anchor) { + auto anchor3d = _anchor->value(); + anchor = Vector2D(anchor3d.x, anchor3d.y); + } + + Vector2D position = Vector2D::Zero(); + if (_position) { + auto position3d = _position->value(); + position = Vector2D(position3d.x, position3d.y); + } + + Vector2D scale = Vector2D(100.0, 100.0); + if (_scale) { + auto scale3d = _scale->value(); + scale = Vector2D(scale3d.x, scale3d.y); + } + + double rotation = 0.0; + if (_rotation) { + rotation = _rotation->value().value; + } + + std::optional skew; + if (_skew) { + skew = _skew->value().value; + } + std::optional skewAxis; + if (_skewAxis) { + skewAxis = _skewAxis->value().value; + } + + return CATransform3D::makeTransform( + anchor, + position, + scale, + rotation, + skew, + skewAxis + ); + } + + virtual std::shared_ptr keypathLayer() const override { + return nullptr; + } + + double opacity() { + if (_opacity) { + return _opacity->value().value; + } else { + return 100.0; + } + } + + std::optional strokeColor() { + if (_strokeColor) { + return _strokeColor->value(); + } else { + return std::nullopt; + } + } + + std::optional fillColor() { + if (_fillColor) { + return _fillColor->value(); + } else { + return std::nullopt; + } + } + + double tracking() { + if (_tracking) { + return _tracking->value().value; + } else { + return 1.0; + } + } + + double strokeWidth() { + if (_strokeWidth) { + return _strokeWidth->value().value; + } else { + return 0.0; + } + } + +private: + std::string _keypathName; + + std::shared_ptr> _anchor; + std::shared_ptr> _position; + std::shared_ptr> _scale; + std::shared_ptr> _skew; + std::shared_ptr> _skewAxis; + std::shared_ptr> _rotation; + std::shared_ptr> _opacity; + std::shared_ptr> _strokeColor; + std::shared_ptr> _fillColor; + std::shared_ptr> _strokeWidth; + std::shared_ptr> _tracking; + + std::map> _keypathProperties; + std::vector> _childKeypaths; + std::vector> _properties; +}; + +class TextOutputNode: virtual public NodeOutput { +public: + TextOutputNode(std::shared_ptr parent) : + _parentTextNode(parent) { + } + + virtual std::shared_ptr parent() override { + return _parentTextNode; + } + + CATransform3D xform() { + if (_xform.has_value()) { + return _xform.value(); + } else if (_parentTextNode) { + return _parentTextNode->xform(); + } else { + return CATransform3D::identity(); + } + } + void setXform(CATransform3D const &xform) { + _xform = xform; + } + + double opacity() { + if (_opacity.has_value()) { + return _opacity.value(); + } else if (_parentTextNode) { + return _parentTextNode->opacity(); + } else { + return 1.0; + } + } + void setOpacity(double opacity) { + _opacity = opacity; + } + + std::optional strokeColor() { + if (_strokeColor.has_value()) { + return _strokeColor.value(); + } else if (_parentTextNode) { + return _parentTextNode->strokeColor(); + } else { + return std::nullopt; + } + } + void setStrokeColor(std::optional strokeColor) { + _strokeColor = strokeColor; + } + + std::optional fillColor() { + if (_fillColor.has_value()) { + return _fillColor.value(); + } else if (_parentTextNode) { + return _parentTextNode->fillColor(); + } else { + return std::nullopt; + } + } + void setFillColor(std::optional fillColor) { + _fillColor = fillColor; + } + + double tracking() { + if (_tracking.has_value()) { + return _tracking.value(); + } else if (_parentTextNode) { + return _parentTextNode->tracking(); + } else { + return 0.0; + } + } + void setTracking(double tracking) { + _tracking = tracking; + } + + double strokeWidth() { + if (_strokeWidth.has_value()) { + return _strokeWidth.value(); + } else if (_parentTextNode) { + return _parentTextNode->strokeWidth(); + } else { + return 0.0; + } + } + void setStrokeWidth(double strokeWidth) { + _strokeWidth = strokeWidth; + } + + virtual bool hasOutputUpdates(double frame) override { + // TODO Fix This + return true; + } + + virtual std::shared_ptr outputPath() override { + return _outputPath; + } + + virtual bool isEnabled() const override { + return _isEnabled; + } + virtual void setIsEnabled(bool isEnabled) override { + _isEnabled = isEnabled; + } + +private: + std::shared_ptr _parentTextNode; + bool _isEnabled = true; + + std::shared_ptr _outputPath; + + std::optional _xform; + std::optional _opacity; + std::optional _strokeColor; + std::optional _fillColor; + std::optional _tracking; + std::optional _strokeWidth; +}; + +class TextAnimatorNode: public AnimatorNode { +public: + TextAnimatorNode(std::shared_ptr const &parentNode, std::shared_ptr const &textAnimator) : + AnimatorNode(parentNode) { + std::shared_ptr parentOutputNode; + if (parentNode) { + parentOutputNode = parentNode->_textOutputNode; + } + _textOutputNode = std::make_shared(parentOutputNode); + + _textAnimatorProperties = std::make_shared(textAnimator); + } + + virtual std::shared_ptr outputNode() override { + return _textOutputNode; + } + + virtual std::shared_ptr propertyMap() const override { + return _textAnimatorProperties; + } + + virtual bool localUpdatesPermeateDownstream() override { + return true; + } + + virtual void rebuildOutputs(double frame) override { + _textOutputNode->setXform(_textAnimatorProperties->caTransform()); + _textOutputNode->setOpacity(((float)_textAnimatorProperties->opacity()) * 0.01f); + _textOutputNode->setStrokeColor(_textAnimatorProperties->strokeColor()); + _textOutputNode->setFillColor(_textAnimatorProperties->fillColor()); + _textOutputNode->setTracking(_textAnimatorProperties->tracking()); + _textOutputNode->setStrokeWidth(_textAnimatorProperties->strokeWidth()); + } + +private: + std::shared_ptr _textOutputNode; + + std::shared_ptr _textAnimatorProperties; +}; + +} + +#endif /* TextAnimatorNode_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift new file mode 100644 index 00000000000..cb7d4841262 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift @@ -0,0 +1,270 @@ +// +// TextAnimatorNode.swift +// lottie-ios-iOS +// +// Created by Brandon Withrow on 2/19/19. +// + +import CoreGraphics +import Foundation +import QuartzCore + +// MARK: - TextAnimatorNodeProperties + +final class TextAnimatorNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(textAnimator: TextAnimator) { + keypathName = textAnimator.name + var properties = [String : AnyNodeProperty]() + + if let keyframeGroup = textAnimator.anchor { + anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Anchor"] = anchor + } else { + anchor = nil + } + + if let keyframeGroup = textAnimator.position { + position = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Position"] = position + } else { + position = nil + } + + if let keyframeGroup = textAnimator.scale { + scale = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Scale"] = scale + } else { + scale = nil + } + + if let keyframeGroup = textAnimator.skew { + skew = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Skew"] = skew + } else { + skew = nil + } + + if let keyframeGroup = textAnimator.skewAxis { + skewAxis = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Skew Axis"] = skewAxis + } else { + skewAxis = nil + } + + if let keyframeGroup = textAnimator.rotation { + rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Rotation"] = rotation + } else { + rotation = nil + } + + if let keyframeGroup = textAnimator.opacity { + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Opacity"] = opacity + } else { + opacity = nil + } + + if let keyframeGroup = textAnimator.strokeColor { + strokeColor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Stroke Color"] = strokeColor + } else { + strokeColor = nil + } + + if let keyframeGroup = textAnimator.fillColor { + fillColor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Fill Color"] = fillColor + } else { + fillColor = nil + } + + if let keyframeGroup = textAnimator.strokeWidth { + strokeWidth = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Stroke Width"] = strokeWidth + } else { + strokeWidth = nil + } + + if let keyframeGroup = textAnimator.tracking { + tracking = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Tracking"] = tracking + } else { + tracking = nil + } + + keypathProperties = properties + + self.properties = Array(keypathProperties.values) + } + + // MARK: Internal + + let keypathName: String + + let anchor: NodeProperty? + let position: NodeProperty? + let scale: NodeProperty? + let skew: NodeProperty? + let skewAxis: NodeProperty? + let rotation: NodeProperty? + let opacity: NodeProperty? + let strokeColor: NodeProperty? + let fillColor: NodeProperty? + let strokeWidth: NodeProperty? + let tracking: NodeProperty? + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + + var caTransform: CATransform3D { + CATransform3D.makeTransform( + anchor: anchor?.value.pointValue ?? .zero, + position: position?.value.pointValue ?? .zero, + scale: scale?.value.sizeValue ?? CGSize(width: 100, height: 100), + rotation: rotation?.value.cgFloatValue ?? 0, + skew: skew?.value.cgFloatValue, + skewAxis: skewAxis?.value.cgFloatValue) + } +} + +// MARK: - TextOutputNode + +final class TextOutputNode: NodeOutput { + + // MARK: Lifecycle + + init(parent: TextOutputNode?) { + parentTextNode = parent + } + + // MARK: Internal + + var parentTextNode: TextOutputNode? + var isEnabled = true + + var outputPath: CGPath? + + var parent: NodeOutput? { + parentTextNode + } + + var xform: CATransform3D { + get { + _xform ?? parentTextNode?.xform ?? CATransform3DIdentity + } + set { + _xform = newValue + } + } + + var opacity: CGFloat { + get { + _opacity ?? parentTextNode?.opacity ?? 1 + } + set { + _opacity = newValue + } + } + + var strokeColor: CGColor? { + get { + _strokeColor ?? parentTextNode?.strokeColor + } + set { + _strokeColor = newValue + } + } + + var fillColor: CGColor? { + get { + _fillColor ?? parentTextNode?.fillColor + } + set { + _fillColor = newValue + } + } + + var tracking: CGFloat { + get { + _tracking ?? parentTextNode?.tracking ?? 0 + } + set { + _tracking = newValue + } + } + + var strokeWidth: CGFloat { + get { + _strokeWidth ?? parentTextNode?.strokeWidth ?? 0 + } + set { + _strokeWidth = newValue + } + } + + func hasOutputUpdates(_: CGFloat) -> Bool { + // TODO Fix This + true + } + + // MARK: Fileprivate + + fileprivate var _xform: CATransform3D? + fileprivate var _opacity: CGFloat? + fileprivate var _strokeColor: CGColor? + fileprivate var _fillColor: CGColor? + fileprivate var _tracking: CGFloat? + fileprivate var _strokeWidth: CGFloat? +} + +// MARK: - TextAnimatorNode + +class TextAnimatorNode: AnimatorNode { + + // MARK: Lifecycle + + init(parentNode: TextAnimatorNode?, textAnimator: TextAnimator) { + textOutputNode = TextOutputNode(parent: parentNode?.textOutputNode) + textAnimatorProperties = TextAnimatorNodeProperties(textAnimator: textAnimator) + self.parentNode = parentNode + } + + // MARK: Internal + + let textOutputNode: TextOutputNode + + let textAnimatorProperties: TextAnimatorNodeProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled = true + + var outputNode: NodeOutput { + textOutputNode + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + textAnimatorProperties + } + + func localUpdatesPermeateDownstream() -> Bool { + true + } + + func rebuildOutputs(frame _: CGFloat) { + textOutputNode.xform = textAnimatorProperties.caTransform + textOutputNode.opacity = (textAnimatorProperties.opacity?.value.cgFloatValue ?? 100) * 0.01 + textOutputNode.strokeColor = textAnimatorProperties.strokeColor?.value.cgColorValue + textOutputNode.fillColor = textAnimatorProperties.fillColor?.value.cgColorValue + textOutputNode.tracking = textAnimatorProperties.tracking?.value.cgFloatValue ?? 1 + textOutputNode.strokeWidth = textAnimatorProperties.strokeWidth?.value.cgFloatValue ?? 0 + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp new file mode 100644 index 00000000000..1d62e6c8d2c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp @@ -0,0 +1,235 @@ +#ifndef AnimatorNode_hpp +#define AnimatorNode_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" + +#include +#include + +namespace lottie { + +class LayerTransformNode; +class PathNode; +class RenderNode; + +/// The Animator Node is the base node in the render system tree. +/// +/// It defines a single node that has an output path and option input node. +/// At animation time the root animation node is asked to update its contents for +/// the current frame. +/// The node reaches up its chain of nodes until the first node that does not need +/// updating is found. Then each node updates its contents down the render pipeline. +/// Each node adds its local path to its input path and passes it forward. +/// +/// An animator node holds a group of interpolators. These interpolators determine +/// if the node needs an update for the current frame. +/// +class AnimatorNode: public KeypathSearchable { +public: + AnimatorNode(std::shared_ptr const &parentNode) : + _parentNode(parentNode) { + } + + AnimatorNode(const AnimatorNode&) = delete; + AnimatorNode& operator=(AnimatorNode&) = delete; + + /// The available properties of the Node. + /// + /// These properties are automatically updated each frame. + /// These properties are also settable and gettable through the dynamic + /// property system. + /// + virtual std::shared_ptr propertyMap() const = 0; + + /// The upstream input node + std::shared_ptr parentNode() { + return _parentNode; + } + void setParentNode(std::shared_ptr const &parentNode) { + _parentNode = parentNode; + } + + /// The output of the node. + virtual std::shared_ptr outputNode() = 0; + + /// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true. + virtual void rebuildOutputs(double frame) = 0; + + /// Setters for marking current node state. + bool isEnabled() { + return _isEnabled; + } + virtual void setIsEnabled(bool isEnabled) { + _isEnabled = isEnabled; + } + + bool hasLocalUpdates() { + return _hasLocalUpdates; + } + virtual void setHasLocalUpdates(bool hasLocalUpdates) { + _hasLocalUpdates = hasLocalUpdates; + } + + bool hasUpstreamUpdates() { + return _hasUpstreamUpdates; + } + virtual void setHasUpstreamUpdates(bool hasUpstreamUpdates) { + _hasUpstreamUpdates = hasUpstreamUpdates; + } + + std::optional lastUpdateFrame() { + return _lastUpdateFrame; + } + virtual void setLastUpdateFrame(std::optional lastUpdateFrame) { + _lastUpdateFrame = lastUpdateFrame; + } + + /// Marks if updates to this node affect nodes downstream. + virtual bool localUpdatesPermeateDownstream() { + /// Optional override + return true; + } + virtual bool forceUpstreamOutputUpdates() { + /// Optional + return false; + } + + /// Called at the end of this nodes update cycle. Always called. Optional. + virtual bool performAdditionalLocalUpdates(double frame, bool forceLocalUpdate) { + /// Optional + return forceLocalUpdate; + } + virtual void performAdditionalOutputUpdates(double frame, bool forceOutputUpdate) { + /// Optional + } + + /// The default simply returns `hasLocalUpdates` + virtual bool shouldRebuildOutputs(double frame) { + return hasLocalUpdates(); + } + + virtual bool updateOutputs(double frame, bool forceOutputUpdate) { + if (!isEnabled()) { + setLastUpdateFrame(frame); + if (const auto parentNodeValue = parentNode()) { + return parentNodeValue->updateOutputs(frame, forceOutputUpdate); + } else { + return false; + } + } + + if (!forceOutputUpdate && lastUpdateFrame().has_value() && lastUpdateFrame().value() == frame) { + /// This node has already updated for this frame. Go ahead and return the results. + return hasUpstreamUpdates() || hasLocalUpdates(); + } + + /// Ask if this node should force output updates upstream. + bool forceUpstreamUpdates = forceOutputUpdate || forceUpstreamOutputUpdates(); + + /// Perform upstream output updates. Optionally mark upstream updates if any. + if (const auto parentNodeValue = parentNode()) { + setHasUpstreamUpdates(parentNodeValue->updateOutputs(frame, forceUpstreamUpdates) || hasUpstreamUpdates()); + } else { + setHasUpstreamUpdates(hasUpstreamUpdates()); + } + + /// Perform additional local output updates + performAdditionalOutputUpdates(frame, forceUpstreamUpdates); + + /// If there are local updates, or if updates have been force, rebuild outputs + if (forceUpstreamUpdates || shouldRebuildOutputs(frame)) { + setLastUpdateFrame(frame); + rebuildOutputs(frame); + } + return hasUpstreamUpdates() || hasLocalUpdates(); + } + + /// Rebuilds the content of this node, and upstream nodes if necessary. + virtual bool updateContents(double frame, bool forceLocalUpdate) { + if (!isEnabled()) { + // Disabled node, pass through. + if (const auto parentNodeValue = parentNode()) { + return parentNodeValue->updateContents(frame, forceLocalUpdate); + } else { + return false; + } + } + + if (forceLocalUpdate == false && lastUpdateFrame().has_value() && lastUpdateFrame().value() == frame) { + /// This node has already updated for this frame. Go ahead and return the results. + return localUpdatesPermeateDownstream() ? hasUpstreamUpdates() || hasLocalUpdates() : hasUpstreamUpdates(); + } + + /// Are there local updates? If so mark the node. + setHasLocalUpdates(forceLocalUpdate ? forceLocalUpdate : propertyMap()->needsLocalUpdate(frame)); + + /// Were there upstream updates? If so mark the node + if (const auto parentNodeValue = parentNode()) { + setHasUpstreamUpdates(parentNodeValue->updateContents(frame, forceLocalUpdate)); + } else { + setHasUpstreamUpdates(false); + } + + /// Perform property updates if necessary. + if (hasLocalUpdates()) { + /// Rebuild local properties + propertyMap()->updateNodeProperties(frame); + } + + /// Ask the node to perform any other updates it might have. + setHasUpstreamUpdates(performAdditionalLocalUpdates(frame, forceLocalUpdate) || hasUpstreamUpdates()); + + /// If the node can update nodes downstream, notify them, otherwise pass on any upstream updates downstream. + return localUpdatesPermeateDownstream() ? hasUpstreamUpdates() || hasLocalUpdates() : hasUpstreamUpdates(); + } + + virtual void updateTree(double frame, bool forceUpdates) { + updateContents(frame, forceUpdates); + updateOutputs(frame, forceUpdates); + } + + /// The name of the Keypath + virtual std::string keypathName() const override { + return propertyMap()->keypathName(); + } + + /// A list of properties belonging to the keypath. + virtual std::map> keypathProperties() const override { + return propertyMap()->keypathProperties(); + } + + /// Children Keypaths + virtual std::vector> const &childKeypaths() const override { + return propertyMap()->childKeypaths(); + } + + virtual std::shared_ptr keypathLayer() const override { + return nullptr; + } + +public: + virtual LayerTransformNode *asLayerTransformNode() { + return nullptr; + } + + virtual PathNode *asPathNode() { + return nullptr; + } + + virtual RenderNode *asRenderNode() { + return nullptr; + } + +private: + std::shared_ptr _parentNode; + bool _isEnabled = true; + bool _hasLocalUpdates = false; + bool _hasUpstreamUpdates = false; + std::optional _lastUpdateFrame; +}; + +} + +#endif /* AnimatorNode_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.swift new file mode 100644 index 00000000000..1327618f614 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.swift @@ -0,0 +1,198 @@ +// +// AnimatorNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/15/19. +// + +import Foundation +import QuartzCore + +// MARK: - NodeOutput + +/// Defines the basic outputs of an animator node. +/// +protocol NodeOutput { + + /// The parent node. + var parent: NodeOutput? { get } + + /// Returns true if there are any updates upstream. OutputPath must be built before returning. + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool + + var outputPath: CGPath? { get } + + var isEnabled: Bool { get set } +} + +// MARK: - AnimatorNode + +/// The Animator Node is the base node in the render system tree. +/// +/// It defines a single node that has an output path and option input node. +/// At animation time the root animation node is asked to update its contents for +/// the current frame. +/// The node reaches up its chain of nodes until the first node that does not need +/// updating is found. Then each node updates its contents down the render pipeline. +/// Each node adds its local path to its input path and passes it forward. +/// +/// An animator node holds a group of interpolators. These interpolators determine +/// if the node needs an update for the current frame. +/// +protocol AnimatorNode: AnyObject, KeypathSearchable { + + /// The available properties of the Node. + /// + /// These properties are automatically updated each frame. + /// These properties are also settable and gettable through the dynamic + /// property system. + /// + var propertyMap: NodePropertyMap & KeypathSearchable { get } + + /// The upstream input node + var parentNode: AnimatorNode? { get } + + /// The output of the node. + var outputNode: NodeOutput { get } + + /// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true. + func rebuildOutputs(frame: CGFloat) + + /// Setters for marking current node state. + var isEnabled: Bool { get set } + var hasLocalUpdates: Bool { get set } + var hasUpstreamUpdates: Bool { get set } + var lastUpdateFrame: CGFloat? { get set } + + // MARK: Optional + + /// Marks if updates to this node affect nodes downstream. + func localUpdatesPermeateDownstream() -> Bool + func forceUpstreamOutputUpdates() -> Bool + + /// Called at the end of this nodes update cycle. Always called. Optional. + func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool + func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) + + /// The default simply returns `hasLocalUpdates` + func shouldRebuildOutputs(frame: CGFloat) -> Bool +} + +/// Basic Node Logic +extension AnimatorNode { + + func shouldRebuildOutputs(frame _: CGFloat) -> Bool { + hasLocalUpdates + } + + func localUpdatesPermeateDownstream() -> Bool { + /// Optional override + true + } + + func forceUpstreamOutputUpdates() -> Bool { + /// Optional + false + } + + func performAdditionalLocalUpdates(frame _: CGFloat, forceLocalUpdate: Bool) -> Bool { + /// Optional + forceLocalUpdate + } + + func performAdditionalOutputUpdates(_: CGFloat, forceOutputUpdate _: Bool) { + /// Optional + } + + @discardableResult + func updateOutputs(_ frame: CGFloat, forceOutputUpdate: Bool) -> Bool { + guard isEnabled else { + // Disabled node, pass through. + lastUpdateFrame = frame + return parentNode?.updateOutputs(frame, forceOutputUpdate: forceOutputUpdate) ?? false + } + + if forceOutputUpdate == false && lastUpdateFrame != nil && lastUpdateFrame! == frame { + /// This node has already updated for this frame. Go ahead and return the results. + return hasUpstreamUpdates || hasLocalUpdates + } + + /// Ask if this node should force output updates upstream. + let forceUpstreamUpdates = forceOutputUpdate || forceUpstreamOutputUpdates() + + /// Perform upstream output updates. Optionally mark upstream updates if any. + hasUpstreamUpdates = ( + parentNode? + .updateOutputs(frame, forceOutputUpdate: forceUpstreamUpdates) ?? false || hasUpstreamUpdates) + + /// Perform additional local output updates + performAdditionalOutputUpdates(frame, forceOutputUpdate: forceUpstreamUpdates) + + /// If there are local updates, or if updates have been force, rebuild outputs + if forceUpstreamUpdates || shouldRebuildOutputs(frame: frame) { + lastUpdateFrame = frame + rebuildOutputs(frame: frame) + } + return hasUpstreamUpdates || hasLocalUpdates + } + + /// Rebuilds the content of this node, and upstream nodes if necessary. + @discardableResult + func updateContents(_ frame: CGFloat, forceLocalUpdate: Bool) -> Bool { + guard isEnabled else { + // Disabled node, pass through. + return parentNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false + } + + if forceLocalUpdate == false && lastUpdateFrame != nil && lastUpdateFrame! == frame { + /// This node has already updated for this frame. Go ahead and return the results. + return localUpdatesPermeateDownstream() ? hasUpstreamUpdates || hasLocalUpdates : hasUpstreamUpdates + } + + /// Are there local updates? If so mark the node. + hasLocalUpdates = forceLocalUpdate ? forceLocalUpdate : propertyMap.needsLocalUpdate(frame: frame) + + /// Were there upstream updates? If so mark the node + hasUpstreamUpdates = parentNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false + + /// Perform property updates if necessary. + if hasLocalUpdates { + /// Rebuild local properties + propertyMap.updateNodeProperties(frame: frame) + } + + /// Ask the node to perform any other updates it might have. + hasUpstreamUpdates = performAdditionalLocalUpdates(frame: frame, forceLocalUpdate: forceLocalUpdate) || hasUpstreamUpdates + + /// If the node can update nodes downstream, notify them, otherwise pass on any upstream updates downstream. + return localUpdatesPermeateDownstream() ? hasUpstreamUpdates || hasLocalUpdates : hasUpstreamUpdates + } + + func updateTree(_ frame: CGFloat, forceUpdates: Bool = false) { + updateContents(frame, forceLocalUpdate: forceUpdates) + updateOutputs(frame, forceOutputUpdate: forceUpdates) + } + +} + +extension AnimatorNode { + /// Default implementation for Keypath searchable. + /// Forward all calls to the propertyMap. + + var keypathName: String { + propertyMap.keypathName + } + + var keypathProperties: [String: AnyNodeProperty] { + propertyMap.keypathProperties + } + + var childKeypaths: [KeypathSearchable] { + propertyMap.childKeypaths + } + + var keypathLayer: CALayer? { + nil + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp new file mode 100644 index 00000000000..970a51ae24e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp @@ -0,0 +1,28 @@ +#ifndef NodeOutput_hpp +#define NodeOutput_hpp + +#include "Lottie/Public/Primitives/CGPath.hpp" + +#include + +namespace lottie { + +/// Defines the basic outputs of an animator node. +/// +class NodeOutput { +public: + /// The parent node. + virtual std::shared_ptr parent() = 0; + + /// Returns true if there are any updates upstream. OutputPath must be built before returning. + virtual bool hasOutputUpdates(double forFrame) = 0; + + virtual std::shared_ptr outputPath() = 0; + + virtual bool isEnabled() const = 0; + virtual void setIsEnabled(bool isEnabled) = 0; +}; + +} + +#endif /* NodeOutput_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/PathNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/PathNode.swift new file mode 100644 index 00000000000..7eaa6cf45bf --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/PathNode.swift @@ -0,0 +1,22 @@ +// +// PathNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import Foundation + +// MARK: - PathNode + +protocol PathNode { + var pathOutput: PathOutputNode { get } +} + +extension PathNode where Self: AnimatorNode { + + var outputNode: NodeOutput { + pathOutput + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp new file mode 100644 index 00000000000..5db444d408c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp @@ -0,0 +1,73 @@ +#ifndef RenderNode_hpp +#define RenderNode_hpp + +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp" + +namespace lottie { + +class StrokeRenderer; +class FillRenderer; +class GradientStrokeRenderer; +class GradientFillRenderer; + +/// A protocol that defines anything with render instructions +class Renderable: virtual public HasRenderUpdates, virtual public HasUpdate { +public: + enum RenderableType { + Fill, + Stroke, + GradientFill, + GradientStroke + }; + +public: + /// Determines if the renderer requires a custom context for drawing. + /// If yes the shape layer will perform a custom drawing pass. + /// If no the shape layer will be a standard CAShapeLayer + virtual bool shouldRenderInContext() = 0; + + /// Passes in the CAShapeLayer to update + virtual void updateShapeLayer(std::shared_ptr const &layer) = 0; + + /// Asks the renderer what the renderable bounds is for the given box. + virtual CGRect renderBoundsFor(CGRect const &boundingBox) { + /// Optional + return boundingBox; + } + + /// Opportunity for renderers to inject sublayers + virtual void setupSublayers(std::shared_ptr const &layer) = 0; + + virtual RenderableType renderableType() const = 0; + + virtual StrokeRenderer *asStrokeRenderer() { + return nullptr; + } + + virtual FillRenderer *asFillRenderer() { + return nullptr; + } + + virtual GradientStrokeRenderer *asGradientStrokeRenderer() { + return nullptr; + } + + virtual GradientFillRenderer *asGradientFillRenderer() { + return nullptr; + } +}; + +/// A protocol that defines a node that holds render instructions +class RenderNode { +public: + virtual std::shared_ptr renderer() = 0; + virtual std::shared_ptr nodeOutput() = 0; +}; + +} + +#endif /* RenderNode_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.swift new file mode 100644 index 00000000000..9b4cfbf2253 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.swift @@ -0,0 +1,62 @@ +// +// RenderNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import CoreGraphics +import Foundation +import QuartzCore + +// MARK: - RenderNode + +/// A protocol that defines a node that holds render instructions +protocol RenderNode { + var renderer: Renderable & NodeOutput { get } +} + +// MARK: - Renderable + +/// A protocol that defines anything with render instructions +protocol Renderable { + + /// The last frame in which this node was updated. + var hasUpdate: Bool { get } + + func hasRenderUpdates(_ forFrame: CGFloat) -> Bool + + /// Determines if the renderer requires a custom context for drawing. + /// If yes the shape layer will perform a custom drawing pass. + /// If no the shape layer will be a standard CAShapeLayer + var shouldRenderInContext: Bool { get } + + /// Passes in the CAShapeLayer to update + func updateShapeLayer(layer: CAShapeLayer) + + /// Asks the renderer what the renderable bounds is for the given box. + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect + + /// Opportunity for renderers to inject sublayers + func setupSublayers(layer: CAShapeLayer) + + /// Renders the shape in a custom context + func render(_ inContext: CGContext) +} + +extension RenderNode where Self: AnimatorNode { + + var outputNode: NodeOutput { + renderer + } + +} + +extension Renderable { + + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { + /// Optional + boundingBox + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp new file mode 100644 index 00000000000..a1b830cf134 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp @@ -0,0 +1,99 @@ +#include "GetGradientParameters.hpp" + +namespace lottie { + +void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector &outColors, std::vector &outLocations) { + std::vector alphaColors; + std::vector alphaValues; + std::vector alphaLocations; + + std::vector gradientColors; + std::vector colorLocations; + + for (int i = 0; i < numberOfColors; i++) { + int ix = i * 4; + if (colors.colors.size() > ix) { + Color color( + colors.colors[ix + 1], + colors.colors[ix + 2], + colors.colors[ix + 3], + 1 + ); + gradientColors.push_back(color); + colorLocations.push_back(colors.colors[ix]); + } + } + + bool drawMask = false; + for (int i = numberOfColors * 4; i < (int)colors.colors.size(); i += 2) { + double alpha = colors.colors[i + 1]; + if (alpha < 1.0) { + drawMask = true; + } + alphaLocations.push_back(colors.colors[i]); + alphaColors.push_back(Color(alpha, alpha, alpha, 1.0)); + alphaValues.push_back(alpha); + } + + if (drawMask) { + std::vector locations; + for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()); i++) { + if (std::find(locations.begin(), locations.end(), colorLocations[i]) == locations.end()) { + locations.push_back(colorLocations[i]); + } + } + for (size_t i = 0; i < std::min(alphaValues.size(), alphaLocations.size()); i++) { + if (std::find(locations.begin(), locations.end(), alphaLocations[i]) == locations.end()) { + locations.push_back(alphaLocations[i]); + } + } + + std::sort(locations.begin(), locations.end()); + if (locations[0] != 0.0) { + locations.insert(locations.begin(), 0.0); + } + if (locations[locations.size() - 1] != 1.0) { + locations.push_back(1.0); + } + + std::vector colors; + + for (const auto location : locations) { + Color color = gradientColors[0]; + for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()) - 1; i++) { + if (location >= colorLocations[i] && location <= colorLocations[i + 1]) { + double localLocation = 0.0; + if (colorLocations[i] != colorLocations[i + 1]) { + localLocation = remapDouble(location, colorLocations[i], colorLocations[i + 1], 0.0, 1.0); + } + color = ValueInterpolator::interpolate(gradientColors[i], gradientColors[i + 1], localLocation, std::nullopt, std::nullopt); + break; + } + } + + double alpha = 1.0; + for (size_t i = 0; i < std::min(alphaValues.size(), alphaLocations.size()) - 1; i++) { + if (location >= alphaLocations[i] && location <= alphaLocations[i + 1]) { + double localLocation = 0.0; + if (alphaLocations[i] != alphaLocations[i + 1]) { + localLocation = remapDouble(location, alphaLocations[i], alphaLocations[i + 1], 0.0, 1.0); + } + alpha = ValueInterpolator::interpolate(alphaValues[i], alphaValues[i + 1], localLocation, std::nullopt, std::nullopt); + break; + } + } + + color.a = alpha; + + colors.push_back(color); + } + + gradientColors = colors; + colorLocations = locations; + } + + outColors = gradientColors; + outLocations = colorLocations; +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp new file mode 100644 index 00000000000..f14c5786440 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp @@ -0,0 +1,13 @@ +#ifndef ShapeRenderLayer_hpp +#define ShapeRenderLayer_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" + +namespace lottie { + +void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector &outColors, std::vector &outLocations); + +} + +#endif /* ShapeRenderLayer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift new file mode 100644 index 00000000000..df1f07680c9 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift @@ -0,0 +1,74 @@ +// +// ShapeContainerLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +/// The base layer that holds Shapes and Shape Renderers +class ShapeContainerLayer: CALayer { + + // MARK: Lifecycle + + override init() { + super.init() + actions = [ + "position" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "transform" : NSNull(), + "opacity" : NSNull(), + "hidden" : NSNull(), + ] + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + guard let layer = layer as? ShapeContainerLayer else { + fatalError("init(layer:) wrong class.") + } + super.init(layer: layer) + } + + // MARK: Internal + + private(set) var renderLayers: [ShapeContainerLayer] = [] + + var renderScale: CGFloat = 1 { + didSet { + updateRenderScale() + } + } + + func insertRenderLayer(_ layer: ShapeContainerLayer) { + renderLayers.append(layer) + insertSublayer(layer, at: 0) + } + + func markRenderUpdates(forFrame: CGFloat) { + if hasRenderUpdate(forFrame: forFrame) { + rebuildContents(forFrame: forFrame) + } + guard isHidden == false else { return } + renderLayers.forEach { $0.markRenderUpdates(forFrame: forFrame) } + } + + func hasRenderUpdate(forFrame _: CGFloat) -> Bool { + false + } + + func rebuildContents(forFrame _: CGFloat) { + /// Override + } + + func updateRenderScale() { + contentsScale = renderScale + renderLayers.forEach( { $0.renderScale = renderScale } ) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift new file mode 100644 index 00000000000..611603de8ad --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift @@ -0,0 +1,100 @@ +// +// RenderLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/18/19. +// + +import Foundation +import QuartzCore + +/// The layer responsible for rendering shape objects +final class ShapeRenderLayer: ShapeContainerLayer, LottieDrawingLayer { + + // MARK: Lifecycle + + init(renderer: Renderable & NodeOutput) { + self.renderer = renderer + super.init() + anchorPoint = .zero + actions = [ + "position" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "path" : NSNull(), + "transform" : NSNull(), + "opacity" : NSNull(), + "hidden" : NSNull(), + ] + shapeLayer.actions = [ + "position" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "path" : NSNull(), + "fillColor" : NSNull(), + "strokeColor" : NSNull(), + "lineWidth" : NSNull(), + "miterLimit" : NSNull(), + "lineDashPhase" : NSNull(), + "opacity": NSNull(), + "hidden" : NSNull(), + ] + addSublayer(shapeLayer) + + renderer.setupSublayers(layer: shapeLayer) + } + + override init(layer: Any) { + guard let layer = layer as? ShapeRenderLayer else { + fatalError("init(layer:) wrong class.") + } + renderer = layer.renderer + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + fileprivate(set) var renderer: Renderable & NodeOutput + + let shapeLayer = LottieCAShapeLayer() + + override func hasRenderUpdate(forFrame: CGFloat) -> Bool { + isHidden = !renderer.isEnabled + guard isHidden == false else { return false } + return renderer.hasRenderUpdates(forFrame) + } + + override func rebuildContents(forFrame _: CGFloat) { + + if renderer.shouldRenderInContext { + if let newPath = renderer.outputPath { + bounds = renderer.renderBoundsFor(lottieSwift_getPathNativeBoundingBox!(newPath)) + } else { + bounds = .zero + } + position = bounds.origin + setNeedsDisplay() + } else { + shapeLayer.path = renderer.outputPath + renderer.updateShapeLayer(layer: shapeLayer) + } + } + + override func draw(in ctx: CGContext) { + if let path = renderer.outputPath { + if !path.isEmpty { + ctx.addPath(path) + } + } + renderer.render(ctx) + } + + override func updateRenderScale() { + super.updateRenderScale() + shapeLayer.contentsScale = renderScale + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Animation.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Animation.cpp new file mode 100644 index 00000000000..577a56be354 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Animation.cpp @@ -0,0 +1,5 @@ +#include "Animation.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Animation.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Animation.hpp new file mode 100644 index 00000000000..f6ed1ab57ec --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Animation.hpp @@ -0,0 +1,314 @@ +#ifndef Animation_hpp +#define Animation_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Private/Utility/Primitives/CoordinateSpace.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Text/Glyph.hpp" +#include "Lottie/Private/Model/Text/Font.hpp" +#include "Lottie/Private/Model/Objects/Marker.hpp" +#include "Lottie/Private/Model/Assets/AssetLibrary.hpp" +#include "Lottie/Private/Model/Objects/FitzModifier.hpp" + +#include "json11/json11.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Private/Model/Layers/LayerModelSerialization.hpp" + +#include +#include +#include +#include + +namespace lottie { + +/// The `Animation` model is the top level model object in Lottie. +/// +/// An `Animation` holds all of the animation data backing a Lottie Animation. +/// Codable, see JSON schema [here](https://github.com/airbnb/lottie-web/tree/master/docs/json). +class Animation { +public: + Animation( + std::optional name_, + std::optional tgs_, + AnimationFrameTime startFrame_, + AnimationFrameTime endFrame_, + double framerate_, + std::string const &version_, + std::optional type_, + int width_, + int height_, + std::vector> const &layers_, + std::optional>> glyphs_, + std::optional> fonts_, + std::shared_ptr assetLibrary_, + std::optional> markers_, + std::optional> fitzModifiers_, + std::optional meta_, + std::optional comps_ + ) : + name(name_), + tgs(tgs_), + startFrame(startFrame_), + endFrame(endFrame_), + framerate(framerate_), + version(version_), + type(type_), + width(width_), + height(height_), + layers(layers_), + glyphs(glyphs_), + fonts(fonts_), + assetLibrary(assetLibrary_), + markers(markers_), + fitzModifiers(fitzModifiers_), + meta(meta_), + comps(comps_) { + if (markers) { + std::map parsedMarkerMap; + for (const auto &marker : markers.value()) { + parsedMarkerMap.insert(std::make_pair(marker.name, marker)); + } + markerMap = std::move(parsedMarkerMap); + } + } + + Animation(const Animation&) = delete; + Animation& operator=(Animation&) = delete; + + static std::shared_ptr fromJson(json11::Json::object const &json) noexcept(false) { + auto name = getOptionalString(json, "nm"); + auto version = getString(json, "v"); + + auto tgs = getOptionalInt(json, "tgs"); + + std::optional type; + if (const auto typeRawValue = getOptionalInt(json, "ddd")) { + if (typeRawValue.value() == 0) { + type = CoordinateSpace::Type2d; + } else { + type = CoordinateSpace::Type3d; + } + } + + AnimationFrameTime startFrame = getDouble(json, "ip"); + AnimationFrameTime endFrame = getDouble(json, "op"); + + double framerate = getDouble(json, "fr"); + + int width = getInt(json, "w"); + int height = getInt(json, "h"); + + auto layerDictionaries = getObjectArray(json, "layers"); + std::vector> layers; + for (size_t i = 0; i < layerDictionaries.size(); i++) { + try { + auto layer = parseLayerModel(layerDictionaries[i]); + layers.push_back(layer); + } catch(...) { + throw LottieParsingException(); + } + } + + std::optional>> glyphs; + if (const auto glyphDictionaries = getOptionalObjectArray(json, "chars")) { + glyphs = std::vector>(); + for (const auto &glyphDictionary : glyphDictionaries.value()) { + glyphs->push_back(std::make_shared(glyphDictionary)); + } + } else { + glyphs = std::nullopt; + } + + std::optional> fonts; + if (const auto fontsDictionary = getOptionalObject(json, "fonts")) { + fonts = std::make_shared(fontsDictionary.value()); + } + + std::shared_ptr assetLibrary; + if (const auto assetLibraryData = getOptionalAny(json, "assets")) { + assetLibrary = std::make_shared(assetLibraryData.value()); + } + + std::optional> markers; + if (const auto markerDictionaries = getOptionalObjectArray(json, "markers")) { + markers = std::vector(); + for (const auto &markerDictionary : markerDictionaries.value()) { + markers->push_back(Marker(markerDictionary)); + } + } + std::optional> fitzModifiers; + if (const auto fitzModifierDictionaries = getOptionalObjectArray(json, "fitz")) { + fitzModifiers = std::vector(); + for (const auto &fitzModifierDictionary : fitzModifierDictionaries.value()) { + fitzModifiers->push_back(FitzModifier(fitzModifierDictionary)); + } + } + + auto meta = getOptionalAny(json, "meta"); + auto comps = getOptionalAny(json, "comps"); + + return std::make_shared( + name, + tgs, + startFrame, + endFrame, + framerate, + version, + type, + width, + height, + std::move(layers), + std::move(glyphs), + std::move(fonts), + assetLibrary, + std::move(markers), + fitzModifiers, + meta, + comps + ); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + + result.insert(std::make_pair("v", json11::Json(version))); + + if (tgs.has_value()) { + result.insert(std::make_pair("tgs", tgs.value())); + } + + if (type.has_value()) { + switch (type.value()) { + case CoordinateSpace::Type2d: + result.insert(std::make_pair("ddd", json11::Json(0))); + break; + case CoordinateSpace::Type3d: + result.insert(std::make_pair("ddd", json11::Json(1))); + break; + } + } + + result.insert(std::make_pair("ip", json11::Json(startFrame))); + result.insert(std::make_pair("op", json11::Json(endFrame))); + result.insert(std::make_pair("fr", json11::Json(framerate))); + result.insert(std::make_pair("w", json11::Json(width))); + result.insert(std::make_pair("h", json11::Json(height))); + + json11::Json::array layersArray; + for (const auto &layer : layers) { + json11::Json::object layerJson; + layer->toJson(layerJson); + layersArray.push_back(layerJson); + } + result.insert(std::make_pair("layers", json11::Json(layersArray))); + + if (glyphs.has_value()) { + json11::Json::array glyphArray; + for (const auto &glyph : glyphs.value()) { + glyphArray.push_back(glyph->toJson()); + } + result.insert(std::make_pair("chars", json11::Json(glyphArray))); + } + + if (fonts.has_value()) { + result.insert(std::make_pair("fonts", fonts.value()->toJson())); + } + + if (assetLibrary) { + result.insert(std::make_pair("assets", assetLibrary->toJson())); + } + + if (markers.has_value()) { + json11::Json::array markerArray; + for (const auto &marker : markers.value()) { + markerArray.push_back(marker.toJson()); + } + result.insert(std::make_pair("markers", json11::Json(markerArray))); + } + + if (fitzModifiers.has_value()) { + json11::Json::array fitzModifierArray; + for (const auto &fitzModifier : fitzModifiers.value()) { + fitzModifierArray.push_back(fitzModifier.toJson()); + } + result.insert(std::make_pair("fitz", json11::Json(fitzModifierArray))); + } + + if (meta.has_value()) { + result.insert(std::make_pair("meta", meta.value())); + } + if (comps.has_value()) { + result.insert(std::make_pair("comps", comps.value())); + } + + return result; + } + +public: + /// The start time of the composition in frameTime. + AnimationFrameTime startFrame; + + /// The end time of the composition in frameTime. + AnimationFrameTime endFrame; + + /// The frame rate of the composition. + double framerate; + + /// Return all marker names, in order, or an empty list if none are specified + std::vector markerNames() { + if (!markers.has_value()) { + return {}; + } + std::vector result; + for (const auto &marker : markers.value()) { + result.push_back(marker.name); + } + return result; + } + + /// Animation name + std::optional name; + + /// The version of the JSON Schema. + std::string version; + + std::optional tgs; + + /// The coordinate space of the composition. + std::optional type; + + /// The height of the composition in points. + int width; + + /// The width of the composition in points. + int height; + + /// The list of animation layers + std::vector> layers; + + /// The list of glyphs used for text rendering + std::optional>> glyphs; + + /// The list of fonts used for text rendering + std::optional> fonts; + + /// Asset Library + std::shared_ptr assetLibrary; + + /// Markers + std::optional> markers; + std::optional> markerMap; + + std::optional> fitzModifiers; + + std::optional meta; + std::optional comps; +}; + +} + +#endif /* Animation_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Animation.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Animation.swift new file mode 100644 index 00000000000..d580337a233 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Animation.swift @@ -0,0 +1,160 @@ +// +// Animation.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import Foundation + +// MARK: - CoordinateSpace + +public enum CoordinateSpace: Int, Codable { + case type2d + case type3d +} + +// MARK: - Animation + +/// The `Animation` model is the top level model object in Lottie. +/// +/// An `Animation` holds all of the animation data backing a Lottie Animation. +/// Codable, see JSON schema [here](https://github.com/airbnb/lottie-web/tree/master/docs/json). +public final class Animation: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Animation.CodingKeys.self) + version = try container.decode(String.self, forKey: .version) + type = try container.decodeIfPresent(CoordinateSpace.self, forKey: .type) ?? .type2d + startFrame = try container.decode(AnimationFrameTime.self, forKey: .startFrame) + endFrame = try container.decode(AnimationFrameTime.self, forKey: .endFrame) + framerate = try container.decode(Double.self, forKey: .framerate) + width = try container.decode(Int.self, forKey: .width) + height = try container.decode(Int.self, forKey: .height) + layers = try container.decode([LayerModel].self, ofFamily: LayerType.self, forKey: .layers) + glyphs = try container.decodeIfPresent([Glyph].self, forKey: .glyphs) + fonts = try container.decodeIfPresent(FontList.self, forKey: .fonts) + assetLibrary = try container.decodeIfPresent(AssetLibrary.self, forKey: .assetLibrary) + markers = try container.decodeIfPresent([Marker].self, forKey: .markers) + + if let markers = markers { + var markerMap: [String: Marker] = [:] + for marker in markers { + markerMap[marker.name] = marker + } + self.markerMap = markerMap + } else { + markerMap = nil + } + } + + public init(dictionary: [String: Any]) throws { + version = try dictionary.value(for: CodingKeys.version) + if + let typeRawValue = dictionary[CodingKeys.type.rawValue] as? Int, + let type = CoordinateSpace(rawValue: typeRawValue) + { + self.type = type + } else { + type = .type2d + } + startFrame = try dictionary.value(for: CodingKeys.startFrame) + endFrame = try dictionary.value(for: CodingKeys.endFrame) + framerate = try dictionary.value(for: CodingKeys.framerate) + width = try dictionary.value(for: CodingKeys.width) + height = try dictionary.value(for: CodingKeys.height) + let layerDictionaries: [[String: Any]] = try dictionary.value(for: CodingKeys.layers) + layers = try [LayerModel].fromDictionaries(layerDictionaries) + if let glyphDictionaries = dictionary[CodingKeys.glyphs.rawValue] as? [[String: Any]] { + glyphs = try glyphDictionaries.map({ try Glyph(dictionary: $0) }) + } else { + glyphs = nil + } + if let fontsDictionary = dictionary[CodingKeys.fonts.rawValue] as? [String: Any] { + fonts = try FontList(dictionary: fontsDictionary) + } else { + fonts = nil + } + if let assetLibraryDictionaries = dictionary[CodingKeys.assetLibrary.rawValue] as? [[String: Any]] { + assetLibrary = try AssetLibrary(value: assetLibraryDictionaries) + } else { + assetLibrary = nil + } + if let markerDictionaries = dictionary[CodingKeys.markers.rawValue] as? [[String: Any]] { + let markers = try markerDictionaries.map({ try Marker(dictionary: $0) }) + var markerMap: [String: Marker] = [:] + for marker in markers { + markerMap[marker.name] = marker + } + self.markers = markers + self.markerMap = markerMap + } else { + markers = nil + markerMap = nil + } + } + + // MARK: Public + + /// The start time of the composition in frameTime. + public let startFrame: AnimationFrameTime + + /// The end time of the composition in frameTime. + public let endFrame: AnimationFrameTime + + /// The frame rate of the composition. + public let framerate: Double + + /// Return all marker names, in order, or an empty list if none are specified + public var markerNames: [String] { + guard let markers = markers else { return [] } + return markers.map { $0.name } + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case version = "v" + case type = "ddd" + case startFrame = "ip" + case endFrame = "op" + case framerate = "fr" + case width = "w" + case height = "h" + case layers + case glyphs = "chars" + case fonts + case assetLibrary = "assets" + case markers + } + + /// The version of the JSON Schema. + let version: String + + /// The coordinate space of the composition. + let type: CoordinateSpace + + /// The height of the composition in points. + public let width: Int + + /// The width of the composition in points. + public let height: Int + + /// The list of animation layers + let layers: [LayerModel] + + /// The list of glyphs used for text rendering + let glyphs: [Glyph]? + + /// The list of fonts used for text rendering + let fonts: FontList? + + /// Asset Library + let assetLibrary: AssetLibrary? + + /// Markers + let markers: [Marker]? + let markerMap: [String: Marker]? +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/Asset.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/Asset.cpp new file mode 100644 index 00000000000..12f67cdd8aa --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/Asset.cpp @@ -0,0 +1,5 @@ +#include "Asset.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/Asset.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/Asset.hpp new file mode 100644 index 00000000000..c071ab78543 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/Asset.hpp @@ -0,0 +1,50 @@ +#ifndef Asset_hpp +#define Asset_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class Asset { +public: + Asset(std::string id_) : + id(id_) { + } + + explicit Asset(json11::Json::object const &json) noexcept(false) { + auto idData = getAny(json, "id"); + if (idData.is_string()) { + id = idData.string_value(); + } else if (idData.is_number()) { + std::ostringstream idString; + idString << idData.int_value(); + id = idString.str(); + } + + objectName = getOptionalString(json, "nm"); + } + + Asset(const Asset&) = delete; + Asset& operator=(Asset&) = delete; + + virtual void toJson(json11::Json::object &json) const { + json.insert(std::make_pair("id", id)); + + if (objectName.has_value()) { + json.insert(std::make_pair("nm", objectName.value())); + } + } + +public: + /// The ID of the asset + std::string id; + + std::optional objectName; +}; + +} + +#endif /* Asset_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/Asset.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/Asset.swift new file mode 100644 index 00000000000..d610b1ce55b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/Asset.swift @@ -0,0 +1,43 @@ +// +// Asset.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +public class Asset: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Asset.CodingKeys.self) + if let id = try? container.decode(String.self, forKey: .id) { + self.id = id + } else { + id = String(try container.decode(Int.self, forKey: .id)) + } + } + + required init(dictionary: [String: Any]) throws { + if let id = dictionary[CodingKeys.id.rawValue] as? String { + self.id = id + } else if let id = dictionary[CodingKeys.id.rawValue] as? Int { + self.id = String(id) + } else { + throw InitializableError.invalidInput + } + } + + // MARK: Public + + /// The ID of the asset + public let id: String + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case id + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/AssetLibrary.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/AssetLibrary.cpp new file mode 100644 index 00000000000..7feff3231eb --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/AssetLibrary.cpp @@ -0,0 +1,5 @@ +#include "AssetLibrary.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/AssetLibrary.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/AssetLibrary.hpp new file mode 100644 index 00000000000..7ff3b07451f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/AssetLibrary.hpp @@ -0,0 +1,71 @@ +#ifndef AssetLibrary_hpp +#define AssetLibrary_hpp + +#include "Lottie/Private/Model/Assets/Asset.hpp" +#include "Lottie/Private/Model/Assets/ImageAsset.hpp" +#include "Lottie/Private/Model/Assets/PrecompAsset.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class AssetLibrary { +public: + AssetLibrary( + std::map> const &assets_, + std::map> const &imageAssets_, + std::map> const &precompAssets_ + ) : + assets(assets_), + imageAssets(imageAssets_), + precompAssets(precompAssets_) { + } + + explicit AssetLibrary(json11::Json const &json) noexcept(false) { + if (!json.is_array()) { + throw LottieParsingException(); + } + + for (const auto &item : json.array_items()) { + if (!item.is_object()) { + throw LottieParsingException(); + } + if (item.object_items().find("layers") != item.object_items().end()) { + auto asset = std::make_shared(item.object_items()); + assets.insert(std::make_pair(asset->id, asset)); + assetList.push_back(asset); + precompAssets.insert(std::make_pair(asset->id, asset)); + } else { + auto asset = std::make_shared(item.object_items()); + assets.insert(std::make_pair(asset->id, asset)); + assetList.push_back(asset); + imageAssets.insert(std::make_pair(asset->id, asset)); + } + } + } + + json11::Json::array toJson() const { + json11::Json::array result; + + for (const auto &asset : assetList) { + json11::Json::object assetJson; + asset->toJson(assetJson); + result.push_back(assetJson); + } + + return result; + } + +public: + /// The Assets + std::vector> assetList; + std::map> assets; + + std::map> imageAssets; + std::map> precompAssets; +}; + +} + +#endif /* AssetLibrary_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/AssetLibrary.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/AssetLibrary.swift new file mode 100644 index 00000000000..ba44c3bc44d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/AssetLibrary.swift @@ -0,0 +1,75 @@ +// +// AssetLibrary.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +final class AssetLibrary: Codable, AnyInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + var containerForKeys = container + + var decodedAssets = [String : Asset]() + + var imageAssets = [String : ImageAsset]() + var precompAssets = [String : PrecompAsset]() + + while !container.isAtEnd { + let keyContainer = try containerForKeys.nestedContainer(keyedBy: PrecompAsset.CodingKeys.self) + if keyContainer.contains(.layers) { + let precompAsset = try container.decode(PrecompAsset.self) + decodedAssets[precompAsset.id] = precompAsset + precompAssets[precompAsset.id] = precompAsset + } else { + let imageAsset = try container.decode(ImageAsset.self) + decodedAssets[imageAsset.id] = imageAsset + imageAssets[imageAsset.id] = imageAsset + } + } + assets = decodedAssets + self.precompAssets = precompAssets + self.imageAssets = imageAssets + } + + init(value: Any) throws { + guard let dictionaries = value as? [[String: Any]] else { + throw InitializableError.invalidInput + } + var decodedAssets = [String : Asset]() + var imageAssets = [String : ImageAsset]() + var precompAssets = [String : PrecompAsset]() + try dictionaries.forEach { dictionary in + if dictionary[PrecompAsset.CodingKeys.layers.rawValue] != nil { + let asset = try PrecompAsset(dictionary: dictionary) + decodedAssets[asset.id] = asset + precompAssets[asset.id] = asset + } else { + let asset = try ImageAsset(dictionary: dictionary) + decodedAssets[asset.id] = asset + imageAssets[asset.id] = asset + } + } + assets = decodedAssets + self.precompAssets = precompAssets + self.imageAssets = imageAssets + } + + // MARK: Internal + + /// The Assets + let assets: [String: Asset] + + let imageAssets: [String: ImageAsset] + let precompAssets: [String: PrecompAsset] + + func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(contentsOf: Array(assets.values)) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/ImageAsset.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/ImageAsset.cpp new file mode 100644 index 00000000000..b5a2e57d524 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/ImageAsset.cpp @@ -0,0 +1,5 @@ +#include "ImageAsset.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/ImageAsset.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/ImageAsset.hpp new file mode 100644 index 00000000000..7fa78ee6e4c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/ImageAsset.hpp @@ -0,0 +1,117 @@ +#ifndef ImageAsset_hpp +#define ImageAsset_hpp + +#include "Lottie/Private/Model/Assets/Asset.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +class ImageAsset: public Asset { +public: + ImageAsset( + std::string id_, + std::string name_, + std::string directory_, + double width_, + double height_ + ) : Asset(id_), + name(name_), + directory(directory_), + width(width_), + height(height_) { + } + + explicit ImageAsset(json11::Json::object const &json) noexcept(false) : + Asset(json) { + name = getString(json, "p"); + directory = getString(json, "u"); + width = getDouble(json, "w"); + height = getDouble(json, "h"); + + _e = getOptionalInt(json, "e"); + _t = getOptionalString(json, "t"); + } + + virtual void toJson(json11::Json::object &json) const override { + Asset::toJson(json); + + json.insert(std::make_pair("p", name)); + json.insert(std::make_pair("u", directory)); + json.insert(std::make_pair("w", width)); + json.insert(std::make_pair("h", height)); + + if (_e.has_value()) { + json.insert(std::make_pair("e", _e.value())); + } + if (_t.has_value()) { + json.insert(std::make_pair("t", _t.value())); + } + } + +public: + /// Image name + std::string name; + + /// Image Directory + std::string directory; + + /// Image Size + double width; + double height; + + std::optional _e; + std::optional _t; +}; + +/*extension Data { + + // MARK: Lifecycle + + /// Initializes `Data` from an `ImageAsset`. + /// + /// Returns nil when the input is not recognized as valid Data URL. + /// - parameter imageAsset: The image asset that contains Data URL. + internal init?(imageAsset: ImageAsset) { + self.init(dataString: imageAsset.name) + } + + /// Initializes `Data` from a [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) String. + /// + /// Returns nil when the input is not recognized as valid Data URL. + /// - parameter dataString: The data string to parse. + /// - parameter options: Options for the string parsing. Default value is `[]`. + internal init?(dataString: String, options: DataURLReadOptions = []) { + guard + dataString.hasPrefix("data:"), + let url = URL(string: dataString) + else { + return nil + } + // The code below is needed because Data(contentsOf:) floods logs + // with messages since url doesn't have a host. This only fixes flooding logs + // when data inside Data URL is base64 encoded. + if + let base64Range = dataString.range(of: ";base64,"), + !options.contains(DataURLReadOptions.legacy) + { + let encodedString = String(dataString[base64Range.upperBound...]) + self.init(base64Encoded: encodedString) + } else { + try? self.init(contentsOf: url) + } + } + + // MARK: Internal + + internal struct DataURLReadOptions: OptionSet { + let rawValue: Int + + /// Will read Data URL using Data(contentsOf:) + static let legacy = DataURLReadOptions(rawValue: 1 << 0) + } + +};*/ + +} + +#endif /* ImageAsset_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/ImageAsset.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/ImageAsset.swift new file mode 100644 index 00000000000..9b5df8f2aad --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/ImageAsset.swift @@ -0,0 +1,112 @@ +// +// ImageAsset.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +// MARK: - ImageAsset + +public final class ImageAsset: Asset { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ImageAsset.CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + directory = try container.decode(String.self, forKey: .directory) + width = try container.decode(Double.self, forKey: .width) + height = try container.decode(Double.self, forKey: .height) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + name = try dictionary.value(for: CodingKeys.name) + directory = try dictionary.value(for: CodingKeys.directory) + width = try dictionary.value(for: CodingKeys.width) + height = try dictionary.value(for: CodingKeys.height) + try super.init(dictionary: dictionary) + } + + // MARK: Public + + /// Image name + public let name: String + + /// Image Directory + public let directory: String + + /// Image Size + public let width: Double + + public let height: Double + + override public func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(directory, forKey: .directory) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case name = "p" + case directory = "u" + case width = "w" + case height = "h" + } +} + +extension Data { + + // MARK: Lifecycle + + /// Initializes `Data` from an `ImageAsset`. + /// + /// Returns nil when the input is not recognized as valid Data URL. + /// - parameter imageAsset: The image asset that contains Data URL. + internal init?(imageAsset: ImageAsset) { + self.init(dataString: imageAsset.name) + } + + /// Initializes `Data` from a [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) String. + /// + /// Returns nil when the input is not recognized as valid Data URL. + /// - parameter dataString: The data string to parse. + /// - parameter options: Options for the string parsing. Default value is `[]`. + internal init?(dataString: String, options: DataURLReadOptions = []) { + guard + dataString.hasPrefix("data:"), + let url = URL(string: dataString) + else { + return nil + } + // The code below is needed because Data(contentsOf:) floods logs + // with messages since url doesn't have a host. This only fixes flooding logs + // when data inside Data URL is base64 encoded. + if + let base64Range = dataString.range(of: ";base64,"), + !options.contains(DataURLReadOptions.legacy) + { + let encodedString = String(dataString[base64Range.upperBound...]) + self.init(base64Encoded: encodedString) + } else { + try? self.init(contentsOf: url) + } + } + + // MARK: Internal + + internal struct DataURLReadOptions: OptionSet { + let rawValue: Int + + /// Will read Data URL using Data(contentsOf:) + static let legacy = DataURLReadOptions(rawValue: 1 << 0) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/PrecompAsset.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/PrecompAsset.cpp new file mode 100644 index 00000000000..976044565aa --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/PrecompAsset.cpp @@ -0,0 +1,5 @@ +#include "PrecompAsset.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/PrecompAsset.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/PrecompAsset.hpp new file mode 100644 index 00000000000..36b5aa542f3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/PrecompAsset.hpp @@ -0,0 +1,62 @@ +#ifndef PrecompAsset_hpp +#define PrecompAsset_hpp + +#include "Lottie/Private/Model/Assets/Asset.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Layers/LayerModelSerialization.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class PrecompAsset: public Asset { +public: + PrecompAsset( + std::string const &id_, + std::vector> const &layers_ + ) : Asset(id_), + layers(layers_) { + } + + explicit PrecompAsset(json11::Json::object const &json) noexcept(false) : + Asset(json) { + frameRate = getOptionalDouble(json, "fr"); + + auto layerDictionaries = getObjectArray(json, "layers"); + for (size_t i = 0; i < layerDictionaries.size(); i++) { + try { + auto layer = parseLayerModel(layerDictionaries[i]); + layers.push_back(layer); + } catch(...) { + throw LottieParsingException(); + } + } + } + + virtual void toJson(json11::Json::object &json) const override { + Asset::toJson(json); + + json11::Json::array layerArray; + for (const auto &layer : layers) { + json11::Json::object layerJson; + layer->toJson(layerJson); + layerArray.push_back(layerJson); + } + json.insert(std::make_pair("layers", layerArray)); + + if (frameRate.has_value()) { + json.insert(std::make_pair("fr", frameRate.value())); + } + } + +public: + /// Layers of the precomp + std::vector> layers; + + std::optional frameRate; +}; + +} + +#endif /* PrecompAsset_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/PrecompAsset.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/PrecompAsset.swift new file mode 100644 index 00000000000..e26ee7af545 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Assets/PrecompAsset.swift @@ -0,0 +1,40 @@ +// +// PrecompAsset.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +final class PrecompAsset: Asset { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: PrecompAsset.CodingKeys.self) + layers = try container.decode([LayerModel].self, ofFamily: LayerType.self, forKey: .layers) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let layerDictionaries: [[String: Any]] = try dictionary.value(for: CodingKeys.layers) + layers = try [LayerModel].fromDictionaries(layerDictionaries) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case layers + } + + /// Layers of the precomp + let layers: [LayerModel] + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(layers, forKey: .layers) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/DictionaryInitializable.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/DictionaryInitializable.swift new file mode 100644 index 00000000000..b53443a0d80 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/DictionaryInitializable.swift @@ -0,0 +1,67 @@ +// +// DictionaryInitializable.swift +// Lottie +// +// Created by Marcelo Fabri on 5/5/22. +// + +import Foundation + +// MARK: - InitializableError + +enum InitializableError: Error { + case invalidInput +} + +// MARK: - DictionaryInitializable + +protocol DictionaryInitializable { + + init(dictionary: [String: Any]) throws + +} + +// MARK: - AnyInitializable + +protocol AnyInitializable { + + init(value: Any) throws + +} + +extension Dictionary { + + @_disfavoredOverload + func value(for key: KeyType) throws -> T where KeyType.RawValue == Key { + guard let value = self[key.rawValue] as? T else { + throw InitializableError.invalidInput + } + return value + } + + func value(for key: KeyType) throws -> T where KeyType.RawValue == Key { + if let value = self[key.rawValue] as? T { + return value + } + + if let value = self[key.rawValue] { + return try T(value: value) + } + + throw InitializableError.invalidInput + } + +} + +// MARK: - Array + AnyInitializable + +extension Array: AnyInitializable where Element == Double { + + init(value: Any) throws { + guard let array = value as? [Double] else { + throw InitializableError.invalidInput + } + self = array + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Extensions/Bundle.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Extensions/Bundle.swift new file mode 100644 index 00000000000..0b57604805f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Extensions/Bundle.swift @@ -0,0 +1,34 @@ +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) +import UIKit +#endif + +extension Bundle { + func getAnimationData(_ name: String, subdirectory: String? = nil) throws -> Data? { + // Check for files in the bundle at the given path + let name = name.removingJSONSuffix() + if let url = url(forResource: name, withExtension: "json", subdirectory: subdirectory) { + return try Data(contentsOf: url) + } + + // Check for data assets (not available on macOS) + #if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) + let assetKey = subdirectory != nil ? "\(subdirectory ?? "")/\(name)" : name + return NSDataAsset(name: assetKey, bundle: self)?.data + #else + return nil + #endif + } +} + +extension String { + fileprivate func removingJSONSuffix() -> String { + // Allow filenames to be passed with a ".json" extension (but not other extensions) + // to keep the behavior from Lottie 2.x - instead of failing to load the animation + guard hasSuffix(".json") else { + return self + } + + return (self as NSString).deletingPathExtension + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift new file mode 100644 index 00000000000..c0d8d755084 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift @@ -0,0 +1,44 @@ +// From: https://medium.com/@kewindannerfjordremeczki/swift-4-0-decodable-heterogeneous-collections-ecc0e6b468cf + +import Foundation + +// MARK: - ClassFamily + +/// To support a new class family, create an enum that conforms to this protocol and contains the different types. +protocol ClassFamily: Decodable { + /// The discriminator key. + static var discriminator: Discriminator { get } + + /// Returns the class type of the object corresponding to the value. + func getType() -> AnyObject.Type +} + +// MARK: - Discriminator + +/// Discriminator key enum used to retrieve discriminator fields in JSON payloads. +enum Discriminator: String, CodingKey { + case type = "ty" +} + +extension KeyedDecodingContainer { + + /// Decode a heterogeneous list of objects for a given family. + /// - Parameters: + /// - heterogeneousType: The decodable type of the list. + /// - family: The ClassFamily enum for the type family. + /// - key: The CodingKey to look up the list in the current container. + /// - Returns: The resulting list of heterogeneousType elements. + func decode(_: [T].Type, ofFamily family: U.Type, forKey key: K) throws -> [T] { + var container = try nestedUnkeyedContainer(forKey: key) + var list = [T]() + var tmpContainer = container + while !container.isAtEnd { + let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self) + let family: U = try typeContainer.decode(U.self, forKey: U.discriminator) + if let type = family.getType() as? T.Type { + list.append(try tmpContainer.decode(type)) + } + } + return list + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeData.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeData.swift new file mode 100644 index 00000000000..20f4ba8372b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeData.swift @@ -0,0 +1,113 @@ +// +// Keyframe.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import CoreGraphics +import Foundation + +// MARK: - KeyframeData + +/// A generic class used to parse and remap keyframe json. +/// +/// Keyframe json has a couple of different variations and formats depending on the +/// type of keyframea and also the version of the JSON. By parsing the raw data +/// we can reconfigure it into a constant format. +final class KeyframeData { + + // MARK: Lifecycle + + init( + startValue: T?, + endValue: T?, + time: AnimationFrameTime?, + hold: Int?, + inTangent: Vector2D?, + outTangent: Vector2D?, + spatialInTangent: Vector3D?, + spatialOutTangent: Vector3D?) + { + self.startValue = startValue + self.endValue = endValue + self.time = time + self.hold = hold + self.inTangent = inTangent + self.outTangent = outTangent + self.spatialInTangent = spatialInTangent + self.spatialOutTangent = spatialOutTangent + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case startValue = "s" + case endValue = "e" + case time = "t" + case hold = "h" + case inTangent = "i" + case outTangent = "o" + case spatialInTangent = "ti" + case spatialOutTangent = "to" + } + + /// The start value of the keyframe + let startValue: T? + /// The End value of the keyframe. Note: Newer versions animation json do not have this field. + let endValue: T? + /// The time in frames of the keyframe. + let time: AnimationFrameTime? + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + let hold: Int? + + /// The in tangent for the time interpolation curve. + let inTangent: Vector2D? + /// The out tangent for the time interpolation curve. + let outTangent: Vector2D? + + /// The spacial in tangent of the vector. + let spatialInTangent: Vector3D? + /// The spacial out tangent of the vector. + let spatialOutTangent: Vector3D? + + var isHold: Bool { + if let hold = hold { + return hold > 0 + } + return false + } +} + +// MARK: Encodable + +extension KeyframeData: Encodable where T: Encodable { } + +// MARK: Decodable + +extension KeyframeData: Decodable where T: Decodable { } + +// MARK: DictionaryInitializable + +extension KeyframeData: DictionaryInitializable where T: AnyInitializable { + convenience init(dictionary: [String: Any]) throws { + let startValue = try? dictionary[CodingKeys.startValue.rawValue].flatMap(T.init) + let endValue = try? dictionary[CodingKeys.endValue.rawValue].flatMap(T.init) + let time: AnimationFrameTime? = try? dictionary.value(for: CodingKeys.time) + let hold: Int? = try? dictionary.value(for: CodingKeys.hold) + let inTangent: Vector2D? = try? dictionary.value(for: CodingKeys.inTangent) + let outTangent: Vector2D? = try? dictionary.value(for: CodingKeys.outTangent) + let spatialInTangent: Vector3D? = try? dictionary.value(for: CodingKeys.spatialInTangent) + let spatialOutTangent: Vector3D? = try? dictionary.value(for: CodingKeys.spatialOutTangent) + + self.init( + startValue: startValue, + endValue: endValue, + time: time, + hold: hold, + inTangent: inTangent, + outTangent: outTangent, + spatialInTangent: spatialInTangent, + spatialOutTangent: spatialOutTangent) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeGroup.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeGroup.cpp new file mode 100644 index 00000000000..2f0c1a788a7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeGroup.cpp @@ -0,0 +1,5 @@ +#include "KeyframeGroup.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeGroup.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeGroup.hpp new file mode 100644 index 00000000000..ce591c76556 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeGroup.hpp @@ -0,0 +1,150 @@ +#ifndef KeyframeGroup_hpp +#define KeyframeGroup_hpp + +#include "Lottie/Public/Keyframes/Keyframe.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// Used for coding/decoding a group of Keyframes by type. +/// +/// Keyframe data is wrapped in a dictionary { "k" : KeyframeData }. +/// The keyframe data can either be an array of keyframes or, if no animation is present, the raw value. +/// This helper object is needed to properly decode the json. + +template +class KeyframeGroup { +public: + KeyframeGroup(std::vector> &&keyframes_) : + keyframes(std::move(keyframes_)), + isSingle(false) { + } + + KeyframeGroup(T const &value_) : + keyframes({ Keyframe(value_, std::nullopt, std::nullopt) }), + isSingle(false) { + } + + KeyframeGroup(json11::Json::object const &json) noexcept(false) { + isAnimated = getOptionalInt(json, "a"); + expression = getOptionalAny(json, "x"); + expressionIndex = getOptionalInt(json, "ix"); + _extraL = getOptionalInt(json, "l"); + + auto containerData = getAny(json, "k"); + + try { + LottieParsingException::Guard expectedException; + T keyframeData = T(containerData); + keyframes.push_back(Keyframe(keyframeData, std::nullopt, std::nullopt)); + isSingle = true; + } catch(...) { + // Decode and array of keyframes. + // + // Body Movin and Lottie deal with keyframes in different ways. + // + // A keyframe object in Body movin defines a span of time with a START + // and an END, from the current keyframe time to the next keyframe time. + // + // A keyframe object in Lottie defines a singular point in time/space. + // This point has an in-tangent and an out-tangent. + // + // To properly decode this we must iterate through keyframes while holding + // reference to the previous keyframe. + + if (!containerData.is_array()) { + throw LottieParsingException(); + } + + std::optional> previousKeyframeData; + for (const auto &containerItem : containerData.array_items()) { + // Ensure that Time and Value are present. + auto keyframeData = KeyframeData(containerItem); + rawKeyframeData.push_back(keyframeData); + + std::optional value; + if (keyframeData.startValue.has_value()) { + value = keyframeData.startValue; + } else if (previousKeyframeData.has_value()) { + value = previousKeyframeData->endValue; + } + if (!value.has_value()) { + throw LottieParsingException(); + } + if (!keyframeData.time.has_value()) { + throw LottieParsingException(); + } + + std::optional inTangent; + std::optional spatialInTangent; + if (previousKeyframeData.has_value()) { + inTangent = previousKeyframeData->inTangent; + spatialInTangent = previousKeyframeData->spatialInTangent; + } + + keyframes.emplace_back( + value.value(), + keyframeData.time.value(), + keyframeData.isHold(), + inTangent, + keyframeData.outTangent, + spatialInTangent, + keyframeData.spatialOutTangent + ); + + previousKeyframeData = keyframeData; + } + + isSingle = false; + } + } + + json11::Json::object toJson() const { + json11::Json::object result; + + assert(!keyframes.empty()); + + if (keyframes.size() == 1 && isSingle) { + result.insert(std::make_pair("k", keyframes[0].value.toJson())); + } else { + json11::Json::array containerData; + + for (const auto &keyframe : rawKeyframeData) { + containerData.push_back(keyframe.toJson()); + } + + result.insert(std::make_pair("k", containerData)); + } + + if (isAnimated.has_value()) { + result.insert(std::make_pair("a", isAnimated.value())); + } + if (expression.has_value()) { + result.insert(std::make_pair("x", expression.value())); + } + if (expressionIndex.has_value()) { + result.insert(std::make_pair("ix", expressionIndex.value())); + } + if (_extraL.has_value()) { + result.insert(std::make_pair("l", _extraL.value())); + } + + return result; + } + +public: + std::vector> keyframes; + std::optional isAnimated; + + std::optional expression; + std::optional expressionIndex; + std::vector> rawKeyframeData; + bool isSingle = false; + std::optional _extraL; +}; + +} + +#endif /* KeyframeGroup_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeGroup.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeGroup.swift new file mode 100644 index 00000000000..74c09cca7a1 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Keyframes/KeyframeGroup.swift @@ -0,0 +1,197 @@ +// +// KeyframeGroup.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import Foundation + +// MARK: - KeyframeGroup + +/// Used for coding/decoding a group of Keyframes by type. +/// +/// Keyframe data is wrapped in a dictionary { "k" : KeyframeData }. +/// The keyframe data can either be an array of keyframes or, if no animation is present, the raw value. +/// This helper object is needed to properly decode the json. + +final class KeyframeGroup { + + // MARK: Lifecycle + + init(keyframes: ContiguousArray>) { + self.keyframes = keyframes + } + + init(_ value: T) { + keyframes = [Keyframe(value)] + } + + // MARK: Internal + + enum KeyframeWrapperKey: String, CodingKey { + case keyframeData = "k" + } + + let keyframes: ContiguousArray> + +} + +// MARK: Decodable + +extension KeyframeGroup: Decodable where T: Decodable { + convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: KeyframeWrapperKey.self) + + if let keyframeData: T = try? container.decode(T.self, forKey: .keyframeData) { + /// Try to decode raw value; No keyframe data. + self.init(keyframes: [Keyframe(keyframeData)]) + } else { + // Decode and array of keyframes. + // + // Body Movin and Lottie deal with keyframes in different ways. + // + // A keyframe object in Body movin defines a span of time with a START + // and an END, from the current keyframe time to the next keyframe time. + // + // A keyframe object in Lottie defines a singular point in time/space. + // This point has an in-tangent and an out-tangent. + // + // To properly decode this we must iterate through keyframes while holding + // reference to the previous keyframe. + + var keyframesContainer = try container.nestedUnkeyedContainer(forKey: .keyframeData) + var keyframes = ContiguousArray>() + var previousKeyframeData: KeyframeData? + while !keyframesContainer.isAtEnd { + // Ensure that Time and Value are present. + + let keyframeData = try keyframesContainer.decode(KeyframeData.self) + + guard + let value: T = keyframeData.startValue ?? previousKeyframeData?.endValue, + let time = keyframeData.time else + { + /// Missing keyframe data. JSON must be corrupt. + throw DecodingError.dataCorruptedError( + forKey: KeyframeWrapperKey.keyframeData, + in: container, + debugDescription: "Missing keyframe data.") + } + + keyframes.append(Keyframe( + value: value, + time: AnimationFrameTime(time), + isHold: keyframeData.isHold, + inTangent: previousKeyframeData?.inTangent, + outTangent: keyframeData.outTangent, + spatialInTangent: previousKeyframeData?.spatialInTangent, + spatialOutTangent: keyframeData.spatialOutTangent)) + previousKeyframeData = keyframeData + } + self.init(keyframes: keyframes) + } + } +} + +// MARK: Encodable + +extension KeyframeGroup: Encodable where T: Encodable { + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: KeyframeWrapperKey.self) + + if keyframes.count == 1 { + let keyframe = keyframes[0] + try container.encode(keyframe.value, forKey: .keyframeData) + } else { + var keyframeContainer = container.nestedUnkeyedContainer(forKey: .keyframeData) + + for i in 1..( + startValue: keyframe.value, + endValue: nextKeyframe.value, + time: keyframe.time, + hold: keyframe.isHold ? 1 : nil, + inTangent: nextKeyframe.inTangent, + outTangent: keyframe.outTangent, + spatialInTangent: nil, + spatialOutTangent: nil) + try keyframeContainer.encode(keyframeData) + } + } + } +} + +// MARK: DictionaryInitializable + +extension KeyframeGroup: DictionaryInitializable where T: AnyInitializable { + convenience init(dictionary: [String: Any]) throws { + var keyframes = ContiguousArray>() + if + let rawValue = dictionary[KeyframeWrapperKey.keyframeData.rawValue], + let value = try? T(value: rawValue) + { + keyframes = [Keyframe(value)] + } else { + var frameDictionaries: [[String: Any]] + if let singleFrameDictionary = dictionary[KeyframeWrapperKey.keyframeData.rawValue] as? [String: Any] { + frameDictionaries = [singleFrameDictionary] + } else { + frameDictionaries = try dictionary.value(for: KeyframeWrapperKey.keyframeData) + } + var previousKeyframeData: KeyframeData? + for frameDictionary in frameDictionaries { + let data = try KeyframeData(dictionary: frameDictionary) + guard + let value: T = data.startValue ?? previousKeyframeData?.endValue, + let time = data.time else + { + throw InitializableError.invalidInput + } + keyframes.append(Keyframe( + value: value, + time: time, + isHold: data.isHold, + inTangent: previousKeyframeData?.inTangent, + outTangent: data.outTangent, + spatialInTangent: previousKeyframeData?.spatialInTangent, + spatialOutTangent: data.spatialOutTangent)) + previousKeyframeData = data + } + } + + self.init(keyframes: keyframes) + } +} + +// MARK: Equatable + +extension KeyframeGroup: Equatable where T: Equatable { + static func == (_ lhs: KeyframeGroup, _ rhs: KeyframeGroup) -> Bool { + lhs.keyframes == rhs.keyframes + } +} + +// MARK: Hashable + +extension KeyframeGroup: Hashable where T: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(keyframes) + } +} + +extension Keyframe { + /// Creates a copy of this `Keyframe` with the same timing data, but a different value + func withValue(_ newValue: Value) -> Keyframe { + Keyframe( + value: newValue, + time: time, + isHold: isHold, + inTangent: inTangent, + outTangent: outTangent, + spatialInTangent: spatialInTangent, + spatialOutTangent: spatialOutTangent) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ImageLayerModel.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ImageLayerModel.cpp new file mode 100644 index 00000000000..4251973760c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ImageLayerModel.cpp @@ -0,0 +1,5 @@ +#include "ImageLayerModel.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ImageLayerModel.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ImageLayerModel.hpp new file mode 100644 index 00000000000..b929fcc64aa --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ImageLayerModel.hpp @@ -0,0 +1,38 @@ +#ifndef ImageLayerModel_hpp +#define ImageLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// A layer that holds an image. +class ImageLayerModel: public LayerModel { +public: + explicit ImageLayerModel(json11::Json::object const &json) noexcept(false) : + LayerModel(json) { + referenceID = getString(json, "refId"); + + _sc = getOptionalString(json, "sc"); + } + + virtual void toJson(json11::Json::object &json) const override { + LayerModel::toJson(json); + + json.insert(std::make_pair("refId", referenceID)); + + if (_sc.has_value()) { + json.insert(std::make_pair("sc", _sc.value())); + } + } + +public: + /// The reference ID of the image. + std::string referenceID; + + std::optional _sc; +}; + +} + +#endif /* ImageLayerModel_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ImageLayerModel.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ImageLayerModel.swift new file mode 100644 index 00000000000..91ea3247997 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ImageLayerModel.swift @@ -0,0 +1,42 @@ +// +// ImageLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// A layer that holds an image. +final class ImageLayerModel: LayerModel { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ImageLayerModel.CodingKeys.self) + referenceID = try container.decode(String.self, forKey: .referenceID) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + referenceID = try dictionary.value(for: CodingKeys.referenceID) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The reference ID of the image. + let referenceID: String + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(referenceID, forKey: .referenceID) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case referenceID = "refId" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModel.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModel.cpp new file mode 100644 index 00000000000..6ceabadcf50 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModel.cpp @@ -0,0 +1,45 @@ +#include "LayerModel.hpp" + +namespace lottie { + +LayerType parseLayerType(json11::Json::object const &json, std::string const &key) { + if (const auto layerTypeValue = getOptionalInt(json, "ty")) { + switch (layerTypeValue.value()) { + case 0: + return LayerType::Precomp; + case 1: + return LayerType::Solid; + case 2: + return LayerType::Image; + case 3: + return LayerType::Null; + case 4: + return LayerType::Shape; + case 5: + return LayerType::Text; + default: + return LayerType::Null; + } + } else { + return LayerType::Null; + } +} + +int serializeLayerType(LayerType value) { + switch (value) { + case LayerType::Precomp: + return 0; + case LayerType::Solid: + return 1; + case LayerType::Image: + return 2; + case LayerType::Null: + return 3; + case LayerType::Shape: + return 4; + case LayerType::Text: + return 5; + } +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModel.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModel.hpp new file mode 100644 index 00000000000..90e2278c122 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModel.hpp @@ -0,0 +1,316 @@ +#ifndef LayerModel_hpp +#define LayerModel_hpp + +#include "Lottie/Private/Model/Objects/Transform.hpp" +#include "Lottie/Private/Model/Objects/Mask.hpp" +#include "Lottie/Private/Utility/Primitives/CoordinateSpace.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include +#include + +namespace lottie { + +enum class LayerType { + Precomp, + Solid, + Image, + Null, + Shape, + Text +}; + +LayerType parseLayerType(json11::Json::object const &json, std::string const &key); +int serializeLayerType(LayerType value); + +enum class MatteType: int { + None = 0, + Add = 1, + Invert = 2, + Unknown = 3 +}; + +enum class BlendMode: int { + Normal = 0, + Multiply = 1, + Screen = 2, + Overlay = 3, + Darken = 4, + Lighten = 5, + ColorDodge = 6, + ColorBurn = 7, + HardLight = 8, + SoftLight = 9, + Difference = 10, + Exclusion = 11, + Hue = 12, + Saturation = 13, + Color = 14, + Luminosity = 15 +}; + +/// A base top container for shapes, images, and other view objects. +class LayerModel { +public: + explicit LayerModel(json11::Json::object const &json) noexcept(false) { + name = getOptionalString(json, "nm"); + index = getOptionalInt(json, "ind"); + + type = parseLayerType(json, "ty"); + + autoOrient = getOptionalInt(json, "ao"); + + if (const auto typeRawValue = getOptionalInt(json, "ddd")) { + if (typeRawValue.value() == 0) { + coordinateSpace = CoordinateSpace::Type2d; + } else { + coordinateSpace = CoordinateSpace::Type3d; + } + } else { + coordinateSpace = std::nullopt; + } + + inFrame = getDouble(json, "ip"); + outFrame = getDouble(json, "op"); + startTime = getDouble(json, "st"); + + transform = std::make_shared(getObject(json, "ks")); + parent = getOptionalInt(json, "parent"); + + if (const auto blendModeRawValue = getOptionalInt(json, "bm")) { + switch (blendModeRawValue.value()) { + case 0: + blendMode = BlendMode::Normal; + break; + case 1: + blendMode = BlendMode::Multiply; + break; + case 2: + blendMode = BlendMode::Screen; + break; + case 3: + blendMode = BlendMode::Overlay; + break; + case 4: + blendMode = BlendMode::Darken; + break; + case 5: + blendMode = BlendMode::Lighten; + break; + case 6: + blendMode = BlendMode::ColorDodge; + break; + case 7: + blendMode = BlendMode::ColorBurn; + break; + case 8: + blendMode = BlendMode::HardLight; + break; + case 9: + blendMode = BlendMode::SoftLight; + break; + case 10: + blendMode = BlendMode::Difference; + break; + case 11: + blendMode = BlendMode::Exclusion; + break; + case 12: + blendMode = BlendMode::Hue; + break; + case 13: + blendMode = BlendMode::Saturation; + break; + case 14: + blendMode = BlendMode::Color; + break; + case 15: + blendMode = BlendMode::Luminosity; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto maskDictionaries = getOptionalObjectArray(json, "masksProperties")) { + masks = std::vector>(); + for (const auto &maskDictionary : maskDictionaries.value()) { + masks->push_back(std::make_shared(maskDictionary)); + } + } + + if (const auto timeStretchData = getOptionalDouble(json, "sr")) { + _timeStretch = timeStretchData.value(); + } + + if (const auto matteRawValue = getOptionalInt(json, "tt")) { + switch (matteRawValue.value()) { + case 0: + matte = MatteType::None; + break; + case 1: + matte = MatteType::Add; + break; + case 2: + matte = MatteType::Invert; + break; + case 3: + matte = MatteType::Unknown; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto hiddenData = getOptionalBool(json, "hd")) { + hidden = hiddenData.value(); + } + + hasMask = getOptionalBool(json, "hasMask"); + td = getOptionalInt(json, "td"); + effectsData = getOptionalAny(json, "ef"); + layerClass = getOptionalString(json, "cl"); + _extraHidden = getOptionalAny(json, "hidden"); + } + + LayerModel(const LayerModel&) = delete; + LayerModel& operator=(LayerModel&) = delete; + + virtual void toJson(json11::Json::object &json) const { + if (name.has_value()) { + json.insert(std::make_pair("nm", name.value())); + } + if (index.has_value()) { + json.insert(std::make_pair("ind", index.value())); + } + + if (autoOrient.has_value()) { + json.insert(std::make_pair("ao", autoOrient.value())); + } + + json.insert(std::make_pair("ty", serializeLayerType(type))); + + if (coordinateSpace.has_value()) { + switch (coordinateSpace.value()) { + case CoordinateSpace::Type2d: + json.insert(std::make_pair("ddd", 0)); + break; + case CoordinateSpace::Type3d: + json.insert(std::make_pair("ddd", 1)); + break; + } + } + + json.insert(std::make_pair("ip", inFrame)); + json.insert(std::make_pair("op", outFrame)); + json.insert(std::make_pair("st", startTime)); + + json.insert(std::make_pair("ks", transform->toJson())); + + if (parent.has_value()) { + json.insert(std::make_pair("parent", parent.value())); + } + + if (blendMode.has_value()) { + json.insert(std::make_pair("bm", (int)blendMode.value())); + } + + if (masks.has_value()) { + json11::Json::array maskArray; + for (const auto &mask : masks.value()) { + maskArray.push_back(mask->toJson()); + } + json.insert(std::make_pair("masksProperties", maskArray)); + } + + if (_timeStretch.has_value()) { + json.insert(std::make_pair("sr", _timeStretch.value())); + } + + if (matte.has_value()) { + json.insert(std::make_pair("tt", (int)matte.value())); + } + + if (hidden.has_value()) { + json.insert(std::make_pair("hd", hidden.value())); + } + + if (hasMask.has_value()) { + json.insert(std::make_pair("hasMask", hasMask.value())); + } + if (td.has_value()) { + json.insert(std::make_pair("td", td.value())); + } + if (effectsData.has_value()) { + json.insert(std::make_pair("ef", effectsData.value())); + } + if (layerClass.has_value()) { + json.insert(std::make_pair("cl", layerClass.value())); + } + if (_extraHidden.has_value()) { + json.insert(std::make_pair("hidden", _extraHidden.value())); + } + } + + double timeStretch() { + if (_timeStretch.has_value()) { + return _timeStretch.value(); + } else { + return 1.0; + } + } + +public: + /// The readable name of the layer + std::optional name; + + /// The index of the layer + std::optional index; + + /// The type of the layer. + LayerType type; + + std::optional autoOrient; + + /// The coordinate space + std::optional coordinateSpace; + + /// The in time of the layer in frames. + double inFrame; + /// The out time of the layer in frames. + double outFrame; + + /// The start time of the layer in frames. + double startTime; + + /// The transform of the layer + std::shared_ptr transform; + + /// The index of the parent layer, if applicable. + std::optional parent; + + /// The blending mode for the layer + std::optional blendMode; + + /// An array of masks for the layer. + std::optional>> masks; + + /// A number that stretches time by a multiplier + std::optional _timeStretch; + + /// The type of matte if any. + std::optional matte; + + std::optional hidden; + + std::optional hasMask; + std::optional td; + std::optional effectsData; + std::optional layerClass; + std::optional _extraHidden; +}; + +} + +#endif /* LayerModel_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModel.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModel.swift new file mode 100644 index 00000000000..e4604205ceb --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModel.swift @@ -0,0 +1,228 @@ +// +// Layer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import Foundation + +// MARK: - LayerType + ClassFamily + +/// Used for mapping a heterogeneous list to classes for parsing. +extension LayerType: ClassFamily { + static var discriminator: Discriminator = .type + + func getType() -> AnyObject.Type { + switch self { + case .precomp: + return PreCompLayerModel.self + case .solid: + return SolidLayerModel.self + case .image: + return ImageLayerModel.self + case .null: + return LayerModel.self + case .shape: + return ShapeLayerModel.self + case .text: + return TextLayerModel.self + } + } +} + +// MARK: - LayerType + +public enum LayerType: Int, Codable { + case precomp + case solid + case image + case null + case shape + case text + + public init(from decoder: Decoder) throws { + self = try LayerType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .null + } +} + +// MARK: - MatteType + +public enum MatteType: Int, Codable { + case none + case add + case invert + case unknown +} + +// MARK: - BlendMode + +public enum BlendMode: Int, Codable { + case normal + case multiply + case screen + case overlay + case darken + case lighten + case colorDodge + case colorBurn + case hardLight + case softLight + case difference + case exclusion + case hue + case saturation + case color + case luminosity +} + +// MARK: - LayerModel + +/// A base top container for shapes, images, and other view objects. +class LayerModel: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: LayerModel.CodingKeys.self) + name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer" + index = try container.decodeIfPresent(Int.self, forKey: .index) ?? .random(in: Int.min...Int.max) + type = try container.decode(LayerType.self, forKey: .type) + coordinateSpace = try container.decodeIfPresent(CoordinateSpace.self, forKey: .coordinateSpace) ?? .type2d + inFrame = try container.decode(Double.self, forKey: .inFrame) + outFrame = try container.decode(Double.self, forKey: .outFrame) + startTime = try container.decode(Double.self, forKey: .startTime) + transform = try container.decode(Transform.self, forKey: .transform) + parent = try container.decodeIfPresent(Int.self, forKey: .parent) + blendMode = try container.decodeIfPresent(BlendMode.self, forKey: .blendMode) ?? .normal + masks = try container.decodeIfPresent([Mask].self, forKey: .masks) + timeStretch = try container.decodeIfPresent(Double.self, forKey: .timeStretch) ?? 1 + matte = try container.decodeIfPresent(MatteType.self, forKey: .matte) + hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false + } + + required init(dictionary: [String: Any]) throws { + name = (try? dictionary.value(for: CodingKeys.name)) ?? "Layer" + index = try dictionary.value(for: CodingKeys.index) ?? .random(in: Int.min...Int.max) + type = LayerType(rawValue: try dictionary.value(for: CodingKeys.type)) ?? .null + if + let coordinateSpaceRawValue = dictionary[CodingKeys.coordinateSpace.rawValue] as? Int, + let coordinateSpace = CoordinateSpace(rawValue: coordinateSpaceRawValue) + { + self.coordinateSpace = coordinateSpace + } else { + coordinateSpace = .type2d + } + inFrame = try dictionary.value(for: CodingKeys.inFrame) + outFrame = try dictionary.value(for: CodingKeys.outFrame) + startTime = try dictionary.value(for: CodingKeys.startTime) + transform = try Transform(dictionary: try dictionary.value(for: CodingKeys.transform)) + parent = try? dictionary.value(for: CodingKeys.parent) + if + let blendModeRawValue = dictionary[CodingKeys.blendMode.rawValue] as? Int, + let blendMode = BlendMode(rawValue: blendModeRawValue) + { + self.blendMode = blendMode + } else { + blendMode = .normal + } + if let maskDictionaries = dictionary[CodingKeys.masks.rawValue] as? [[String: Any]] { + masks = try maskDictionaries.map({ try Mask(dictionary: $0) }) + } else { + masks = nil + } + timeStretch = (try? dictionary.value(for: CodingKeys.timeStretch)) ?? 1 + if let matteRawValue = dictionary[CodingKeys.matte.rawValue] as? Int { + matte = MatteType(rawValue: matteRawValue) + } else { + matte = nil + } + hidden = (try? dictionary.value(for: CodingKeys.hidden)) ?? false + } + + // MARK: Internal + + /// The readable name of the layer + let name: String + + /// The index of the layer + let index: Int + + /// The type of the layer. + let type: LayerType + + /// The coordinate space + let coordinateSpace: CoordinateSpace + + /// The in time of the layer in frames. + let inFrame: Double + /// The out time of the layer in frames. + let outFrame: Double + + /// The start time of the layer in frames. + let startTime: Double + + /// The transform of the layer + let transform: Transform + + /// The index of the parent layer, if applicable. + let parent: Int? + + /// The blending mode for the layer + let blendMode: BlendMode + + /// An array of masks for the layer. + let masks: [Mask]? + + /// A number that stretches time by a multiplier + let timeStretch: Double + + /// The type of matte if any. + let matte: MatteType? + + let hidden: Bool + + // MARK: Fileprivate + + fileprivate enum CodingKeys: String, CodingKey { + case name = "nm" + case index = "ind" + case type = "ty" + case coordinateSpace = "ddd" + case inFrame = "ip" + case outFrame = "op" + case startTime = "st" + case transform = "ks" + case parent + case blendMode = "bm" + case masks = "masksProperties" + case timeStretch = "sr" + case matte = "tt" + case hidden = "hd" + } +} + +extension Array where Element == LayerModel { + + static func fromDictionaries(_ dictionaries: [[String: Any]]) throws -> [LayerModel] { + try dictionaries.compactMap { dictionary in + let layerType = dictionary[LayerModel.CodingKeys.type.rawValue] as? Int + switch LayerType(rawValue: layerType ?? LayerType.null.rawValue) { + case .precomp: + return try PreCompLayerModel(dictionary: dictionary) + case .solid: + return try SolidLayerModel(dictionary: dictionary) + case .image: + return try ImageLayerModel(dictionary: dictionary) + case .null: + return try LayerModel(dictionary: dictionary) + case .shape: + return try ShapeLayerModel(dictionary: dictionary) + case .text: + return try TextLayerModel(dictionary: dictionary) + case .none: + return nil + } + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModelSerialization.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModelSerialization.cpp new file mode 100644 index 00000000000..6183d51be3f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModelSerialization.cpp @@ -0,0 +1,34 @@ +#include "LayerModelSerialization.hpp" + +#include "Lottie/Private/Model/Layers/PreCompLayerModel.hpp" +#include "Lottie/Private/Model/Layers/SolidLayerModel.hpp" +#include "Lottie/Private/Model/Layers/ImageLayerModel.hpp" +#include "Lottie/Private/Model/Layers/ShapeLayerModel.hpp" +#include "Lottie/Private/Model/Layers/TextLayerModel.hpp" + +namespace lottie { + +std::shared_ptr parseLayerModel(json11::Json::object const &json) noexcept(false) { + LayerType layerType = parseLayerType(json, "ty"); + + switch (layerType) { + case LayerType::Precomp: + return std::make_shared(json); + case LayerType::Solid: + return std::make_shared(json); + case LayerType::Image: + return std::make_shared(json); + case LayerType::Null: + return std::make_shared(json); + case LayerType::Shape: + try { + return std::make_shared(json); + } catch(...) { + throw LottieParsingException(); + } + case LayerType::Text: + return std::make_shared(json); + } +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModelSerialization.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModelSerialization.hpp new file mode 100644 index 00000000000..9619f45f89c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/LayerModelSerialization.hpp @@ -0,0 +1,14 @@ +#ifndef LayerModelSerialization_hpp +#define LayerModelSerialization_hpp + +#include "json11/json11.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" + +namespace lottie { + +std::shared_ptr parseLayerModel(json11::Json::object const &json) noexcept(false); + +} + +#endif /* LayerModelSerialization_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/PreCompLayerModel.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/PreCompLayerModel.cpp new file mode 100644 index 00000000000..2a4c7b1363a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/PreCompLayerModel.cpp @@ -0,0 +1,5 @@ +#include "PreCompLayerModel.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/PreCompLayerModel.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/PreCompLayerModel.hpp new file mode 100644 index 00000000000..f2f4d6bc83f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/PreCompLayerModel.hpp @@ -0,0 +1,55 @@ +#ifndef PreCompLayerModel_hpp +#define PreCompLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// A layer that holds another animation composition. +class PreCompLayerModel: public LayerModel { +public: + PreCompLayerModel(json11::Json::object const &json) : + LayerModel(json) { + referenceID = getString(json, "refId"); + if (const auto timeRemappingData = getOptionalObject(json, "tm")) { + timeRemapping = KeyframeGroup(timeRemappingData.value()); + } + width = getDouble(json, "w"); + height = getDouble(json, "h"); + } + + virtual void toJson(json11::Json::object &json) const override { + LayerModel::toJson(json); + + json.insert(std::make_pair("refId", referenceID)); + + if (timeRemapping.has_value()) { + json.insert(std::make_pair("tm", timeRemapping->toJson())); + } + + json.insert(std::make_pair("w", width)); + json.insert(std::make_pair("h", height)); + } + +public: + /// The reference ID of the precomp. + std::string referenceID; + + /// A value that remaps time over time. + std::optional> timeRemapping; + + /// Precomp Width + double width; + + /// Precomp Height + double height; +}; + +} + +#endif /* PreCompLayerModel_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/PreCompLayerModel.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/PreCompLayerModel.swift new file mode 100644 index 00000000000..d9e426a2a57 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/PreCompLayerModel.swift @@ -0,0 +1,67 @@ +// +// PreCompLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// A layer that holds another animation composition. +final class PreCompLayerModel: LayerModel { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: PreCompLayerModel.CodingKeys.self) + referenceID = try container.decode(String.self, forKey: .referenceID) + timeRemapping = try container.decodeIfPresent(KeyframeGroup.self, forKey: .timeRemapping) + width = try container.decode(Double.self, forKey: .width) + height = try container.decode(Double.self, forKey: .height) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + referenceID = try dictionary.value(for: CodingKeys.referenceID) + if let timeRemappingDictionary = dictionary[CodingKeys.timeRemapping.rawValue] as? [String: Any] { + timeRemapping = try KeyframeGroup(dictionary: timeRemappingDictionary) + } else { + timeRemapping = nil + } + width = try dictionary.value(for: CodingKeys.width) + height = try dictionary.value(for: CodingKeys.height) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The reference ID of the precomp. + let referenceID: String + + /// A value that remaps time over time. + let timeRemapping: KeyframeGroup? + + /// Precomp Width + let width: Double + + /// Precomp Height + let height: Double + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(referenceID, forKey: .referenceID) + try container.encode(timeRemapping, forKey: .timeRemapping) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case referenceID = "refId" + case timeRemapping = "tm" + case width = "w" + case height = "h" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ShapeLayerModel.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ShapeLayerModel.cpp new file mode 100644 index 00000000000..608dca567bf --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ShapeLayerModel.cpp @@ -0,0 +1,5 @@ +#include "ShapeLayerModel.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ShapeLayerModel.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ShapeLayerModel.hpp new file mode 100644 index 00000000000..25111fd674e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ShapeLayerModel.hpp @@ -0,0 +1,43 @@ +#ifndef ShapeLayerModel_hpp +#define ShapeLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// A layer that holds vector shape objects. +class ShapeLayerModel: public LayerModel { +public: + ShapeLayerModel(json11::Json::object const &json) noexcept(false) : + LayerModel(json) { + auto shapeItemsData = getObjectArray(json, "shapes"); + for (const auto &shapeItemData : shapeItemsData) { + items.push_back(parseShapeItem(shapeItemData)); + } + } + + virtual void toJson(json11::Json::object &json) const override { + LayerModel::toJson(json); + + json11::Json::array shapeItemArray; + for (const auto &item : items) { + json11::Json::object itemJson; + item->toJson(itemJson); + shapeItemArray.push_back(itemJson); + } + + json.insert(std::make_pair("shapes", shapeItemArray)); + } + +public: + /// A list of shape items. + std::vector> items; +}; + +} + +#endif /* ShapeLayerModel_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ShapeLayerModel.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ShapeLayerModel.swift new file mode 100644 index 00000000000..b9562ad4208 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/ShapeLayerModel.swift @@ -0,0 +1,43 @@ +// +// ShapeLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// A layer that holds vector shape objects. +final class ShapeLayerModel: LayerModel { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ShapeLayerModel.CodingKeys.self) + items = try container.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .items) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let itemDictionaries: [[String: Any]] = try dictionary.value(for: CodingKeys.items) + items = try [ShapeItem].fromDictionaries(itemDictionaries) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// A list of shape items. + let items: [ShapeItem] + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(items, forKey: .items) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case items = "shapes" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/SolidLayerModel.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/SolidLayerModel.cpp new file mode 100644 index 00000000000..153c2839532 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/SolidLayerModel.cpp @@ -0,0 +1,5 @@ +#include "SolidLayerModel.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/SolidLayerModel.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/SolidLayerModel.hpp new file mode 100644 index 00000000000..0ba379c7a84 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/SolidLayerModel.hpp @@ -0,0 +1,40 @@ +#ifndef SolidLayerModel_hpp +#define SolidLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// A layer that holds a solid color. +class SolidLayerModel: public LayerModel { +public: + explicit SolidLayerModel(json11::Json::object const &json) noexcept(false) : + LayerModel(json) { + colorHex = getString(json, "sc"); + width = getDouble(json, "sw"); + height = getDouble(json, "sh"); + } + + virtual void toJson(json11::Json::object &json) const override { + LayerModel::toJson(json); + + json.insert(std::make_pair("sc", colorHex)); + json.insert(std::make_pair("sw", width)); + json.insert(std::make_pair("sh", height)); + } + +public: + /// The color of the solid in Hex // Change to value provider. + std::string colorHex; + + /// The Width of the color layer + double width; + + /// The height of the color layer + double height; +}; + +} + +#endif /* SolidLayerModel_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/SolidLayerModel.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/SolidLayerModel.swift new file mode 100644 index 00000000000..9ad232a238b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/SolidLayerModel.swift @@ -0,0 +1,56 @@ +// +// SolidLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// A layer that holds a solid color. +final class SolidLayerModel: LayerModel { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: SolidLayerModel.CodingKeys.self) + colorHex = try container.decode(String.self, forKey: .colorHex) + width = try container.decode(Double.self, forKey: .width) + height = try container.decode(Double.self, forKey: .height) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + colorHex = try dictionary.value(for: CodingKeys.colorHex) + width = try dictionary.value(for: CodingKeys.width) + height = try dictionary.value(for: CodingKeys.height) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The color of the solid in Hex // Change to value provider. + let colorHex: String + + /// The Width of the color layer + let width: Double + + /// The height of the color layer + let height: Double + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(colorHex, forKey: .colorHex) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case colorHex = "sc" + case width = "sw" + case height = "sh" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/TextLayerModel.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/TextLayerModel.cpp new file mode 100644 index 00000000000..9e2ad034b15 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/TextLayerModel.cpp @@ -0,0 +1,5 @@ +#include "TextLayerModel.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/TextLayerModel.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/TextLayerModel.hpp new file mode 100644 index 00000000000..5b6b7ae77ee --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/TextLayerModel.hpp @@ -0,0 +1,81 @@ +#ifndef TextLayerModel_hpp +#define TextLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/Text/TextDocument.hpp" +#include "Lottie/Private/Model/Text/TextAnimator.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// A layer that holds text. +class TextLayerModel: public LayerModel { +public: + TextLayerModel(json11::Json::object const &json) : + LayerModel(json), + text(KeyframeGroup(TextDocument( + "", + 0.0, + "", + TextJustification::Left, + 0, + 0.0, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt + ))) { + auto textContainer = getObject(json, "t"); + + auto textData = getObject(textContainer, "d"); + text = KeyframeGroup(textData); + + if (auto animatorsData = getOptionalObjectArray(textContainer, "a")) { + for (const auto &animatorData : animatorsData.value()) { + animators.push_back(std::make_shared(animatorData)); + } + } + + _extraM = getOptionalAny(textContainer, "m"); + _extraP = getOptionalAny(textContainer, "p"); + } + + virtual void toJson(json11::Json::object &json) const override { + LayerModel::toJson(json); + + json11::Json::object textContainer; + textContainer.insert(std::make_pair("d", text.toJson())); + if (_extraM.has_value()) { + textContainer.insert(std::make_pair("m", _extraM.value())); + } + if (_extraP.has_value()) { + textContainer.insert(std::make_pair("p", _extraP.value())); + } + json11::Json::array animatorArray; + for (const auto &animator : animators) { + animatorArray.push_back(animator->toJson()); + } + textContainer.insert(std::make_pair("a", animatorArray)); + + json.insert(std::make_pair("t", textContainer)); + } + +public: + /// The text for the layer + KeyframeGroup text; + + /// Text animators + std::vector> animators; + + std::optional _extraM; + std::optional _extraP; + std::optional _extraA; +}; + +} + +#endif /* TextLayerModel_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/TextLayerModel.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/TextLayerModel.swift new file mode 100644 index 00000000000..f4db922a5b4 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Layers/TextLayerModel.swift @@ -0,0 +1,58 @@ +// +// TextLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// A layer that holds text. +final class TextLayerModel: LayerModel { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: TextLayerModel.CodingKeys.self) + let textContainer = try container.nestedContainer(keyedBy: TextCodingKeys.self, forKey: .textGroup) + text = try textContainer.decode(KeyframeGroup.self, forKey: .text) + animators = try textContainer.decode([TextAnimator].self, forKey: .animators) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let containerDictionary: [String: Any] = try dictionary.value(for: CodingKeys.textGroup) + let textDictionary: [String: Any] = try containerDictionary.value(for: TextCodingKeys.text) + text = try KeyframeGroup(dictionary: textDictionary) + let animatorDictionaries: [[String: Any]] = try containerDictionary.value(for: TextCodingKeys.animators) + animators = try animatorDictionaries.map({ try TextAnimator(dictionary: $0) }) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The text for the layer + let text: KeyframeGroup + + /// Text animators + let animators: [TextAnimator] + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + var textContainer = container.nestedContainer(keyedBy: TextCodingKeys.self, forKey: .textGroup) + try textContainer.encode(text, forKey: .text) + try textContainer.encode(animators, forKey: .animators) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case textGroup = "t" + } + + private enum TextCodingKeys: String, CodingKey { + case text = "d" + case animators = "a" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/DashElement.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/DashElement.cpp new file mode 100644 index 00000000000..33426de3384 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/DashElement.cpp @@ -0,0 +1,5 @@ +#include "DashPattern.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/DashElement.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/DashElement.hpp new file mode 100644 index 00000000000..6cd73101ff0 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/DashElement.hpp @@ -0,0 +1,78 @@ +#ifndef DashElement_hpp +#define DashElement_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Primitives/DashPattern.hpp" + +namespace lottie { + +enum class DashElementType { + Offset, + Dash, + Gap +}; + +class DashElement { +public: + DashElement( + DashElementType type_, + KeyframeGroup const &value_ + ) : + type(type_), + value(value_) { + } + + explicit DashElement(json11::Json::object const &json) noexcept(false) : + type(DashElementType::Offset), + value(KeyframeGroup(Vector1D(0.0))) { + auto typeRawValue = getString(json, "n"); + if (typeRawValue == "o") { + type = DashElementType::Offset; + } else if (typeRawValue == "d") { + type = DashElementType::Dash; + } else if (typeRawValue == "g") { + type = DashElementType::Gap; + } else { + throw LottieParsingException(); + } + + value = KeyframeGroup(getObject(json, "v")); + + name = getOptionalString(json, "nm"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + switch (type) { + case DashElementType::Offset: + result.insert(std::make_pair("n", "o")); + break; + case DashElementType::Dash: + result.insert(std::make_pair("n", "d")); + break; + case DashElementType::Gap: + result.insert(std::make_pair("n", "g")); + break; + } + + result.insert(std::make_pair("v", value.toJson())); + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + + return result; + } + +public: + DashElementType type; + KeyframeGroup value; + + std::optional name; +}; + +} + +#endif /* DashElement_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/DashPattern.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/DashPattern.swift new file mode 100644 index 00000000000..5fc74d93e9b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/DashPattern.swift @@ -0,0 +1,44 @@ +// +// DashPattern.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation + +// MARK: - DashElementType + +enum DashElementType: String, Codable { + case offset = "o" + case dash = "d" + case gap = "g" +} + +// MARK: - DashElement + +final class DashElement: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + init(dictionary: [String: Any]) throws { + let typeRawValue: String = try dictionary.value(for: CodingKeys.type) + guard let type = DashElementType(rawValue: typeRawValue) else { + throw InitializableError.invalidInput + } + self.type = type + let valueDictionary: [String: Any] = try dictionary.value(for: CodingKeys.value) + value = try KeyframeGroup(dictionary: valueDictionary) + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case type = "n" + case value = "v" + } + + let type: DashElementType + let value: KeyframeGroup + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/FitzModifier.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/FitzModifier.cpp new file mode 100644 index 00000000000..7b18e01e4a3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/FitzModifier.cpp @@ -0,0 +1,5 @@ +#include "FitzModifier.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/FitzModifier.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/FitzModifier.hpp new file mode 100644 index 00000000000..163cf09c21e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/FitzModifier.hpp @@ -0,0 +1,53 @@ +#ifndef FitzModifier_hpp +#define FitzModifier_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +class FitzModifier { +public: + explicit FitzModifier(json11::Json::object const &json) noexcept(false) { + original = getInt(json, "o"); + type12 = getOptionalInt(json, "f12"); + type3 = getOptionalInt(json, "f3"); + type4 = getOptionalInt(json, "f4"); + type5 = getOptionalInt(json, "f5"); + type6 = getOptionalInt(json, "f6"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("o", (double)original)); + if (type12.has_value()) { + result.insert(std::make_pair("f12", (double)type12.value())); + } + if (type3.has_value()) { + result.insert(std::make_pair("f3", (double)type3.value())); + } + if (type4.has_value()) { + result.insert(std::make_pair("f4", (double)type4.value())); + } + if (type5.has_value()) { + result.insert(std::make_pair("f5", (double)type5.value())); + } + if (type6.has_value()) { + result.insert(std::make_pair("f6", (double)type6.value())); + } + + return result; + } + +public: + double original; + std::optional type12; + std::optional type3; + std::optional type4; + std::optional type5; + std::optional type6; +}; + +} + +#endif /* FitzModifier_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Marker.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Marker.cpp new file mode 100644 index 00000000000..ea2664d4162 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Marker.cpp @@ -0,0 +1,5 @@ +#include "Marker.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Marker.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Marker.hpp new file mode 100644 index 00000000000..4219996a4b3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Marker.hpp @@ -0,0 +1,52 @@ +#ifndef Marker_hpp +#define Marker_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class Marker { +public: + Marker( + std::string const &name_, + AnimationFrameTime frameTime_ + ) : + name(name_), + frameTime(frameTime_) { + } + + explicit Marker(json11::Json::object const &json) noexcept(false) { + name = getString(json, "cm"); + frameTime = getDouble(json, "tm"); + dr = getOptionalInt(json, "dr"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("cm", name)); + result.insert(std::make_pair("tm", frameTime)); + + if (dr.has_value()) { + result.insert(std::make_pair("dr", dr.value())); + } + + return result; + } + +public: + /// The Marker Name + std::string name; + + /// The Frame time of the marker + AnimationFrameTime frameTime; + + std::optional dr; +}; + +} + +#endif /* Marker_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Marker.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Marker.swift new file mode 100644 index 00000000000..830977efc98 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Marker.swift @@ -0,0 +1,33 @@ +// +// Marker.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +/// A time marker +final class Marker: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + init(dictionary: [String: Any]) throws { + name = try dictionary.value(for: CodingKeys.name) + frameTime = try dictionary.value(for: CodingKeys.frameTime) + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case name = "cm" + case frameTime = "tm" + } + + /// The Marker Name + let name: String + + /// The Frame time of the marker + let frameTime: AnimationFrameTime + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Mask.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Mask.cpp new file mode 100644 index 00000000000..8cb64d67095 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Mask.cpp @@ -0,0 +1,5 @@ +#include "Mask.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Mask.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Mask.hpp new file mode 100644 index 00000000000..d453a1c3741 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Mask.hpp @@ -0,0 +1,139 @@ +#ifndef Mask_hpp +#define Mask_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class MaskMode { + Add, + Subtract, + Intersect, + Lighten, + Darken, + Difference, + None +}; + +class Mask { +public: + explicit Mask(json11::Json::object const &json) noexcept(false) : + opacity(KeyframeGroup(Vector1D(100.0))), + shape(KeyframeGroup(BezierPath())), + inverted(false), + expansion(KeyframeGroup(Vector1D(0.0))) { + if (const auto modeRawValue = getOptionalString(json, "mode")) { + if (modeRawValue.value() == "a") { + _mode = MaskMode::Add; + } else if (modeRawValue.value() == "s") { + _mode = MaskMode::Subtract; + } else if (modeRawValue.value() == "i") { + _mode = MaskMode::Intersect; + } else if (modeRawValue.value() == "l") { + _mode = MaskMode::Lighten; + } else if (modeRawValue.value() == "d") { + _mode = MaskMode::Darken; + } else if (modeRawValue.value() == "f") { + _mode = MaskMode::Difference; + } else if (modeRawValue.value() == "n") { + _mode = MaskMode::None; + } else { + throw LottieParsingException(); + } + } + + if (const auto opacityData = getOptionalObject(json, "o")) { + opacity = KeyframeGroup(opacityData.value()); + } + + shape = KeyframeGroup(getObject(json, "pt")); + + if (const auto invertedData = getOptionalBool(json, "inv")) { + inverted = invertedData.value(); + } + + if (const auto expansionData = getOptionalObject(json, "x")) { + expansion = KeyframeGroup(expansionData.value()); + } + + name = getOptionalString(json, "nm"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + if (_mode.has_value()) { + switch (_mode.value()) { + case MaskMode::Add: + result.insert(std::make_pair("mode", "a")); + break; + case MaskMode::Subtract: + result.insert(std::make_pair("mode", "s")); + break; + case MaskMode::Intersect: + result.insert(std::make_pair("mode", "i")); + break; + case MaskMode::Lighten: + result.insert(std::make_pair("mode", "l")); + break; + case MaskMode::Darken: + result.insert(std::make_pair("mode", "d")); + break; + case MaskMode::Difference: + result.insert(std::make_pair("mode", "f")); + break; + case MaskMode::None: + result.insert(std::make_pair("mode", "n")); + break; + } + } + + if (opacity.has_value()) { + result.insert(std::make_pair("o", opacity->toJson())); + } + + result.insert(std::make_pair("pt", shape.toJson())); + + if (inverted.has_value()) { + result.insert(std::make_pair("inv", inverted.value())); + } + + if (expansion.has_value()) { + result.insert(std::make_pair("x", expansion->toJson())); + } + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + + return result; + } + +public: + MaskMode mode() const { + if (_mode.has_value()) { + return _mode.value(); + } else { + return MaskMode::Add; + } + } + +public: + std::optional _mode; + + std::optional> opacity; + + KeyframeGroup shape; + + std::optional inverted; + + std::optional> expansion; + + std::optional name; +}; + +} + +#endif /* Mask_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Mask.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Mask.swift new file mode 100644 index 00000000000..a3c6ce4a629 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Mask.swift @@ -0,0 +1,80 @@ +// +// Mask.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +// MARK: - MaskMode + +enum MaskMode: String, Codable { + case add = "a" + case subtract = "s" + case intersect = "i" + case lighten = "l" + case darken = "d" + case difference = "f" + case none = "n" +} + +// MARK: - Mask + +final class Mask: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Mask.CodingKeys.self) + mode = try container.decodeIfPresent(MaskMode.self, forKey: .mode) ?? .add + opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) + shape = try container.decode(KeyframeGroup.self, forKey: .shape) + inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false + expansion = try container.decodeIfPresent(KeyframeGroup.self, forKey: .expansion) ?? KeyframeGroup(Vector1D(0)) + } + + init(dictionary: [String: Any]) throws { + if + let modeRawType = dictionary[CodingKeys.mode.rawValue] as? String, + let mode = MaskMode(rawValue: modeRawType) + { + self.mode = mode + } else { + mode = .add + } + if let opacityDictionary = dictionary[CodingKeys.opacity.rawValue] as? [String: Any] { + opacity = try KeyframeGroup(dictionary: opacityDictionary) + } else { + opacity = KeyframeGroup(Vector1D(100)) + } + let shapeDictionary: [String: Any] = try dictionary.value(for: CodingKeys.shape) + shape = try KeyframeGroup(dictionary: shapeDictionary) + inverted = (try? dictionary.value(for: CodingKeys.inverted)) ?? false + if let expansionDictionary = dictionary[CodingKeys.expansion.rawValue] as? [String: Any] { + expansion = try KeyframeGroup(dictionary: expansionDictionary) + } else { + expansion = KeyframeGroup(Vector1D(0)) + } + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case mode + case opacity = "o" + case inverted = "inv" + case shape = "pt" + case expansion = "x" + } + + let mode: MaskMode + + let opacity: KeyframeGroup + + let shape: KeyframeGroup + + let inverted: Bool + + let expansion: KeyframeGroup +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Transform.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Transform.cpp new file mode 100644 index 00000000000..e55154304fc --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Transform.cpp @@ -0,0 +1,5 @@ +#include "Transform.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Transform.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Transform.hpp new file mode 100644 index 00000000000..d3bfa4471e7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Transform.hpp @@ -0,0 +1,267 @@ +#ifndef Transform_hpp +#define Transform_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class Transform { +public: + enum class PositionInternalRepresentation { + None, + TopLevelXY, + TopLevelCombined, + NestedXY + }; + + enum class RotationZInternalRepresentation { + RZ, + R + }; + +public: + Transform( + std::optional> anchorPoint_, + std::optional> position_, + std::optional> positionX_, + std::optional> positionY_, + std::optional> scale_, + std::optional> rotation_, + std::optional> &opacity_, + std::optional> rotationZ_ + ) : + _anchorPoint(anchorPoint_), + _position(position_), + _positionX(positionX_), + _positionY(positionY_), + _scale(scale_), + _rotation(rotation_), + _opacity(opacity_), + _rotationZ(rotationZ_) { + } + + explicit Transform(json11::Json::object const &json) noexcept(false) { + // AnchorPoint + if (const auto anchorPointDictionary = getOptionalObject(json, "a")) { + _anchorPoint = KeyframeGroup(anchorPointDictionary.value()); + } + + try { + auto xDictionary = getOptionalObject(json, "px"); + auto yDictionary = getOptionalObject(json, "py"); + if (xDictionary.has_value() && yDictionary.has_value()) { + _positionX = KeyframeGroup(xDictionary.value()); + _positionY = KeyframeGroup(yDictionary.value()); + _position = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::TopLevelXY; + } else if (const auto positionData = getOptionalObject(json, "p")) { + try { + LottieParsingException::Guard expectedGuard; + + _position = KeyframeGroup(positionData.value()); + _positionX = std::nullopt; + _positionX = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::TopLevelCombined; + } catch(...) { + auto xData = getObject(positionData.value(), "x"); + auto yData = getObject(positionData.value(), "y"); + _positionX = KeyframeGroup(xData); + _positionY = KeyframeGroup(yData); + _position = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::NestedXY; + _extra_positionS = getOptionalBool(positionData.value(), "s"); + } + } else { + _position = std::nullopt; + _positionX = std::nullopt; + _positionY = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::None; + } + } catch(...) { + throw LottieParsingException(); + } + + try { + // Scale + if (const auto scaleData = getOptionalObject(json, "s")) { + _scale = KeyframeGroup(scaleData.value()); + } + + // Rotation + if (const auto rotationZData = getOptionalObject(json, "rz")) { + _rotationZ = KeyframeGroup(rotationZData.value()); + _rotationZInternalRepresentation = RotationZInternalRepresentation::RZ; + } else if (const auto rotationData = getOptionalObject(json, "r")) { + _rotation = KeyframeGroup(rotationData.value()); + _rotationZInternalRepresentation = RotationZInternalRepresentation::R; + } + + // Opacity + if (const auto opacityData = getOptionalObject(json, "o")) { + _opacity = KeyframeGroup(opacityData.value()); + } + } catch(...) { + throw LottieParsingException(); + } + + _extraTy = getOptionalString(json, "ty"); + _extraSa = getOptionalAny(json, "sa"); + _extraSk = getOptionalAny(json, "sk"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + if (_anchorPoint.has_value()) { + result.insert(std::make_pair("a", _anchorPoint->toJson())); + } + + switch (_positionInternalRepresentation) { + case PositionInternalRepresentation::None: + assert(!_positionX.has_value()); + assert(!_positionY.has_value()); + assert(!_position.has_value()); + break; + case PositionInternalRepresentation::TopLevelXY: + assert(_positionX.has_value()); + assert(_positionY.has_value()); + assert(!_position.has_value()); + result.insert(std::make_pair("x", _positionX->toJson())); + result.insert(std::make_pair("y", _positionY->toJson())); + break; + case PositionInternalRepresentation::TopLevelCombined: + assert(_position.has_value()); + assert(!_positionX.has_value()); + assert(!_positionY.has_value()); + result.insert(std::make_pair("p", _position->toJson())); + break; + case PositionInternalRepresentation::NestedXY: + json11::Json::object nestedPosition; + assert(_positionX.has_value()); + assert(_positionY.has_value()); + assert(!_position.has_value()); + nestedPosition.insert(std::make_pair("x", _positionX->toJson())); + nestedPosition.insert(std::make_pair("y", _positionY->toJson())); + if (_extra_positionS.has_value()) { + nestedPosition.insert(std::make_pair("s", _extra_positionS.value())); + } + result.insert(std::make_pair("p", nestedPosition)); + break; + } + + if (_scale.has_value()) { + result.insert(std::make_pair("s", _scale->toJson())); + } + + if (_rotation.has_value()) { + switch (_rotationZInternalRepresentation) { + case RotationZInternalRepresentation::RZ: + result.insert(std::make_pair("rz", _rotation->toJson())); + break; + case RotationZInternalRepresentation::R: + result.insert(std::make_pair("r", _rotation->toJson())); + break; + } + } + + if (_opacity.has_value()) { + result.insert(std::make_pair("o", _opacity->toJson())); + } + + if (_extraTy.has_value()) { + result.insert(std::make_pair("ty", _extraTy.value())); + } + if (_extraSa.has_value()) { + result.insert(std::make_pair("sa", _extraSa.value())); + } + if (_extraSk.has_value()) { + result.insert(std::make_pair("sk", _extraSk.value())); + } + + return result; + } + + KeyframeGroup anchorPoint() { + if (_anchorPoint.has_value()) { + return _anchorPoint.value(); + } else { + return KeyframeGroup(Vector3D(0.0, 0.0, 0.0)); + } + } + + KeyframeGroup scale() const { + if (_scale) { + return _scale.value(); + } else { + return KeyframeGroup(Vector3D(100.0, 100.0, 100.0)); + } + } + + KeyframeGroup rotation() const { + if (_rotation) { + return _rotation.value(); + } else { + return KeyframeGroup(Vector1D(0.0)); + } + } + + KeyframeGroup opacity() const { + if (_opacity) { + return _opacity.value(); + } else { + return KeyframeGroup(Vector1D(100.0)); + } + } + + std::optional> const &position() const { + return _position; + } + + std::optional> const &positionX() const { + return _positionX; + } + + std::optional> const &positionY() const { + return _positionY; + } + +private: + /// The anchor point of the transform. + std::optional> _anchorPoint; + + /// The position of the transform. This is nil if the position data was split. + std::optional> _position; + + /// The positionX of the transform. This is nil if the position property is set. + std::optional> _positionX; + + /// The positionY of the transform. This is nil if the position property is set. + std::optional> _positionY; + + PositionInternalRepresentation _positionInternalRepresentation = PositionInternalRepresentation::None; + + /// The scale of the transform + std::optional> _scale; + + /// The rotation of the transform. Note: This is single dimensional rotation. + std::optional> _rotation; + + /// The opacity of the transform. + std::optional> _opacity; + + /// Should always be nil. + std::optional> _rotationZ; + RotationZInternalRepresentation _rotationZInternalRepresentation; + + std::optional _extra_positionS; + std::optional _extraTy; + std::optional _extraSa; + std::optional _extraSk; +}; + +} + +#endif /* Transform_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Transform.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Transform.swift new file mode 100644 index 00000000000..c67bc2caae1 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Objects/Transform.swift @@ -0,0 +1,179 @@ +// +// Transform.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import Foundation + +/// The animatable transform for a layer. Controls position, rotation, scale, and opacity. +final class Transform: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + /// This manual override of decode is required because we want to throw an error + /// in the case that there is not position data. + let container = try decoder.container(keyedBy: Transform.CodingKeys.self) + + // AnchorPoint + anchorPoint = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .anchorPoint) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + + // Position + if container.contains(.positionX), container.contains(.positionY) { + // Position dimensions are split into two keyframe groups + positionX = try container.decode(KeyframeGroup.self, forKey: .positionX) + positionY = try container.decode(KeyframeGroup.self, forKey: .positionY) + position = nil + } else if let positionKeyframes = try? container.decode(KeyframeGroup.self, forKey: .position) { + // Position dimensions are a single keyframe group. + position = positionKeyframes + positionX = nil + positionY = nil + } else if + let positionContainer = try? container.nestedContainer(keyedBy: PositionCodingKeys.self, forKey: .position), + let positionX = try? positionContainer.decode(KeyframeGroup.self, forKey: .positionX), + let positionY = try? positionContainer.decode(KeyframeGroup.self, forKey: .positionY) + { + /// Position keyframes are split and nested. + self.positionX = positionX + self.positionY = positionY + position = nil + } else { + /// Default value. + position = KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + positionX = nil + positionY = nil + } + + // Scale + scale = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) + + // Rotation + if let rotationZ = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotationZ) { + rotation = rotationZ + } else { + rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) + } + rotationZ = nil + + // Opacity + opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) + } + + init(dictionary: [String: Any]) throws { + if + let anchorPointDictionary = dictionary[CodingKeys.anchorPoint.rawValue] as? [String: Any], + let anchorPoint = try? KeyframeGroup(dictionary: anchorPointDictionary) + { + self.anchorPoint = anchorPoint + } else { + anchorPoint = KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + } + + if + let xDictionary = dictionary[CodingKeys.positionX.rawValue] as? [String: Any], + let yDictionary = dictionary[CodingKeys.positionY.rawValue] as? [String: Any] + { + positionX = try KeyframeGroup(dictionary: xDictionary) + positionY = try KeyframeGroup(dictionary: yDictionary) + position = nil + } else if + let positionDictionary = dictionary[CodingKeys.position.rawValue] as? [String: Any], + positionDictionary[KeyframeGroup.KeyframeWrapperKey.keyframeData.rawValue] != nil + { + position = try KeyframeGroup(dictionary: positionDictionary) + positionX = nil + positionY = nil + } else if + let positionDictionary = dictionary[CodingKeys.position.rawValue] as? [String: Any], + let xDictionary = positionDictionary[PositionCodingKeys.positionX.rawValue] as? [String: Any], + let yDictionary = positionDictionary[PositionCodingKeys.positionY.rawValue] as? [String: Any] + { + positionX = try KeyframeGroup(dictionary: xDictionary) + positionY = try KeyframeGroup(dictionary: yDictionary) + position = nil + } else { + position = KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + positionX = nil + positionY = nil + } + + if + let scaleDictionary = dictionary[CodingKeys.scale.rawValue] as? [String: Any], + let scale = try? KeyframeGroup(dictionary: scaleDictionary) + { + self.scale = scale + } else { + scale = KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) + } + if + let rotationDictionary = dictionary[CodingKeys.rotationZ.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + self.rotation = rotation + } else if + let rotationDictionary = dictionary[CodingKeys.rotation.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + self.rotation = rotation + } else { + rotation = KeyframeGroup(Vector1D(0)) + } + rotationZ = nil + if + let opacityDictionary = dictionary[CodingKeys.opacity.rawValue] as? [String: Any], + let opacity = try? KeyframeGroup(dictionary: opacityDictionary) + { + self.opacity = opacity + } else { + opacity = KeyframeGroup(Vector1D(100)) + } + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case anchorPoint = "a" + case position = "p" + case positionX = "px" + case positionY = "py" + case scale = "s" + case rotation = "r" + case rotationZ = "rz" + case opacity = "o" + } + + enum PositionCodingKeys: String, CodingKey { + case split = "s" + case positionX = "x" + case positionY = "y" + } + + /// The anchor point of the transform. + let anchorPoint: KeyframeGroup + + /// The position of the transform. This is nil if the position data was split. + let position: KeyframeGroup? + + /// The positionX of the transform. This is nil if the position property is set. + let positionX: KeyframeGroup? + + /// The positionY of the transform. This is nil if the position property is set. + let positionY: KeyframeGroup? + + /// The scale of the transform + let scale: KeyframeGroup + + /// The rotation of the transform. Note: This is single dimensional rotation. + let rotation: KeyframeGroup + + /// The opacity of the transform. + let opacity: KeyframeGroup + + /// Should always be nil. + let rotationZ: KeyframeGroup? +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Ellipse.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Ellipse.cpp new file mode 100644 index 00000000000..9378276f24f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Ellipse.cpp @@ -0,0 +1,5 @@ +#include "Ellipse.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Ellipse.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Ellipse.hpp new file mode 100644 index 00000000000..51f1c1879d7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Ellipse.hpp @@ -0,0 +1,62 @@ +#ifndef Ellipse_hpp +#define Ellipse_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class PathDirection: int { + Clockwise = 1, + UserSetClockwise = 2, + CounterClockwise = 3 +}; + +/// An item that define an ellipse shape +class Ellipse: public ShapeItem { +public: + explicit Ellipse(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + size(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + position = KeyframeGroup(getObject(json, "p")); + size = KeyframeGroup(getObject(json, "s")); + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + json.insert(std::make_pair("p", position.toJson())); + json.insert(std::make_pair("s", size.toJson())); + } + +public: + std::optional direction; + KeyframeGroup position; + KeyframeGroup size; +}; + +} + +#endif /* Ellipse_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Ellipse.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Ellipse.swift new file mode 100644 index 00000000000..0ce70da75fc --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Ellipse.swift @@ -0,0 +1,75 @@ +// +// EllipseItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +// MARK: - PathDirection + +enum PathDirection: Int, Codable { + case clockwise = 1 + case userSetClockwise = 2 + case counterClockwise = 3 +} + +// MARK: - Ellipse + +/// An item that define an ellipse shape +final class Ellipse: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Ellipse.CodingKeys.self) + direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise + position = try container.decode(KeyframeGroup.self, forKey: .position) + size = try container.decode(KeyframeGroup.self, forKey: .size) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + if + let directionRawType = dictionary[CodingKeys.direction.rawValue] as? Int, + let direction = PathDirection(rawValue: directionRawType) + { + self.direction = direction + } else { + direction = .clockwise + } + let positionDictionary: [String: Any] = try dictionary.value(for: CodingKeys.position) + position = try KeyframeGroup(dictionary: positionDictionary) + let sizeDictionary: [String: Any] = try dictionary.value(for: CodingKeys.size) + size = try KeyframeGroup(dictionary: sizeDictionary) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The direction of the ellipse. + let direction: PathDirection + + /// The position of the ellipse + let position: KeyframeGroup + + /// The size of the ellipse + let size: KeyframeGroup + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(direction, forKey: .direction) + try container.encode(position, forKey: .position) + try container.encode(size, forKey: .size) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case direction = "d" + case position = "p" + case size = "s" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Fill.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Fill.cpp new file mode 100644 index 00000000000..992a25a5be2 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Fill.cpp @@ -0,0 +1,6 @@ +#include "Fill.hpp" + +namespace lottie { + +} + diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Fill.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Fill.hpp new file mode 100644 index 00000000000..3e89d2a8c8a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Fill.hpp @@ -0,0 +1,74 @@ +#ifndef Fill_hpp +#define Fill_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class FillRule: int { + None = 0, + NonZeroWinding = 1, + EvenOdd = 2 +}; + +class Fill: public ShapeItem { +public: + explicit Fill(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(0.0))), + color(KeyframeGroup(Color(0.0, 0.0, 0.0, 0.0))) { + opacity = KeyframeGroup(getObject(json, "o")); + color = KeyframeGroup(getObject(json, "c")); + + if (const auto fillRuleRawValue = getOptionalInt(json, "r")) { + switch (fillRuleRawValue.value()) { + case 0: + fillRule = FillRule::None; + break; + case 1: + fillRule = FillRule::NonZeroWinding; + break; + case 2: + fillRule = FillRule::EvenOdd; + break; + default: + throw LottieParsingException(); + } + } + + fillEnabled = getOptionalBool(json, "fillEnabled"); + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("c", color.toJson())); + + if (fillRule.has_value()) { + json.insert(std::make_pair("r", (int)fillRule.value())); + } + + if (fillEnabled.has_value()) { + json.insert(std::make_pair("fillEnabled", fillEnabled.value())); + } + } + +public: + KeyframeGroup opacity; + + /// The color keyframes for the fill + KeyframeGroup color; + + std::optional fillRule; + + std::optional fillEnabled; +}; + +} + +#endif /* Fill_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Fill.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Fill.swift new file mode 100644 index 00000000000..c158ae45ae4 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Fill.swift @@ -0,0 +1,74 @@ +// +// FillShape.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +// MARK: - FillRule + +enum FillRule: Int, Codable { + case none + case nonZeroWinding + case evenOdd +} + +// MARK: - Fill + +/// An item that defines a fill render +final class Fill: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Fill.CodingKeys.self) + opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + color = try container.decode(KeyframeGroup.self, forKey: .color) + fillRule = try container.decodeIfPresent(FillRule.self, forKey: .fillRule) ?? .nonZeroWinding + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let opacityDictionary: [String: Any] = try dictionary.value(for: CodingKeys.opacity) + opacity = try KeyframeGroup(dictionary: opacityDictionary) + let colorDictionary: [String: Any] = try dictionary.value(for: CodingKeys.color) + color = try KeyframeGroup(dictionary: colorDictionary) + if + let fillRuleRawValue = dictionary[CodingKeys.fillRule.rawValue] as? Int, + let fillRule = FillRule(rawValue: fillRuleRawValue) + { + self.fillRule = fillRule + } else { + fillRule = .nonZeroWinding + } + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The opacity of the fill + let opacity: KeyframeGroup + + /// The color keyframes for the fill + let color: KeyframeGroup + + let fillRule: FillRule + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(color, forKey: .color) + try container.encode(fillRule, forKey: .fillRule) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case opacity = "o" + case color = "c" + case fillRule = "r" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientFill.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientFill.cpp new file mode 100644 index 00000000000..f2ed96d1b9e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientFill.cpp @@ -0,0 +1,5 @@ +#include "GradientFill.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientFill.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientFill.hpp new file mode 100644 index 00000000000..58711c2cbe0 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientFill.hpp @@ -0,0 +1,116 @@ +#ifndef GradientFill_hpp +#define GradientFill_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Primitives/GradientColorSet.hpp" + +namespace lottie { + +enum class GradientType: int { + None = 0, + Linear = 1, + Radial = 2 +}; + +/// An item that define a gradient fill +class GradientFill: public ShapeItem { +public: + explicit GradientFill(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(100.0))), + startPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + endPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + gradientType(GradientType::None), + numberOfColors(0), + colors(KeyframeGroup(GradientColorSet())) { + opacity = KeyframeGroup(getObject(json, "o")); + startPoint = KeyframeGroup(getObject(json, "s")); + endPoint = KeyframeGroup(getObject(json, "e")); + + auto gradientTypeRawValue = getInt(json, "t"); + switch (gradientTypeRawValue) { + case 0: + gradientType = GradientType::None; + break; + case 1: + gradientType = GradientType::Linear; + break; + case 2: + gradientType = GradientType::Radial; + break; + default: + throw LottieParsingException(); + } + + if (const auto highlightLengthData = getOptionalObject(json, "h")) { + highlightLength = KeyframeGroup(highlightLengthData.value()); + } + if (const auto highlightAngleData = getOptionalObject(json, "a")) { + highlightAngle = KeyframeGroup(highlightAngleData.value()); + } + + auto colorsContainer = getObject(json, "g"); + numberOfColors = getInt(colorsContainer, "p"); + colors = KeyframeGroup(getObject(colorsContainer, "k")); + + rValue = getOptionalInt(json, "r"); + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("s", startPoint.toJson())); + json.insert(std::make_pair("e", endPoint.toJson())); + json.insert(std::make_pair("t", (int)gradientType)); + + if (highlightLength.has_value()) { + json.insert(std::make_pair("h", highlightLength->toJson())); + } + if (highlightAngle.has_value()) { + json.insert(std::make_pair("a", highlightAngle->toJson())); + } + + json11::Json::object colorsContainer; + colorsContainer.insert(std::make_pair("p", numberOfColors)); + colorsContainer.insert(std::make_pair("k", colors.toJson())); + json.insert(std::make_pair("g", colorsContainer)); + + if (rValue.has_value()) { + json.insert(std::make_pair("r", rValue.value())); + } + } + +public: + /// The opacity of the fill + KeyframeGroup opacity; + + /// The start of the gradient + KeyframeGroup startPoint; + + /// The end of the gradient + KeyframeGroup endPoint; + + /// The type of gradient + GradientType gradientType; + + /// Gradient Highlight Length. Only if type is Radial + std::optional> highlightLength; + + /// Highlight Angle. Only if type is Radial + std::optional> highlightAngle; + + /// The number of color points in the gradient + int numberOfColors; + + /// The Colors of the gradient. + KeyframeGroup colors; + + std::optional rValue; +}; + +} + +#endif /* GradientFill_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientFill.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientFill.swift new file mode 100644 index 00000000000..426d101a89b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientFill.swift @@ -0,0 +1,124 @@ +// +// GradientFill.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +// MARK: - GradientType + +enum GradientType: Int, Codable { + case none + case linear + case radial +} + +// MARK: - GradientFill + +/// An item that define a gradient fill +final class GradientFill: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: GradientFill.CodingKeys.self) + opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + startPoint = try container.decode(KeyframeGroup.self, forKey: .startPoint) + endPoint = try container.decode(KeyframeGroup.self, forKey: .endPoint) + gradientType = try container.decode(GradientType.self, forKey: .gradientType) + highlightLength = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightLength) + highlightAngle = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightAngle) + let colorsContainer = try container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + colors = try colorsContainer.decode(KeyframeGroup<[Double]>.self, forKey: .colors) + numberOfColors = try colorsContainer.decode(Int.self, forKey: .numberOfColors) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let opacityDictionary: [String: Any] = try dictionary.value(for: CodingKeys.opacity) + opacity = try KeyframeGroup(dictionary: opacityDictionary) + let startPointDictionary: [String: Any] = try dictionary.value(for: CodingKeys.startPoint) + startPoint = try KeyframeGroup(dictionary: startPointDictionary) + let endPointDictionary: [String: Any] = try dictionary.value(for: CodingKeys.endPoint) + endPoint = try KeyframeGroup(dictionary: endPointDictionary) + let gradientRawType: Int = try dictionary.value(for: CodingKeys.gradientType) + guard let gradient = GradientType(rawValue: gradientRawType) else { + throw InitializableError.invalidInput + } + gradientType = gradient + if let highlightLengthDictionary = dictionary[CodingKeys.highlightLength.rawValue] as? [String: Any] { + highlightLength = try? KeyframeGroup(dictionary: highlightLengthDictionary) + } else { + highlightLength = nil + } + if let highlightAngleDictionary = dictionary[CodingKeys.highlightAngle.rawValue] as? [String: Any] { + highlightAngle = try? KeyframeGroup(dictionary: highlightAngleDictionary) + } else { + highlightAngle = nil + } + let colorsDictionary: [String: Any] = try dictionary.value(for: CodingKeys.colors) + let nestedColorsDictionary: [String: Any] = try colorsDictionary.value(for: GradientDataKeys.colors) + colors = try KeyframeGroup<[Double]>(dictionary: nestedColorsDictionary) + numberOfColors = try colorsDictionary.value(for: GradientDataKeys.numberOfColors) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The opacity of the fill + let opacity: KeyframeGroup + + /// The start of the gradient + let startPoint: KeyframeGroup + + /// The end of the gradient + let endPoint: KeyframeGroup + + /// The type of gradient + let gradientType: GradientType + + /// Gradient Highlight Length. Only if type is Radial + let highlightLength: KeyframeGroup? + + /// Highlight Angle. Only if type is Radial + let highlightAngle: KeyframeGroup? + + /// The number of color points in the gradient + let numberOfColors: Int + + /// The Colors of the gradient. + let colors: KeyframeGroup<[Double]> + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(startPoint, forKey: .startPoint) + try container.encode(endPoint, forKey: .endPoint) + try container.encode(gradientType, forKey: .gradientType) + try container.encodeIfPresent(highlightLength, forKey: .highlightLength) + try container.encodeIfPresent(highlightAngle, forKey: .highlightAngle) + var colorsContainer = container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + try colorsContainer.encode(numberOfColors, forKey: .numberOfColors) + try colorsContainer.encode(colors, forKey: .colors) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case opacity = "o" + case startPoint = "s" + case endPoint = "e" + case gradientType = "t" + case highlightLength = "h" + case highlightAngle = "a" + case colors = "g" + } + + private enum GradientDataKeys: String, CodingKey { + case numberOfColors = "p" + case colors = "k" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientStroke.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientStroke.cpp new file mode 100644 index 00000000000..465b20fde88 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientStroke.cpp @@ -0,0 +1,5 @@ +#include "GradientStroke.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientStroke.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientStroke.hpp new file mode 100644 index 00000000000..ba5fc18f449 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientStroke.hpp @@ -0,0 +1,191 @@ +#ifndef GradientStroke_hpp +#define GradientStroke_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/Objects/DashElement.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Primitives/DrawingAttributes.hpp" + +namespace lottie { + +/// An item that define a gradient stroke +class GradientStroke: public ShapeItem { +public: + explicit GradientStroke(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(100.0))), + startPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + endPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + gradientType(GradientType::None), + numberOfColors(0), + colors(KeyframeGroup(GradientColorSet())), + width(KeyframeGroup(Vector1D(0.0))), + lineCap(LineCap::Round), + lineJoin(LineJoin::Round) { + opacity = KeyframeGroup(getObject(json, "o")); + startPoint = KeyframeGroup(getObject(json, "s")); + endPoint = KeyframeGroup(getObject(json, "e")); + + auto gradientTypeRawValue = getInt(json, "t"); + switch (gradientTypeRawValue) { + case 0: + gradientType = GradientType::None; + break; + case 1: + gradientType = GradientType::Linear; + break; + case 2: + gradientType = GradientType::Radial; + break; + default: + throw LottieParsingException(); + } + + if (const auto highlightLengthData = getOptionalObject(json, "h")) { + highlightLength = KeyframeGroup(highlightLengthData.value()); + } + if (const auto highlightAngleData = getOptionalObject(json, "a")) { + highlightAngle = KeyframeGroup(highlightAngleData.value()); + } + + width = KeyframeGroup(getObject(json, "w")); + + if (const auto lineCapRawValue = getOptionalInt(json, "lc")) { + switch (lineCapRawValue.value()) { + case 0: + lineCap = LineCap::None; + break; + case 1: + lineCap = LineCap::Butt; + break; + case 2: + lineCap = LineCap::Round; + break; + case 3: + lineCap = LineCap::Square; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto lineJoinRawValue = getOptionalInt(json, "lj")) { + switch (lineJoinRawValue.value()) { + case 0: + lineJoin = LineJoin::None; + break; + case 1: + lineJoin = LineJoin::Miter; + break; + case 2: + lineJoin = LineJoin::Round; + break; + case 3: + lineJoin = LineJoin::Bevel; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto miterLimitData = getOptionalDouble(json, "ml")) { + miterLimit = miterLimitData.value(); + } + + auto colorsContainer = getObject(json, "g"); + numberOfColors = getInt(colorsContainer, "p"); + auto colorsData = getObject(colorsContainer, "k"); + colors = KeyframeGroup(colorsData); + + if (const auto dashElementsData = getOptionalObjectArray(json, "d")) { + dashPattern = std::vector(); + for (const auto &dashElementData : dashElementsData.value()) { + dashPattern->push_back(DashElement(dashElementData)); + } + } + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("s", startPoint.toJson())); + json.insert(std::make_pair("e", endPoint.toJson())); + json.insert(std::make_pair("t", (int)gradientType)); + + if (highlightLength.has_value()) { + json.insert(std::make_pair("h", highlightLength->toJson())); + } + if (highlightAngle.has_value()) { + json.insert(std::make_pair("a", highlightAngle->toJson())); + } + + json.insert(std::make_pair("w", width.toJson())); + + json.insert(std::make_pair("lc", (int)lineCap)); + json.insert(std::make_pair("lj", (int)lineJoin)); + + if (miterLimit.has_value()) { + json.insert(std::make_pair("ml", miterLimit.value())); + } + + json11::Json::object colorsContainer; + colorsContainer.insert(std::make_pair("p", numberOfColors)); + colorsContainer.insert(std::make_pair("k", colors.toJson())); + json.insert(std::make_pair("g", colorsContainer)); + + if (dashPattern.has_value()) { + json11::Json::array dashElements; + for (const auto &dashElement : dashPattern.value()) { + dashElements.push_back(dashElement.toJson()); + } + json.insert(std::make_pair("d", dashElements)); + } + } + +public: + /// The opacity of the fill + KeyframeGroup opacity; + + /// The start of the gradient + KeyframeGroup startPoint; + + /// The end of the gradient + KeyframeGroup endPoint; + + /// The type of gradient + GradientType gradientType; + + /// Gradient Highlight Length. Only if type is Radial + std::optional> highlightLength; + + /// Highlight Angle. Only if type is Radial + std::optional> highlightAngle; + + /// The number of color points in the gradient + int numberOfColors; + + /// The Colors of the gradient. + KeyframeGroup colors; + + /// The width of the stroke + KeyframeGroup width; + + /// Line Cap + LineCap lineCap; + + /// Line Join + LineJoin lineJoin; + + /// Miter Limit + std::optional miterLimit; + + /// The dash pattern of the stroke + std::optional> dashPattern; +}; + +} + +#endif /* GradientStroke_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientStroke.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientStroke.swift new file mode 100644 index 00000000000..90d60eef873 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/GradientStroke.swift @@ -0,0 +1,186 @@ +// +// GradientStroke.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +// MARK: - LineCap + +enum LineCap: Int, Codable { + case none + case butt + case round + case square +} + +// MARK: - LineJoin + +enum LineJoin: Int, Codable { + case none + case miter + case round + case bevel +} + +// MARK: - GradientStroke + +/// An item that define an ellipse shape +final class GradientStroke: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: GradientStroke.CodingKeys.self) + opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + startPoint = try container.decode(KeyframeGroup.self, forKey: .startPoint) + endPoint = try container.decode(KeyframeGroup.self, forKey: .endPoint) + gradientType = try container.decode(GradientType.self, forKey: .gradientType) + highlightLength = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightLength) + highlightAngle = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightAngle) + width = try container.decode(KeyframeGroup.self, forKey: .width) + lineCap = try container.decodeIfPresent(LineCap.self, forKey: .lineCap) ?? .round + lineJoin = try container.decodeIfPresent(LineJoin.self, forKey: .lineJoin) ?? .round + miterLimit = try container.decodeIfPresent(Double.self, forKey: .miterLimit) ?? 4 + // TODO Decode Color Objects instead of array. + let colorsContainer = try container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + colors = try colorsContainer.decode(KeyframeGroup<[Double]>.self, forKey: .colors) + numberOfColors = try colorsContainer.decode(Int.self, forKey: .numberOfColors) + dashPattern = try container.decodeIfPresent([DashElement].self, forKey: .dashPattern) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let opacityDictionary: [String: Any] = try dictionary.value(for: CodingKeys.opacity) + opacity = try KeyframeGroup(dictionary: opacityDictionary) + let startPointDictionary: [String: Any] = try dictionary.value(for: CodingKeys.startPoint) + startPoint = try KeyframeGroup(dictionary: startPointDictionary) + let endPointDictionary: [String: Any] = try dictionary.value(for: CodingKeys.endPoint) + endPoint = try KeyframeGroup(dictionary: endPointDictionary) + let gradientRawType: Int = try dictionary.value(for: CodingKeys.gradientType) + guard let gradient = GradientType(rawValue: gradientRawType) else { + throw InitializableError.invalidInput + } + gradientType = gradient + if let highlightLengthDictionary = dictionary[CodingKeys.highlightLength.rawValue] as? [String: Any] { + highlightLength = try? KeyframeGroup(dictionary: highlightLengthDictionary) + } else { + highlightLength = nil + } + if let highlightAngleDictionary = dictionary[CodingKeys.highlightAngle.rawValue] as? [String: Any] { + highlightAngle = try? KeyframeGroup(dictionary: highlightAngleDictionary) + } else { + highlightAngle = nil + } + let widthDictionary: [String: Any] = try dictionary.value(for: CodingKeys.width) + width = try KeyframeGroup(dictionary: widthDictionary) + if + let lineCapRawValue = dictionary[CodingKeys.lineCap.rawValue] as? Int, + let lineCap = LineCap(rawValue: lineCapRawValue) + { + self.lineCap = lineCap + } else { + lineCap = .round + } + if + let lineJoinRawValue = dictionary[CodingKeys.lineJoin.rawValue] as? Int, + let lineJoin = LineJoin(rawValue: lineJoinRawValue) + { + self.lineJoin = lineJoin + } else { + lineJoin = .round + } + miterLimit = (try? dictionary.value(for: CodingKeys.miterLimit)) ?? 4 + let colorsDictionary: [String: Any] = try dictionary.value(for: CodingKeys.colors) + let nestedColorsDictionary: [String: Any] = try colorsDictionary.value(for: GradientDataKeys.colors) + colors = try KeyframeGroup<[Double]>(dictionary: nestedColorsDictionary) + numberOfColors = try colorsDictionary.value(for: GradientDataKeys.numberOfColors) + let dashPatternDictionaries = dictionary[CodingKeys.dashPattern.rawValue] as? [[String: Any]] + dashPattern = try? dashPatternDictionaries?.map({ try DashElement(dictionary: $0) }) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The opacity of the fill + let opacity: KeyframeGroup + + /// The start of the gradient + let startPoint: KeyframeGroup + + /// The end of the gradient + let endPoint: KeyframeGroup + + /// The type of gradient + let gradientType: GradientType + + /// Gradient Highlight Length. Only if type is Radial + let highlightLength: KeyframeGroup? + + /// Highlight Angle. Only if type is Radial + let highlightAngle: KeyframeGroup? + + /// The number of color points in the gradient + let numberOfColors: Int + + /// The Colors of the gradient. + let colors: KeyframeGroup<[Double]> + + /// The width of the stroke + let width: KeyframeGroup + + /// Line Cap + let lineCap: LineCap + + /// Line Join + let lineJoin: LineJoin + + /// Miter Limit + let miterLimit: Double + + /// The dash pattern of the stroke + let dashPattern: [DashElement]? + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(startPoint, forKey: .startPoint) + try container.encode(endPoint, forKey: .endPoint) + try container.encode(gradientType, forKey: .gradientType) + try container.encodeIfPresent(highlightLength, forKey: .highlightLength) + try container.encodeIfPresent(highlightAngle, forKey: .highlightAngle) + try container.encode(width, forKey: .width) + try container.encode(lineCap, forKey: .lineCap) + try container.encode(lineJoin, forKey: .lineJoin) + try container.encode(miterLimit, forKey: .miterLimit) + var colorsContainer = container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + try colorsContainer.encode(numberOfColors, forKey: .numberOfColors) + try colorsContainer.encode(colors, forKey: .colors) + try container.encodeIfPresent(dashPattern, forKey: .dashPattern) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case opacity = "o" + case startPoint = "s" + case endPoint = "e" + case gradientType = "t" + case highlightLength = "h" + case highlightAngle = "a" + case colors = "g" + case width = "w" + case lineCap = "lc" + case lineJoin = "lj" + case miterLimit = "ml" + case dashPattern = "d" + } + + private enum GradientDataKeys: String, CodingKey { + case numberOfColors = "p" + case colors = "k" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Group.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Group.cpp new file mode 100644 index 00000000000..89ea7daea73 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Group.cpp @@ -0,0 +1,5 @@ +#include "Group.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Group.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Group.hpp new file mode 100644 index 00000000000..cde67f30d87 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Group.hpp @@ -0,0 +1,51 @@ +#ifndef Group_hpp +#define Group_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +/// An item that define an ellipse shape +class Group: public ShapeItem { +public: + explicit Group(json11::Json::object const &json) noexcept(false) : + ShapeItem(json) { + auto itemsData = getObjectArray(json, "it"); + for (const auto &itemData : itemsData) { + items.push_back(parseShapeItem(itemData)); + } + + numberOfProperties = getOptionalInt(json, "np"); + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json11::Json::array itemArray; + for (const auto &item : items) { + json11::Json::object itemJson; + item->toJson(itemJson); + itemArray.push_back(itemJson); + } + + json.insert(std::make_pair("it", itemArray)); + + if (numberOfProperties.has_value()) { + json.insert(std::make_pair("np", numberOfProperties.value())); + } + } + +public: + /// A list of shape items. + std::vector> items; + + std::optional numberOfProperties; +}; + +} + +#endif /* Group_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Group.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Group.swift new file mode 100644 index 00000000000..97436e3d4a5 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Group.swift @@ -0,0 +1,43 @@ +// +// GroupItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class Group: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Group.CodingKeys.self) + items = try container.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .items) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let itemDictionaries: [[String: Any]] = try dictionary.value(for: CodingKeys.items) + items = try [ShapeItem].fromDictionaries(itemDictionaries) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// A list of shape items. + let items: [ShapeItem] + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(items, forKey: .items) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case items = "it" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Merge.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Merge.cpp new file mode 100644 index 00000000000..b22b05d1f6d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Merge.cpp @@ -0,0 +1,5 @@ +#include "Merge.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Merge.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Merge.hpp new file mode 100644 index 00000000000..e0f70a8265d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Merge.hpp @@ -0,0 +1,62 @@ +#ifndef Merge_hpp +#define Merge_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class MergeMode: int { + None = 0, + Merge = 1, + Add = 2, + Subtract = 3, + Intersect = 4, + Exclude = 5 +}; + +/// An item that define an ellipse shape +class Merge: public ShapeItem { +public: + explicit Merge(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + mode(MergeMode::None) { + auto modeRawValue = getInt(json, "mm"); + switch (modeRawValue) { + case 0: + mode = MergeMode::None; + break; + case 1: + mode = MergeMode::Merge; + break; + case 2: + mode = MergeMode::Add; + break; + case 3: + mode = MergeMode::Subtract; + break; + case 4: + mode = MergeMode::Intersect; + break; + case 5: + mode = MergeMode::Exclude; + break; + default: + throw LottieParsingException(); + } + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("mm", (int)mode)); + } + +public: + /// The mode of the merge path + MergeMode mode; +}; + +} + +#endif /* Merge_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Merge.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Merge.swift new file mode 100644 index 00000000000..a7b0c951812 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Merge.swift @@ -0,0 +1,59 @@ +// +// Merge.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +// MARK: - MergeMode + +enum MergeMode: Int, Codable { + case none + case merge + case add + case subtract + case intersect + case exclude +} + +// MARK: - Merge + +/// An item that define an ellipse shape +final class Merge: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Merge.CodingKeys.self) + mode = try container.decode(MergeMode.self, forKey: .mode) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let modeRawType: Int = try dictionary.value(for: CodingKeys.mode) + guard let mode = MergeMode(rawValue: modeRawType) else { + throw InitializableError.invalidInput + } + self.mode = mode + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The mode of the merge path + let mode: MergeMode + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(mode, forKey: .mode) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case mode = "mm" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Rectangle.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Rectangle.cpp new file mode 100644 index 00000000000..c686130aa9e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Rectangle.cpp @@ -0,0 +1,5 @@ +#include "Rectangle.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Rectangle.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Rectangle.hpp new file mode 100644 index 00000000000..634ae441555 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Rectangle.hpp @@ -0,0 +1,99 @@ +#ifndef Rectangle_hpp +#define Rectangle_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define an ellipse shape +class Rectangle: public ShapeItem { +public: + explicit Rectangle(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + size(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + cornerRadius(KeyframeGroup(Vector1D(0.0))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + position = KeyframeGroup(getObject(json, "p")); + size = KeyframeGroup(getObject(json, "s")); + cornerRadius = KeyframeGroup(getObject(json, "r")); + } + + explicit Rectangle( + std::optional name_, + std::optional matchName_, + std::optional expressionIndex_, + std::optional cix_, + std::optional _hidden_, + std::optional index_, + std::optional blendMode_, + std::optional layerClass_, + std::optional direction_, + KeyframeGroup position_, + KeyframeGroup size_, + KeyframeGroup cornerRadius_ + ) : + ShapeItem( + name_, + matchName_, + expressionIndex_, + cix_, + ShapeType::Rectangle, + _hidden_, + index_, + blendMode_, + layerClass_ + ), + direction(direction_), + position(position_), + size(size_), + cornerRadius(cornerRadius_) { + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + + json.insert(std::make_pair("p", position.toJson())); + json.insert(std::make_pair("s", size.toJson())); + json.insert(std::make_pair("r", cornerRadius.toJson())); + } + +public: + /// The direction of the rect. + std::optional direction; + + /// The position + KeyframeGroup position; + + /// The size + KeyframeGroup size; + + /// The Corner radius of the rectangle + KeyframeGroup cornerRadius; +}; + +} + +#endif /* Rectangle_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Rectangle.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Rectangle.swift new file mode 100644 index 00000000000..03d221172a0 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Rectangle.swift @@ -0,0 +1,73 @@ +// +// Rectangle.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class Rectangle: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Rectangle.CodingKeys.self) + direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise + position = try container.decode(KeyframeGroup.self, forKey: .position) + size = try container.decode(KeyframeGroup.self, forKey: .size) + cornerRadius = try container.decode(KeyframeGroup.self, forKey: .cornerRadius) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + if + let directionRawType = dictionary[CodingKeys.direction.rawValue] as? Int, + let direction = PathDirection(rawValue: directionRawType) + { + self.direction = direction + } else { + direction = .clockwise + } + let positionDictionary: [String: Any] = try dictionary.value(for: CodingKeys.position) + position = try KeyframeGroup(dictionary: positionDictionary) + let sizeDictionary: [String: Any] = try dictionary.value(for: CodingKeys.size) + size = try KeyframeGroup(dictionary: sizeDictionary) + let cornerRadiusDictionary: [String: Any] = try dictionary.value(for: CodingKeys.cornerRadius) + cornerRadius = try KeyframeGroup(dictionary: cornerRadiusDictionary) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The direction of the rect. + let direction: PathDirection + + /// The position + let position: KeyframeGroup + + /// The size + let size: KeyframeGroup + + /// The Corner radius of the rectangle + let cornerRadius: KeyframeGroup + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(direction, forKey: .direction) + try container.encode(position, forKey: .position) + try container.encode(size, forKey: .size) + try container.encode(cornerRadius, forKey: .cornerRadius) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case direction = "d" + case position = "p" + case size = "s" + case cornerRadius = "r" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Repeater.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Repeater.cpp new file mode 100644 index 00000000000..70477e31af8 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Repeater.cpp @@ -0,0 +1,5 @@ +#include "Repeater.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Repeater.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Repeater.hpp new file mode 100644 index 00000000000..9f283d56c97 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Repeater.hpp @@ -0,0 +1,98 @@ +#ifndef Repeater_hpp +#define Repeater_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define a repeater +class Repeater: public ShapeItem { +public: + explicit Repeater(json11::Json::object const &json) noexcept(false) : + ShapeItem(json) { + if (const auto copiesData = getOptionalObject(json, "c")) { + copies = KeyframeGroup(copiesData.value()); + } + if (const auto offsetData = getOptionalObject(json, "o")) { + offset = KeyframeGroup(offsetData.value()); + } + + auto transformContainer = getObject(json, "tr"); + if (const auto startOpacityData = getOptionalObject(transformContainer, "so")) { + startOpacity = KeyframeGroup(startOpacityData.value()); + } + if (const auto endOpacityData = getOptionalObject(transformContainer, "eo")) { + endOpacity = KeyframeGroup(endOpacityData.value()); + } + if (const auto rotationData = getOptionalObject(transformContainer, "r")) { + rotation = KeyframeGroup(rotationData.value()); + } + if (const auto positionData = getOptionalObject(transformContainer, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto scaleData = getOptionalObject(transformContainer, "s")) { + scale = KeyframeGroup(scaleData.value()); + } + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (copies.has_value()) { + json.insert(std::make_pair("c", copies->toJson())); + } + if (offset.has_value()) { + json.insert(std::make_pair("o", offset->toJson())); + } + + json11::Json::object transformContainer; + if (startOpacity.has_value()) { + json.insert(std::make_pair("so", startOpacity->toJson())); + } + if (endOpacity.has_value()) { + json.insert(std::make_pair("eo", endOpacity->toJson())); + } + if (rotation.has_value()) { + json.insert(std::make_pair("r", rotation->toJson())); + } + if (position.has_value()) { + json.insert(std::make_pair("p", position->toJson())); + } + if (scale.has_value()) { + json.insert(std::make_pair("s", scale->toJson())); + } + + json.insert(std::make_pair("tr", transformContainer)); + } + +public: + /// The number of copies to repeat + std::optional> copies; + + /// The offset of each copy + std::optional> offset; + + /// Start Opacity + std::optional> startOpacity; + + /// End opacity + std::optional> endOpacity; + + /// The rotation + std::optional> rotation; + + /// Anchor Point + std::optional> anchorPoint; + + /// Position + std::optional> position; + + /// Scale + std::optional> scale; +}; + +} + +#endif /* Repeater_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Repeater.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Repeater.swift new file mode 100644 index 00000000000..ced87e5d26b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Repeater.swift @@ -0,0 +1,136 @@ +// +// Repeater.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class Repeater: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Repeater.CodingKeys.self) + copies = try container.decodeIfPresent(KeyframeGroup.self, forKey: .copies) ?? KeyframeGroup(Vector1D(0)) + offset = try container.decodeIfPresent(KeyframeGroup.self, forKey: .offset) ?? KeyframeGroup(Vector1D(0)) + let transformContainer = try container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform) + startOpacity = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .startOpacity) ?? KeyframeGroup(Vector1D(100)) + endOpacity = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .endOpacity) ?? KeyframeGroup(Vector1D(100)) + rotation = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) + position = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .position) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + anchorPoint = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .anchorPoint) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + scale = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + if let copiesDictionary = dictionary[CodingKeys.copies.rawValue] as? [String: Any] { + copies = try KeyframeGroup(dictionary: copiesDictionary) + } else { + copies = KeyframeGroup(Vector1D(0)) + } + if let offsetDictionary = dictionary[CodingKeys.offset.rawValue] as? [String: Any] { + offset = try KeyframeGroup(dictionary: offsetDictionary) + } else { + offset = KeyframeGroup(Vector1D(0)) + } + let transformDictionary: [String: Any] = try dictionary.value(for: CodingKeys.transform) + if let startOpacityDictionary = transformDictionary[TransformKeys.startOpacity.rawValue] as? [String: Any] { + startOpacity = try KeyframeGroup(dictionary: startOpacityDictionary) + } else { + startOpacity = KeyframeGroup(Vector1D(100)) + } + if let endOpacityDictionary = transformDictionary[TransformKeys.endOpacity.rawValue] as? [String: Any] { + endOpacity = try KeyframeGroup(dictionary: endOpacityDictionary) + } else { + endOpacity = KeyframeGroup(Vector1D(100)) + } + if let rotationDictionary = transformDictionary[TransformKeys.rotation.rawValue] as? [String: Any] { + rotation = try KeyframeGroup(dictionary: rotationDictionary) + } else { + rotation = KeyframeGroup(Vector1D(0)) + } + if let positionDictionary = transformDictionary[TransformKeys.position.rawValue] as? [String: Any] { + position = try KeyframeGroup(dictionary: positionDictionary) + } else { + position = KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + } + if let anchorPointDictionary = transformDictionary[TransformKeys.anchorPoint.rawValue] as? [String: Any] { + anchorPoint = try KeyframeGroup(dictionary: anchorPointDictionary) + } else { + anchorPoint = KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + } + if let scaleDictionary = transformDictionary[TransformKeys.scale.rawValue] as? [String: Any] { + scale = try KeyframeGroup(dictionary: scaleDictionary) + } else { + scale = KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) + } + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The number of copies to repeat + let copies: KeyframeGroup + + /// The offset of each copy + let offset: KeyframeGroup + + /// Start Opacity + let startOpacity: KeyframeGroup + + /// End opacity + let endOpacity: KeyframeGroup + + /// The rotation + let rotation: KeyframeGroup + + /// Anchor Point + let anchorPoint: KeyframeGroup + + /// Position + let position: KeyframeGroup + + /// Scale + let scale: KeyframeGroup + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(copies, forKey: .copies) + try container.encode(offset, forKey: .offset) + var transformContainer = container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform) + try transformContainer.encode(startOpacity, forKey: .startOpacity) + try transformContainer.encode(endOpacity, forKey: .endOpacity) + try transformContainer.encode(rotation, forKey: .rotation) + try transformContainer.encode(position, forKey: .position) + try transformContainer.encode(anchorPoint, forKey: .anchorPoint) + try transformContainer.encode(scale, forKey: .scale) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case copies = "c" + case offset = "o" + case transform = "tr" + } + + private enum TransformKeys: String, CodingKey { + case rotation = "r" + case startOpacity = "so" + case endOpacity = "eo" + case anchorPoint = "a" + case position = "p" + case scale = "s" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/RoundedRectangle.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/RoundedRectangle.cpp new file mode 100644 index 00000000000..64cba093630 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/RoundedRectangle.cpp @@ -0,0 +1,5 @@ +#include "RoundedRectangle.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/RoundedRectangle.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/RoundedRectangle.hpp new file mode 100644 index 00000000000..c380dc22d48 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/RoundedRectangle.hpp @@ -0,0 +1,75 @@ +#ifndef RoundedRectangle_hpp +#define RoundedRectangle_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define an ellipse shape +class RoundedRectangle: public ShapeItem { +public: + explicit RoundedRectangle(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + cornerRadius(KeyframeGroup(Vector1D(0.0))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto positionData = getOptionalObject(json, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto sizeData = getOptionalObject(json, "s")) { + size = KeyframeGroup(sizeData.value()); + } + + cornerRadius = KeyframeGroup(getObject(json, "r")); + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + if (position.has_value()) { + json.insert(std::make_pair("p", position->toJson())); + } + if (size.has_value()) { + json.insert(std::make_pair("s", size->toJson())); + } + + json.insert(std::make_pair("r", cornerRadius.toJson())); + } + +public: + /// The direction of the rect. + std::optional direction; + + /// The position + std::optional> position; + + /// The size + std::optional> size; + + /// The Corner radius of the rectangle + KeyframeGroup cornerRadius; +}; + +} + +#endif /* RoundedRectangle_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Shape.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Shape.cpp new file mode 100644 index 00000000000..bf5a96876c4 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Shape.cpp @@ -0,0 +1,5 @@ +#include "Shape.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Shape.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Shape.hpp new file mode 100644 index 00000000000..5ebaa9881ca --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Shape.hpp @@ -0,0 +1,53 @@ +#ifndef Shape_hpp +#define Shape_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// An item that defines an custom shape +class Shape: public ShapeItem { +public: + explicit Shape(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + path(KeyframeGroup(getObject(json, "ks"))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("ks", path.toJson())); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + } + +public: + KeyframeGroup path; + std::optional direction; +}; + +} + +#endif /* Shape_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Shape.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Shape.swift new file mode 100644 index 00000000000..c1eeb66e68e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Shape.swift @@ -0,0 +1,56 @@ +// +// VectorShape.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that defines an custom shape +final class Shape: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Shape.CodingKeys.self) + path = try container.decode(KeyframeGroup.self, forKey: .path) + direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let pathDictionary: [String: Any] = try dictionary.value(for: CodingKeys.path) + path = try KeyframeGroup(dictionary: pathDictionary) + if + let directionRawValue = dictionary[CodingKeys.direction.rawValue] as? Int, + let direction = PathDirection(rawValue: directionRawValue) + { + self.direction = direction + } else { + direction = nil + } + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The Path + let path: KeyframeGroup + + let direction: PathDirection? + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(path, forKey: .path) + try container.encodeIfPresent(direction, forKey: .direction) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case path = "ks" + case direction = "d" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeItem.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeItem.cpp new file mode 100644 index 00000000000..ca859c56ddd --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeItem.cpp @@ -0,0 +1,55 @@ +#include "ShapeItem.hpp" + +#include "Ellipse.hpp" +#include "Fill.hpp" +#include "GradientFill.hpp" +#include "Group.hpp" +#include "GradientStroke.hpp" +#include "Merge.hpp" +#include "Rectangle.hpp" +#include "RoundedRectangle.hpp" +#include "Repeater.hpp" +#include "Shape.hpp" +#include "Star.hpp" +#include "Stroke.hpp" +#include "Trim.hpp" +#include "ShapeTransform.hpp" + +namespace lottie { + +std::shared_ptr parseShapeItem(json11::Json::object const &json) noexcept(false) { + auto typeRawValue = getString(json, "ty"); + if (typeRawValue == "el") { + return std::make_shared(json); + } else if (typeRawValue == "fl") { + return std::make_shared(json); + } else if (typeRawValue == "gf") { + return std::make_shared(json); + } else if (typeRawValue == "gr") { + return std::make_shared(json); + } else if (typeRawValue == "gs") { + return std::make_shared(json); + } else if (typeRawValue == "mm") { + return std::make_shared(json); + } else if (typeRawValue == "rc") { + return std::make_shared(json); + } else if (typeRawValue == "rp") { + return std::make_shared(json); + } else if (typeRawValue == "sh") { + return std::make_shared(json); + } else if (typeRawValue == "sr") { + return std::make_shared(json); + } else if (typeRawValue == "st") { + return std::make_shared(json); + } else if (typeRawValue == "tm") { + return std::make_shared(json); + } else if (typeRawValue == "tr") { + return std::make_shared(json); + } else if (typeRawValue == "rd") { + return std::make_shared(json); + } else { + throw LottieParsingException(); + } +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeItem.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeItem.hpp new file mode 100644 index 00000000000..2b05dba93a6 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeItem.hpp @@ -0,0 +1,209 @@ +#ifndef ShapeItem_hpp +#define ShapeItem_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +enum class ShapeType { + Ellipse, + Fill, + GradientFill, + Group, + GradientStroke, + Merge, + Rectangle, + Repeater, + Shape, + Star, + Stroke, + Trim, + Transform, + RoundedRectangle +}; + +/// An item belonging to a Shape Layer +class ShapeItem { +public: + ShapeItem(json11::Json const &jsonAny) noexcept(false) : + type(ShapeType::Ellipse) { + if (!jsonAny.is_object()) { + throw LottieParsingException(); + } + + json11::Json::object const &json = jsonAny.object_items(); + + name = getOptionalString(json, "nm"); + matchName = getOptionalString(json, "mn"); + expressionIndex = getOptionalInt(json, "ix"); + cix = getOptionalInt(json, "cix"); + + index = getOptionalInt(json, "ind"); + blendMode = getOptionalInt(json, "bm"); + + auto typeRawValue = getString(json, "ty"); + if (typeRawValue == "el") { + type = ShapeType::Ellipse; + } else if (typeRawValue == "fl") { + type = ShapeType::Fill; + } else if (typeRawValue == "gf") { + type = ShapeType::GradientFill; + } else if (typeRawValue == "gr") { + type = ShapeType::Group; + } else if (typeRawValue == "gs") { + type = ShapeType::GradientStroke; + } else if (typeRawValue == "mm") { + type = ShapeType::Merge; + } else if (typeRawValue == "rc") { + type = ShapeType::Rectangle; + } else if (typeRawValue == "rp") { + type = ShapeType::Repeater; + } else if (typeRawValue == "sh") { + type = ShapeType::Shape; + } else if (typeRawValue == "sr") { + type = ShapeType::Star; + } else if (typeRawValue == "st") { + type = ShapeType::Stroke; + } else if (typeRawValue == "tm") { + type = ShapeType::Trim; + } else if (typeRawValue == "tr") { + type = ShapeType::Transform; + } else if (typeRawValue == "rd") { + type = ShapeType::RoundedRectangle; + } else { + throw LottieParsingException(); + } + + _hidden = getOptionalBool(json, "hd"); + + layerClass = getOptionalString(json, "cl"); + } + + ShapeItem( + std::optional name_, + std::optional matchName_, + std::optional expressionIndex_, + std::optional cix_, + ShapeType type_, + std::optional _hidden_, + std::optional index_, + std::optional blendMode_, + std::optional layerClass_ + ) : + name(name_), + matchName(matchName_), + expressionIndex(expressionIndex_), + cix(cix_), + type(type_), + _hidden(_hidden_), + index(index_), + blendMode(blendMode_), + layerClass(layerClass_) { + } + + ShapeItem(const ShapeItem&) = delete; + ShapeItem& operator=(ShapeItem&) = delete; + + virtual void toJson(json11::Json::object &json) const { + if (name.has_value()) { + json.insert(std::make_pair("nm", name.value())); + } + if (matchName.has_value()) { + json.insert(std::make_pair("mn", matchName.value())); + } + if (expressionIndex.has_value()) { + json.insert(std::make_pair("ix", expressionIndex.value())); + } + if (cix.has_value()) { + json.insert(std::make_pair("cix", cix.value())); + } + + if (index.has_value()) { + json.insert(std::make_pair("ind", index.value())); + } + if (blendMode.has_value()) { + json.insert(std::make_pair("bm", blendMode.value())); + } + + switch (type) { + case ShapeType::Ellipse: + json.insert(std::make_pair("ty", "el")); + break; + case ShapeType::Fill: + json.insert(std::make_pair("ty", "fl")); + break; + case ShapeType::GradientFill: + json.insert(std::make_pair("ty", "gf")); + break; + case ShapeType::Group: + json.insert(std::make_pair("ty", "gr")); + break; + case ShapeType::GradientStroke: + json.insert(std::make_pair("ty", "gs")); + break; + case ShapeType::Merge: + json.insert(std::make_pair("ty", "mm")); + break; + case ShapeType::Rectangle: + json.insert(std::make_pair("ty", "rc")); + break; + case ShapeType::RoundedRectangle: + json.insert(std::make_pair("ty", "rd")); + break; + case ShapeType::Repeater: + json.insert(std::make_pair("ty", "rp")); + break; + case ShapeType::Shape: + json.insert(std::make_pair("ty", "sh")); + break; + case ShapeType::Star: + json.insert(std::make_pair("ty", "sr")); + break; + case ShapeType::Stroke: + json.insert(std::make_pair("ty", "st")); + break; + case ShapeType::Trim: + json.insert(std::make_pair("ty", "tm")); + break; + case ShapeType::Transform: + json.insert(std::make_pair("ty", "tr")); + break; + } + + if (_hidden.has_value()) { + json.insert(std::make_pair("hd", _hidden.value())); + } + + if (layerClass.has_value()) { + json.insert(std::make_pair("cl", layerClass.value())); + } + } + + bool hidden() const { + if (_hidden.has_value()) { + return _hidden.value(); + } else { + return false; + } + } + +public: + std::optional name; + std::optional matchName; + std::optional expressionIndex; + std::optional cix; + ShapeType type; + std::optional _hidden; + std::optional index; + std::optional blendMode; + + std::optional layerClass; +}; + +std::shared_ptr parseShapeItem(json11::Json::object const &json) noexcept(false); + +} + +#endif /* ShapeItem_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeItem.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeItem.swift new file mode 100644 index 00000000000..2e28498a91f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeItem.swift @@ -0,0 +1,163 @@ +// +// ShapeItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +// MARK: - ShapeType + ClassFamily + +/// Used for mapping a heterogeneous list to classes for parsing. +extension ShapeType: ClassFamily { + + static var discriminator: Discriminator = .type + + func getType() -> AnyObject.Type { + switch self { + case .ellipse: + return Ellipse.self + case .fill: + return Fill.self + case .gradientFill: + return GradientFill.self + case .group: + return Group.self + case .gradientStroke: + return GradientStroke.self + case .merge: + return Merge.self + case .rectangle: + return Rectangle.self + case .repeater: + return Repeater.self + case .shape: + return Shape.self + case .star: + return Star.self + case .stroke: + return Stroke.self + case .trim: + return Trim.self + case .transform: + return ShapeTransform.self + default: + return ShapeItem.self + } + } +} + +// MARK: - ShapeType + +enum ShapeType: String, Codable { + case ellipse = "el" + case fill = "fl" + case gradientFill = "gf" + case group = "gr" + case gradientStroke = "gs" + case merge = "mm" + case rectangle = "rc" + case repeater = "rp" + case round = "rd" + case shape = "sh" + case star = "sr" + case stroke = "st" + case trim = "tm" + case transform = "tr" + case unknown + + public init(from decoder: Decoder) throws { + self = try ShapeType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown + } +} + +// MARK: - ShapeItem + +/// An item belonging to a Shape Layer +class ShapeItem: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ShapeItem.CodingKeys.self) + name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer" + type = try container.decode(ShapeType.self, forKey: .type) + hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false + } + + required init(dictionary: [String: Any]) throws { + name = (try? dictionary.value(for: CodingKeys.name)) ?? "Layer" + type = ShapeType(rawValue: try dictionary.value(for: CodingKeys.type)) ?? .unknown + hidden = (try? dictionary.value(for: CodingKeys.hidden)) ?? false + } + + init( + name: String, + type: ShapeType, + hidden: Bool) + { + self.name = name + self.type = type + self.hidden = hidden + } + + // MARK: Internal + + /// The name of the shape + let name: String + + /// The type of shape + let type: ShapeType + + let hidden: Bool + + // MARK: Fileprivate + + fileprivate enum CodingKeys: String, CodingKey { + case name = "nm" + case type = "ty" + case hidden = "hd" + } +} + +extension Array where Element == ShapeItem { + + static func fromDictionaries(_ dictionaries: [[String: Any]]) throws -> [ShapeItem] { + try dictionaries.compactMap { dictionary in + let shapeType = dictionary[ShapeItem.CodingKeys.type.rawValue] as? String + switch ShapeType(rawValue: shapeType ?? ShapeType.unknown.rawValue) { + case .ellipse: + return try Ellipse(dictionary: dictionary) + case .fill: + return try Fill(dictionary: dictionary) + case .gradientFill: + return try GradientFill(dictionary: dictionary) + case .group: + return try Group(dictionary: dictionary) + case .gradientStroke: + return try GradientStroke(dictionary: dictionary) + case .merge: + return try Merge(dictionary: dictionary) + case .rectangle: + return try Rectangle(dictionary: dictionary) + case .repeater: + return try Repeater(dictionary: dictionary) + case .shape: + return try Shape(dictionary: dictionary) + case .star: + return try Star(dictionary: dictionary) + case .stroke: + return try Stroke(dictionary: dictionary) + case .trim: + return try Trim(dictionary: dictionary) + case .transform: + return try ShapeTransform(dictionary: dictionary) + case .none: + return nil + default: + return try ShapeItem(dictionary: dictionary) + } + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeTransform.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeTransform.cpp new file mode 100644 index 00000000000..1df07218bc0 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeTransform.cpp @@ -0,0 +1,5 @@ +#include "ShapeTransform.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeTransform.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeTransform.hpp new file mode 100644 index 00000000000..584e37e10e9 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeTransform.hpp @@ -0,0 +1,89 @@ +#ifndef ShapeTransform_hpp +#define ShapeTransform_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define a shape transform +class ShapeTransform: public ShapeItem { +public: + explicit ShapeTransform(json11::Json::object const &json) noexcept(false) : + ShapeItem(json) { + if (const auto anchorData = getOptionalObject(json, "a")) { + anchor = KeyframeGroup(anchorData.value()); + } + if (const auto positionData = getOptionalObject(json, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto scaleData = getOptionalObject(json, "s")) { + scale = KeyframeGroup(scaleData.value()); + } + if (const auto rotationData = getOptionalObject(json, "r")) { + rotation = KeyframeGroup(rotationData.value()); + } + if (const auto opacityData = getOptionalObject(json, "o")) { + opacity = KeyframeGroup(opacityData.value()); + } + if (const auto skewData = getOptionalObject(json, "sk")) { + skew = KeyframeGroup(skewData.value()); + } + if (const auto skewAxisData = getOptionalObject(json, "sa")) { + skewAxis = KeyframeGroup(skewAxisData.value()); + } + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (anchor.has_value()) { + json.insert(std::make_pair("a", anchor->toJson())); + } + if (position.has_value()) { + json.insert(std::make_pair("p", position->toJson())); + } + if (scale.has_value()) { + json.insert(std::make_pair("s", scale->toJson())); + } + if (rotation.has_value()) { + json.insert(std::make_pair("r", rotation->toJson())); + } + if (opacity.has_value()) { + json.insert(std::make_pair("o", opacity->toJson())); + } + if (skew.has_value()) { + json.insert(std::make_pair("sk", skew->toJson())); + } + if (skewAxis.has_value()) { + json.insert(std::make_pair("sa", skewAxis->toJson())); + } + } + +public: + /// Anchor Point + std::optional> anchor; + + /// Position + std::optional> position; + + /// Scale + std::optional> scale; + + /// Rotation + std::optional> rotation; + + /// opacity + std::optional> opacity; + + /// Skew + std::optional> skew; + + /// Skew Axis + std::optional> skewAxis; +}; + +} + +#endif /* ShapeTransform_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeTransform.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeTransform.swift new file mode 100644 index 00000000000..734f01e1142 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/ShapeTransform.swift @@ -0,0 +1,136 @@ +// +// TransformItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class ShapeTransform: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ShapeTransform.CodingKeys.self) + anchor = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .anchor) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + position = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .position) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + scale = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) + rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) + opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) + skew = try container.decodeIfPresent(KeyframeGroup.self, forKey: .skew) ?? KeyframeGroup(Vector1D(0)) + skewAxis = try container.decodeIfPresent(KeyframeGroup.self, forKey: .skewAxis) ?? KeyframeGroup(Vector1D(0)) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + if + let anchorDictionary = dictionary[CodingKeys.anchor.rawValue] as? [String: Any], + let anchor = try? KeyframeGroup(dictionary: anchorDictionary) + { + self.anchor = anchor + } else { + anchor = KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + } + if + let positionDictionary = dictionary[CodingKeys.position.rawValue] as? [String: Any], + let position = try? KeyframeGroup(dictionary: positionDictionary) + { + self.position = position + } else { + position = KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + } + if + let scaleDictionary = dictionary[CodingKeys.scale.rawValue] as? [String: Any], + let scale = try? KeyframeGroup(dictionary: scaleDictionary) + { + self.scale = scale + } else { + scale = KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) + } + if + let rotationDictionary = dictionary[CodingKeys.rotation.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + self.rotation = rotation + } else { + rotation = KeyframeGroup(Vector1D(0)) + } + if + let opacityDictionary = dictionary[CodingKeys.opacity.rawValue] as? [String: Any], + let opacity = try? KeyframeGroup(dictionary: opacityDictionary) + { + self.opacity = opacity + } else { + opacity = KeyframeGroup(Vector1D(100)) + } + if + let skewDictionary = dictionary[CodingKeys.skew.rawValue] as? [String: Any], + let skew = try? KeyframeGroup(dictionary: skewDictionary) + { + self.skew = skew + } else { + skew = KeyframeGroup(Vector1D(0)) + } + if + let skewAxisDictionary = dictionary[CodingKeys.skewAxis.rawValue] as? [String: Any], + let skewAxis = try? KeyframeGroup(dictionary: skewAxisDictionary) + { + self.skewAxis = skewAxis + } else { + skewAxis = KeyframeGroup(Vector1D(0)) + } + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// Anchor Point + let anchor: KeyframeGroup + + /// Position + let position: KeyframeGroup + + /// Scale + let scale: KeyframeGroup + + /// Rotation + let rotation: KeyframeGroup + + /// opacity + let opacity: KeyframeGroup + + /// Skew + let skew: KeyframeGroup + + /// Skew Axis + let skewAxis: KeyframeGroup + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(anchor, forKey: .anchor) + try container.encode(position, forKey: .position) + try container.encode(scale, forKey: .scale) + try container.encode(rotation, forKey: .rotation) + try container.encode(opacity, forKey: .opacity) + try container.encode(skew, forKey: .skew) + try container.encode(skewAxis, forKey: .skewAxis) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case anchor = "a" + case position = "p" + case scale = "s" + case rotation = "r" + case opacity = "o" + case skew = "sk" + case skewAxis = "sa" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Star.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Star.cpp new file mode 100644 index 00000000000..c5f38f5946d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Star.cpp @@ -0,0 +1,5 @@ +#include "Star.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Star.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Star.hpp new file mode 100644 index 00000000000..5f5a58dad0a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Star.hpp @@ -0,0 +1,131 @@ +#ifndef Star_hpp +#define Star_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +enum class StarType: int { + None = 0, + Star = 1, + Polygon = 2 +}; + +/// An item that define a star shape +class Star: public ShapeItem { +public: + explicit Star(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + outerRadius(KeyframeGroup(Vector1D(0.0))), + outerRoundness(KeyframeGroup(Vector1D(0.0))), + rotation(KeyframeGroup(Vector1D(0.0))), + points(KeyframeGroup(Vector1D(0.0))), + starType(StarType::None) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + position = KeyframeGroup(getObject(json, "p")); + outerRadius = KeyframeGroup(getObject(json, "or")); + outerRoundness = KeyframeGroup(getObject(json, "os")); + + if (const auto innerRadiusData = getOptionalObject(json, "ir")) { + innerRadius = KeyframeGroup(innerRadiusData.value()); + } + if (const auto innerRoundnessData = getOptionalObject(json, "is")) { + innerRoundness = KeyframeGroup(innerRoundnessData.value()); + } + + rotation = KeyframeGroup(getObject(json, "r")); + points = KeyframeGroup(getObject(json, "pt")); + + auto starTypeRawValue = getInt(json, "sy"); + switch (starTypeRawValue) { + case 0: + starType = StarType::None; + break; + case 1: + starType = StarType::Star; + break; + case 2: + starType = StarType::Polygon; + break; + default: + throw LottieParsingException(); + } + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + + json.insert(std::make_pair("p", position.toJson())); + json.insert(std::make_pair("or", outerRadius.toJson())); + json.insert(std::make_pair("os", outerRoundness.toJson())); + + if (innerRadius.has_value()) { + json.insert(std::make_pair("ir", innerRadius->toJson())); + } + if (innerRoundness.has_value()) { + json.insert(std::make_pair("is", innerRoundness->toJson())); + } + + json.insert(std::make_pair("r", rotation.toJson())); + json.insert(std::make_pair("pt", points.toJson())); + + json.insert(std::make_pair("sy", (int)starType)); + } + +public: + /// The direction of the star. + std::optional direction; + + /// The position of the star + KeyframeGroup position; + + /// The outer radius of the star + KeyframeGroup outerRadius; + + /// The outer roundness of the star + KeyframeGroup outerRoundness; + + /// The outer radius of the star + std::optional> innerRadius; + + /// The outer roundness of the star + std::optional> innerRoundness; + + /// The rotation of the star + KeyframeGroup rotation; + + /// The number of points on the star + KeyframeGroup points; + + /// The type of star + StarType starType; +}; + +} + +#endif /* Star_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Star.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Star.swift new file mode 100644 index 00000000000..74c4e3d9074 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Star.swift @@ -0,0 +1,132 @@ +// +// Star.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +// MARK: - StarType + +enum StarType: Int, Codable { + case none + case star + case polygon +} + +// MARK: - Star + +/// An item that define an ellipse shape +final class Star: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Star.CodingKeys.self) + direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise + position = try container.decode(KeyframeGroup.self, forKey: .position) + outerRadius = try container.decode(KeyframeGroup.self, forKey: .outerRadius) + outerRoundness = try container.decode(KeyframeGroup.self, forKey: .outerRoundness) + innerRadius = try container.decodeIfPresent(KeyframeGroup.self, forKey: .innerRadius) + innerRoundness = try container.decodeIfPresent(KeyframeGroup.self, forKey: .innerRoundness) + rotation = try container.decode(KeyframeGroup.self, forKey: .rotation) + points = try container.decode(KeyframeGroup.self, forKey: .points) + starType = try container.decode(StarType.self, forKey: .starType) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + if + let directionRawValue = dictionary[CodingKeys.direction.rawValue] as? Int, + let direction = PathDirection(rawValue: directionRawValue) + { + self.direction = direction + } else { + direction = .clockwise + } + let positionDictionary: [String: Any] = try dictionary.value(for: CodingKeys.position) + position = try KeyframeGroup(dictionary: positionDictionary) + let outerRadiusDictionary: [String: Any] = try dictionary.value(for: CodingKeys.outerRadius) + outerRadius = try KeyframeGroup(dictionary: outerRadiusDictionary) + let outerRoundnessDictionary: [String: Any] = try dictionary.value(for: CodingKeys.outerRoundness) + outerRoundness = try KeyframeGroup(dictionary: outerRoundnessDictionary) + if let innerRadiusDictionary = dictionary[CodingKeys.innerRadius.rawValue] as? [String: Any] { + innerRadius = try KeyframeGroup(dictionary: innerRadiusDictionary) + } else { + innerRadius = nil + } + if let innerRoundnessDictionary = dictionary[CodingKeys.innerRoundness.rawValue] as? [String: Any] { + innerRoundness = try KeyframeGroup(dictionary: innerRoundnessDictionary) + } else { + innerRoundness = nil + } + let rotationDictionary: [String: Any] = try dictionary.value(for: CodingKeys.rotation) + rotation = try KeyframeGroup(dictionary: rotationDictionary) + let pointsDictionary: [String: Any] = try dictionary.value(for: CodingKeys.points) + points = try KeyframeGroup(dictionary: pointsDictionary) + let starTypeRawValue: Int = try dictionary.value(for: CodingKeys.starType) + guard let starType = StarType(rawValue: starTypeRawValue) else { + throw InitializableError.invalidInput + } + self.starType = starType + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The direction of the star. + let direction: PathDirection + + /// The position of the star + let position: KeyframeGroup + + /// The outer radius of the star + let outerRadius: KeyframeGroup + + /// The outer roundness of the star + let outerRoundness: KeyframeGroup + + /// The outer radius of the star + let innerRadius: KeyframeGroup? + + /// The outer roundness of the star + let innerRoundness: KeyframeGroup? + + /// The rotation of the star + let rotation: KeyframeGroup + + /// The number of points on the star + let points: KeyframeGroup + + /// The type of star + let starType: StarType + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(direction, forKey: .direction) + try container.encode(position, forKey: .position) + try container.encode(outerRadius, forKey: .outerRadius) + try container.encode(outerRoundness, forKey: .outerRoundness) + try container.encode(innerRadius, forKey: .innerRadius) + try container.encode(innerRoundness, forKey: .innerRoundness) + try container.encode(rotation, forKey: .rotation) + try container.encode(points, forKey: .points) + try container.encode(starType, forKey: .starType) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case direction = "d" + case position = "p" + case outerRadius = "or" + case outerRoundness = "os" + case innerRadius = "ir" + case innerRoundness = "is" + case rotation = "r" + case points = "pt" + case starType = "sy" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Stroke.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Stroke.cpp new file mode 100644 index 00000000000..0c858f24425 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Stroke.cpp @@ -0,0 +1,5 @@ +#include "Stroke.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Stroke.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Stroke.hpp new file mode 100644 index 00000000000..2fe24d3050f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Stroke.hpp @@ -0,0 +1,140 @@ +#ifndef Stroke_hpp +#define Stroke_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientStroke.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/Objects/DashElement.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// An item that define an ellipse shape +class Stroke: public ShapeItem { +public: + explicit Stroke(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(100.0))), + color(KeyframeGroup(Color(0.0, 0.0, 0.0, 0.0))), + width(KeyframeGroup(Vector1D(0.0))), + lineCap(LineCap::Round), + lineJoin(LineJoin::Round) { + opacity = KeyframeGroup(getObject(json, "o")); + color = KeyframeGroup(getObject(json, "c")); + width = KeyframeGroup(getObject(json, "w")); + + if (const auto lineCapRawValue = getOptionalInt(json, "lc")) { + switch (lineCapRawValue.value()) { + case 0: + lineCap = LineCap::None; + break; + case 1: + lineCap = LineCap::Butt; + break; + case 2: + lineCap = LineCap::Round; + break; + case 3: + lineCap = LineCap::Square; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto lineJoinRawValue = getOptionalInt(json, "lj")) { + switch (lineJoinRawValue.value()) { + case 0: + lineJoin = LineJoin::None; + break; + case 1: + lineJoin = LineJoin::Miter; + break; + case 2: + lineJoin = LineJoin::Round; + break; + case 3: + lineJoin = LineJoin::Bevel; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto miterLimitData = getOptionalDouble(json, "ml")) { + miterLimit = miterLimitData.value(); + } + + if (const auto dashElementsData = getOptionalObjectArray(json, "d")) { + dashPattern = std::vector(); + for (const auto &dashElementData : dashElementsData.value()) { + dashPattern->push_back(DashElement(dashElementData)); + } + } + + fillEnabled = getOptionalBool(json, "fillEnabled"); + ml2 = getOptionalAny(json, "ml2"); + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("c", color.toJson())); + json.insert(std::make_pair("w", width.toJson())); + + json.insert(std::make_pair("lc", (int)lineCap)); + json.insert(std::make_pair("lj", (int)lineJoin)); + + if (miterLimit.has_value()) { + json.insert(std::make_pair("ml", miterLimit.value())); + } + + if (dashPattern.has_value()) { + json11::Json::array dashElements; + for (const auto &dashElement : dashPattern.value()) { + dashElements.push_back(dashElement.toJson()); + } + json.insert(std::make_pair("d", dashElements)); + } + + if (fillEnabled.has_value()) { + json.insert(std::make_pair("fillEnabled", fillEnabled.value())); + } + if (ml2.has_value()) { + json.insert(std::make_pair("ml2", ml2.value())); + } + } + +public: + /// The opacity of the stroke + KeyframeGroup opacity; + + /// The Color of the stroke + KeyframeGroup color; + + /// The width of the stroke + KeyframeGroup width; + + /// Line Cap + LineCap lineCap; + + /// Line Join + LineJoin lineJoin; + + /// Miter Limit + std::optional miterLimit; + + /// The dash pattern of the stroke + std::optional> dashPattern; + + std::optional fillEnabled; + std::optional ml2; +}; + +} + +#endif /* Stroke_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Stroke.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Stroke.swift new file mode 100644 index 00000000000..6caebb61be4 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Stroke.swift @@ -0,0 +1,102 @@ +// +// Stroke.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class Stroke: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Stroke.CodingKeys.self) + opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + color = try container.decode(KeyframeGroup.self, forKey: .color) + width = try container.decode(KeyframeGroup.self, forKey: .width) + lineCap = try container.decodeIfPresent(LineCap.self, forKey: .lineCap) ?? .round + lineJoin = try container.decodeIfPresent(LineJoin.self, forKey: .lineJoin) ?? .round + miterLimit = try container.decodeIfPresent(Double.self, forKey: .miterLimit) ?? 4 + dashPattern = try container.decodeIfPresent([DashElement].self, forKey: .dashPattern) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let opacityDictionary: [String: Any] = try dictionary.value(for: CodingKeys.opacity) + opacity = try KeyframeGroup(dictionary: opacityDictionary) + let colorDictionary: [String: Any] = try dictionary.value(for: CodingKeys.color) + color = try KeyframeGroup(dictionary: colorDictionary) + let widthDictionary: [String: Any] = try dictionary.value(for: CodingKeys.width) + width = try KeyframeGroup(dictionary: widthDictionary) + if + let lineCapRawValue = dictionary[CodingKeys.lineCap.rawValue] as? Int, + let lineCap = LineCap(rawValue: lineCapRawValue) + { + self.lineCap = lineCap + } else { + lineCap = .round + } + if + let lineJoinRawValue = dictionary[CodingKeys.lineJoin.rawValue] as? Int, + let lineJoin = LineJoin(rawValue: lineJoinRawValue) + { + self.lineJoin = lineJoin + } else { + lineJoin = .round + } + miterLimit = (try? dictionary.value(for: CodingKeys.miterLimit)) ?? 4 + let dashPatternDictionaries = dictionary[CodingKeys.dashPattern.rawValue] as? [[String: Any]] + dashPattern = try? dashPatternDictionaries?.map({ try DashElement(dictionary: $0) }) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The opacity of the stroke + let opacity: KeyframeGroup + + /// The Color of the stroke + let color: KeyframeGroup + + /// The width of the stroke + let width: KeyframeGroup + + /// Line Cap + let lineCap: LineCap + + /// Line Join + let lineJoin: LineJoin + + /// Miter Limit + let miterLimit: Double + + /// The dash pattern of the stroke + let dashPattern: [DashElement]? + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(color, forKey: .color) + try container.encode(width, forKey: .width) + try container.encode(lineCap, forKey: .lineCap) + try container.encode(lineJoin, forKey: .lineJoin) + try container.encode(miterLimit, forKey: .miterLimit) + try container.encodeIfPresent(dashPattern, forKey: .dashPattern) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case opacity = "o" + case color = "c" + case width = "w" + case lineCap = "lc" + case lineJoin = "lj" + case miterLimit = "ml" + case dashPattern = "d" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Trim.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Trim.cpp new file mode 100644 index 00000000000..85f95d8b0cf --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Trim.cpp @@ -0,0 +1,5 @@ +#include "Trim.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Trim.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Trim.hpp new file mode 100644 index 00000000000..6bc05b1f04d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Trim.hpp @@ -0,0 +1,60 @@ +#ifndef Trim_hpp +#define Trim_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class TrimType: int { + Simultaneously = 1, + Individually = 2 +}; + +/// An item that defines trim +class Trim: public ShapeItem { +public: + explicit Trim(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + start(KeyframeGroup(Vector1D(0.0))), + end(KeyframeGroup(Vector1D(0.0))), + offset(KeyframeGroup(Vector1D(0.0))), + trimType(TrimType::Simultaneously) { + start = KeyframeGroup(getObject(json, "s")); + end = KeyframeGroup(getObject(json, "e")); + offset = KeyframeGroup(getObject(json, "o")); + + auto trimTypeRawValue = getInt(json, "m"); + switch (trimTypeRawValue) { + case 1: + trimType = TrimType::Simultaneously; + break; + case 2: + trimType = TrimType::Individually; + break; + default: + throw LottieParsingException(); + } + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("s", start.toJson())); + json.insert(std::make_pair("e", end.toJson())); + json.insert(std::make_pair("o", offset.toJson())); + json.insert(std::make_pair("m", (int)trimType)); + } + +public: + KeyframeGroup start; + KeyframeGroup end; + KeyframeGroup offset; + TrimType trimType; +}; + +} + +#endif /* Trim_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Trim.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Trim.swift new file mode 100644 index 00000000000..7dc08144322 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/ShapeItems/Trim.swift @@ -0,0 +1,78 @@ +// +// Trim.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +// MARK: - TrimType + +enum TrimType: Int, Codable { + case simultaneously = 1 + case individually = 2 +} + +// MARK: - Trim + +/// An item that define an ellipse shape +final class Trim: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Trim.CodingKeys.self) + start = try container.decode(KeyframeGroup.self, forKey: .start) + end = try container.decode(KeyframeGroup.self, forKey: .end) + offset = try container.decode(KeyframeGroup.self, forKey: .offset) + trimType = try container.decode(TrimType.self, forKey: .trimType) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let startDictionary: [String: Any] = try dictionary.value(for: CodingKeys.start) + start = try KeyframeGroup(dictionary: startDictionary) + let endDictionary: [String: Any] = try dictionary.value(for: CodingKeys.end) + end = try KeyframeGroup(dictionary: endDictionary) + let offsetDictionary: [String: Any] = try dictionary.value(for: CodingKeys.offset) + offset = try KeyframeGroup(dictionary: offsetDictionary) + let trimTypeRawValue: Int = try dictionary.value(for: CodingKeys.trimType) + guard let trimType = TrimType(rawValue: trimTypeRawValue) else { + throw InitializableError.invalidInput + } + self.trimType = trimType + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The start of the trim + let start: KeyframeGroup + + /// The end of the trim + let end: KeyframeGroup + + /// The offset of the trim + let offset: KeyframeGroup + + let trimType: TrimType + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(start, forKey: .start) + try container.encode(end, forKey: .end) + try container.encode(offset, forKey: .offset) + try container.encode(trimType, forKey: .trimType) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case start = "s" + case end = "e" + case offset = "o" + case trimType = "m" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Font.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Font.cpp new file mode 100644 index 00000000000..7dfd91b3cc3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Font.cpp @@ -0,0 +1,5 @@ +#include "Font.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Font.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Font.hpp new file mode 100644 index 00000000000..8bde83023e2 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Font.hpp @@ -0,0 +1,103 @@ +#ifndef Font_hpp +#define Font_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class Font { +public: + Font( + std::string const &name_, + std::string const &familyName_, + std::string const &style_, + double ascent_ + ) : + name(name_), + familyName(familyName_), + style(style_), + ascent(ascent_) { + } + + explicit Font(json11::Json::object const &json) noexcept(false) { + name = getString(json, "fName"); + familyName = getString(json, "fFamily"); + path = getOptionalString(json, "fPath"); + weight = getOptionalString(json, "fWeight"); + fontClass = getOptionalString(json, "fClass"); + style = getString(json, "fStyle"); + ascent = getDouble(json, "ascent"); + origin = getOptionalInt(json, "origin"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("fName", name)); + result.insert(std::make_pair("fFamily", familyName)); + if (path.has_value()) { + result.insert(std::make_pair("fPath", path.value())); + } + if (weight.has_value()) { + result.insert(std::make_pair("fWeight", weight.value())); + } + if (fontClass.has_value()) { + result.insert(std::make_pair("fClass", fontClass.value())); + } + result.insert(std::make_pair("fStyle", style)); + result.insert(std::make_pair("ascent", ascent)); + if (origin.has_value()) { + result.insert(std::make_pair("origin", origin.value())); + } + + return result; + } + +public: + std::string name; + std::string familyName; + std::optional path; + std::optional weight; + std::optional fontClass; + std::string style; + double ascent; + std::optional origin; +}; + +/// A list of fonts +class FontList { +public: + FontList(std::vector const &fonts_) : + fonts(fonts_) { + } + + explicit FontList(json11::Json::object const &json) noexcept(false) { + if (const auto fontsData = getOptionalObjectArray(json, "list")) { + for (const auto &fontData : fontsData.value()) { + fonts.emplace_back(fontData); + } + } + } + + json11::Json::object toJson() const { + json11::Json::array fontArray; + + for (const auto &font : fonts) { + fontArray.push_back(font.toJson()); + } + + json11::Json::object result; + result.insert(std::make_pair("list", fontArray)); + return result; + } + +public: + std::vector fonts; +}; + +} + +#endif /* Font_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Font.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Font.swift new file mode 100644 index 00000000000..d78ee0dcdd3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Font.swift @@ -0,0 +1,61 @@ +// +// Font.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +// MARK: - Font + +final class Font: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + init(dictionary: [String: Any]) throws { + name = try dictionary.value(for: CodingKeys.name) + familyName = try dictionary.value(for: CodingKeys.familyName) + style = try dictionary.value(for: CodingKeys.style) + ascent = try dictionary.value(for: CodingKeys.ascent) + } + + // MARK: Internal + + let name: String + let familyName: String + let style: String + let ascent: Double + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case name = "fName" + case familyName = "fFamily" + case style = "fStyle" + case ascent + } + +} + +// MARK: - FontList + +/// A list of fonts +final class FontList: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + init(dictionary: [String: Any]) throws { + let fontDictionaries: [[String: Any]] = try dictionary.value(for: CodingKeys.fonts) + fonts = try fontDictionaries.map({ try Font(dictionary: $0) }) + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case fonts = "list" + } + + let fonts: [Font] + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Glyph.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Glyph.cpp new file mode 100644 index 00000000000..071050db7a7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Glyph.cpp @@ -0,0 +1,5 @@ +#include "Glyph.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Glyph.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Glyph.hpp new file mode 100644 index 00000000000..25d0af5fa9a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Glyph.hpp @@ -0,0 +1,108 @@ +#ifndef Glyph_hpp +#define Glyph_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class Glyph { +public: + Glyph( + std::string const &character_, + double fontSize_, + std::string const &fontFamily_, + std::string const &fontStyle_, + double width_, + std::optional>> shapes_ + ) : + character(character_), + fontSize(fontSize_), + fontFamily(fontFamily_), + fontStyle(fontStyle_), + width(width_), + shapes(shapes_) { + } + + explicit Glyph(json11::Json::object const &json) noexcept(false) : + character(""), + fontSize(0.0), + fontFamily(""), + fontStyle(""), + width(0.0) { + character = getString(json, "ch"); + fontSize = getDouble(json, "size"); + fontFamily = getString(json, "fFamily"); + fontStyle = getString(json, "style"); + width = getDouble(json, "w"); + + if (const auto shapeContainer = getOptionalObject(json, "data")) { + internalHasData = true; + + if (const auto shapesData = getOptionalObjectArray(shapeContainer.value(), "shapes")) { + shapes = std::vector>(); + + for (const auto &shapeData : shapesData.value()) { + shapes->push_back(parseShapeItem(shapeData)); + } + } + } + } + + json11::Json::object toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("ch", character)); + result.insert(std::make_pair("size", fontSize)); + result.insert(std::make_pair("fFamily", fontFamily)); + result.insert(std::make_pair("style", fontStyle)); + result.insert(std::make_pair("w", width)); + + if (internalHasData || shapes.has_value()) { + json11::Json::object shapeContainer; + + if (shapes.has_value()) { + json11::Json::array shapeArray; + + for (const auto &shape : shapes.value()) { + json11::Json::object shapeJson; + shape->toJson(shapeJson); + shapeArray.push_back(shapeJson); + } + + shapeContainer.insert(std::make_pair("shapes", shapeArray)); + } + result.insert(std::make_pair("data", shapeContainer)); + } + + return result; + } + +public: + /// The character + std::string character; + + /// The font size of the character + double fontSize; + + /// The font family of the character + std::string fontFamily; + + /// The Style of the character + std::string fontStyle; + + /// The Width of the character + double width; + + /// The Shape Data of the Character + std::optional>> shapes; + + bool internalHasData = false; +}; + +} + +#endif /* Glyph_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Glyph.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Glyph.swift new file mode 100644 index 00000000000..03a7c58e556 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/Glyph.swift @@ -0,0 +1,96 @@ +// +// Glyph.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +/// A model that holds a vector character +final class Glyph: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Glyph.CodingKeys.self) + character = try container.decode(String.self, forKey: .character) + fontSize = try container.decode(Double.self, forKey: .fontSize) + fontFamily = try container.decode(String.self, forKey: .fontFamily) + fontStyle = try container.decode(String.self, forKey: .fontStyle) + width = try container.decode(Double.self, forKey: .width) + if + container.contains(.shapeWrapper), + let shapeContainer = try? container.nestedContainer(keyedBy: ShapeKey.self, forKey: .shapeWrapper), + shapeContainer.contains(.shapes) + { + shapes = try shapeContainer.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .shapes) + } else { + shapes = [] + } + } + + init(dictionary: [String: Any]) throws { + character = try dictionary.value(for: CodingKeys.character) + fontSize = try dictionary.value(for: CodingKeys.fontSize) + fontFamily = try dictionary.value(for: CodingKeys.fontFamily) + fontStyle = try dictionary.value(for: CodingKeys.fontStyle) + width = try dictionary.value(for: CodingKeys.width) + if + let shapes = dictionary[CodingKeys.shapeWrapper.rawValue] as? [String: Any], + let shapeDictionaries = shapes[ShapeKey.shapes.rawValue] as? [[String: Any]] + { + self.shapes = try [ShapeItem].fromDictionaries(shapeDictionaries) + } else { + shapes = [ShapeItem]() + } + } + + // MARK: Internal + + /// The character + let character: String + + /// The font size of the character + let fontSize: Double + + /// The font family of the character + let fontFamily: String + + /// The Style of the character + let fontStyle: String + + /// The Width of the character + let width: Double + + /// The Shape Data of the Character + let shapes: [ShapeItem] + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(character, forKey: .character) + try container.encode(fontSize, forKey: .fontSize) + try container.encode(fontFamily, forKey: .fontFamily) + try container.encode(fontStyle, forKey: .fontStyle) + try container.encode(width, forKey: .width) + + var shapeContainer = container.nestedContainer(keyedBy: ShapeKey.self, forKey: .shapeWrapper) + try shapeContainer.encode(shapes, forKey: .shapes) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case character = "ch" + case fontSize = "size" + case fontFamily = "fFamily" + case fontStyle = "style" + case width = "w" + case shapeWrapper = "data" + } + + private enum ShapeKey: String, CodingKey { + case shapes + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextAnimator.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextAnimator.cpp new file mode 100644 index 00000000000..7cd9b257158 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextAnimator.cpp @@ -0,0 +1,5 @@ +#include "TextAnimator.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextAnimator.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextAnimator.hpp new file mode 100644 index 00000000000..e302a9051f5 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextAnimator.hpp @@ -0,0 +1,183 @@ +#ifndef TextAnimator_hpp +#define TextAnimator_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class TextAnimator { +public: + TextAnimator( + std::optional &name_, + std::optional> anchor_, + std::optional> position_, + std::optional> scale_, + std::optional> skew_, + std::optional> skewAxis_, + std::optional> rotation_, + std::optional> opacity_, + std::optional> strokeColor_, + std::optional> fillColor_, + std::optional> strokeWidth_, + std::optional> tracking_ + ) : + name(name_), + anchor(anchor_), + position(position_), + scale(scale_), + skew(skew_), + skewAxis(skewAxis_), + rotation(rotation_), + opacity(opacity_), + strokeColor(strokeColor_), + fillColor(fillColor_), + strokeWidth(strokeWidth_), + tracking(tracking_) { + } + + explicit TextAnimator(json11::Json const &jsonAny) { + if (!jsonAny.is_object()) { + throw LottieParsingException(); + } + json11::Json::object const &json = jsonAny.object_items(); + + if (const auto nameData = getOptionalString(json, "nm")) { + name = nameData.value(); + } + _extraS = getOptionalAny(json, "s"); + + json11::Json::object const &animatorContainer = getObject(json, "a"); + + if (const auto fillColorData = getOptionalObject(animatorContainer, "fc")) { + fillColor = KeyframeGroup(fillColorData.value()); + } + if (const auto strokeColorData = getOptionalObject(animatorContainer, "sc")) { + strokeColor = KeyframeGroup(strokeColorData.value()); + } + if (const auto strokeWidthData = getOptionalObject(animatorContainer, "sw")) { + strokeWidth = KeyframeGroup(strokeWidthData.value()); + } + if (const auto trackingData = getOptionalObject(animatorContainer, "t")) { + tracking = KeyframeGroup(trackingData.value()); + } + if (const auto anchorData = getOptionalObject(animatorContainer, "a")) { + anchor = KeyframeGroup(anchorData.value()); + } + if (const auto positionData = getOptionalObject(animatorContainer, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto scaleData = getOptionalObject(animatorContainer, "s")) { + scale = KeyframeGroup(scaleData.value()); + } + if (const auto skewData = getOptionalObject(animatorContainer, "sk")) { + skew = KeyframeGroup(skewData.value()); + } + if (const auto skewAxisData = getOptionalObject(animatorContainer, "sa")) { + skewAxis = KeyframeGroup(skewAxisData.value()); + } + if (const auto rotationData = getOptionalObject(animatorContainer, "r")) { + rotation = KeyframeGroup(rotationData.value()); + } + if (const auto opacityData = getOptionalObject(animatorContainer, "o")) { + opacity = KeyframeGroup(opacityData.value()); + } + } + + json11::Json::object toJson() const { + json11::Json::object animatorContainer; + + if (fillColor.has_value()) { + animatorContainer.insert(std::make_pair("fc", fillColor->toJson())); + } + if (strokeColor.has_value()) { + animatorContainer.insert(std::make_pair("sc", strokeColor->toJson())); + } + if (strokeWidth.has_value()) { + animatorContainer.insert(std::make_pair("sw", strokeWidth->toJson())); + } + if (tracking.has_value()) { + animatorContainer.insert(std::make_pair("t", tracking->toJson())); + } + if (anchor.has_value()) { + animatorContainer.insert(std::make_pair("a", anchor->toJson())); + } + if (position.has_value()) { + animatorContainer.insert(std::make_pair("p", position->toJson())); + } + if (scale.has_value()) { + animatorContainer.insert(std::make_pair("s", scale->toJson())); + } + if (skew.has_value()) { + animatorContainer.insert(std::make_pair("sk", skew->toJson())); + } + if (skewAxis.has_value()) { + animatorContainer.insert(std::make_pair("sa", skewAxis->toJson())); + } + if (rotation.has_value()) { + animatorContainer.insert(std::make_pair("r", rotation->toJson())); + } + if (opacity.has_value()) { + animatorContainer.insert(std::make_pair("o", opacity->toJson())); + } + + json11::Json::object result; + result.insert(std::make_pair("a", animatorContainer)); + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + if (_extraS.has_value()) { + result.insert(std::make_pair("s", _extraS.value())); + } + + return result; + } + +public: + std::optional name; + + /// Anchor + std::optional> anchor; + + /// Position + std::optional> position; + + /// Scale + std::optional> scale; + + /// Skew + std::optional> skew; + + /// Skew Axis + std::optional> skewAxis; + + /// Rotation + std::optional> rotation; + + /// Opacity + std::optional> opacity; + + /// Stroke Color + std::optional> strokeColor; + + /// Fill Color + std::optional> fillColor; + + /// Stroke Width + std::optional> strokeWidth; + + /// Tracking + std::optional> tracking; + + std::optional _extraS; +}; + +} + +#endif /* TextAnimator_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextAnimator.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextAnimator.swift new file mode 100644 index 00000000000..f8307267c1e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextAnimator.swift @@ -0,0 +1,165 @@ +// +// TextAnimator.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +final class TextAnimator: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: TextAnimator.CodingKeys.self) + name = try container.decodeIfPresent(String.self, forKey: .name) ?? "" + let animatorContainer = try container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator) + fillColor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .fillColor) + strokeColor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .strokeColor) + strokeWidth = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .strokeWidth) + tracking = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .tracking) + anchor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .anchor) + position = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .position) + scale = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .scale) + skew = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .skew) + skewAxis = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .skewAxis) + rotation = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) + opacity = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) + + } + + init(dictionary: [String: Any]) throws { + name = (try? dictionary.value(for: CodingKeys.name)) ?? "" + let animatorDictionary: [String: Any] = try dictionary.value(for: CodingKeys.textAnimator) + if let fillColorDictionary = animatorDictionary[TextAnimatorKeys.fillColor.rawValue] as? [String: Any] { + fillColor = try? KeyframeGroup(dictionary: fillColorDictionary) + } else { + fillColor = nil + } + if let strokeColorDictionary = animatorDictionary[TextAnimatorKeys.strokeColor.rawValue] as? [String: Any] { + strokeColor = try? KeyframeGroup(dictionary: strokeColorDictionary) + } else { + strokeColor = nil + } + if let strokeWidthDictionary = animatorDictionary[TextAnimatorKeys.strokeWidth.rawValue] as? [String: Any] { + strokeWidth = try? KeyframeGroup(dictionary: strokeWidthDictionary) + } else { + strokeWidth = nil + } + if let trackingDictionary = animatorDictionary[TextAnimatorKeys.tracking.rawValue] as? [String: Any] { + tracking = try? KeyframeGroup(dictionary: trackingDictionary) + } else { + tracking = nil + } + if let anchorDictionary = animatorDictionary[TextAnimatorKeys.anchor.rawValue] as? [String: Any] { + anchor = try? KeyframeGroup(dictionary: anchorDictionary) + } else { + anchor = nil + } + if let positionDictionary = animatorDictionary[TextAnimatorKeys.position.rawValue] as? [String: Any] { + position = try? KeyframeGroup(dictionary: positionDictionary) + } else { + position = nil + } + if let scaleDictionary = animatorDictionary[TextAnimatorKeys.scale.rawValue] as? [String: Any] { + scale = try? KeyframeGroup(dictionary: scaleDictionary) + } else { + scale = nil + } + if let skewDictionary = animatorDictionary[TextAnimatorKeys.skew.rawValue] as? [String: Any] { + skew = try? KeyframeGroup(dictionary: skewDictionary) + } else { + skew = nil + } + if let skewAxisDictionary = animatorDictionary[TextAnimatorKeys.skewAxis.rawValue] as? [String: Any] { + skewAxis = try? KeyframeGroup(dictionary: skewAxisDictionary) + } else { + skewAxis = nil + } + if let rotationDictionary = animatorDictionary[TextAnimatorKeys.rotation.rawValue] as? [String: Any] { + rotation = try? KeyframeGroup(dictionary: rotationDictionary) + } else { + rotation = nil + } + if let opacityDictionary = animatorDictionary[TextAnimatorKeys.opacity.rawValue] as? [String: Any] { + opacity = try KeyframeGroup(dictionary: opacityDictionary) + } else { + opacity = nil + } + } + + // MARK: Internal + + let name: String + + /// Anchor + let anchor: KeyframeGroup? + + /// Position + let position: KeyframeGroup? + + /// Scale + let scale: KeyframeGroup? + + /// Skew + let skew: KeyframeGroup? + + /// Skew Axis + let skewAxis: KeyframeGroup? + + /// Rotation + let rotation: KeyframeGroup? + + /// Opacity + let opacity: KeyframeGroup? + + /// Stroke Color + let strokeColor: KeyframeGroup? + + /// Fill Color + let fillColor: KeyframeGroup? + + /// Stroke Width + let strokeWidth: KeyframeGroup? + + /// Tracking + let tracking: KeyframeGroup? + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var animatorContainer = container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator) + try animatorContainer.encodeIfPresent(fillColor, forKey: .fillColor) + try animatorContainer.encodeIfPresent(strokeColor, forKey: .strokeColor) + try animatorContainer.encodeIfPresent(strokeWidth, forKey: .strokeWidth) + try animatorContainer.encodeIfPresent(tracking, forKey: .tracking) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { +// case textSelector = "s" TODO + case textAnimator = "a" + case name = "nm" + } + + private enum TextSelectorKeys: String, CodingKey { + case start = "s" + case end = "e" + case offset = "o" + } + + private enum TextAnimatorKeys: String, CodingKey { + case fillColor = "fc" + case strokeColor = "sc" + case strokeWidth = "sw" + case tracking = "t" + case anchor = "a" + case position = "p" + case scale = "s" + case skew = "sk" + case skewAxis = "sa" + case rotation = "r" + case opacity = "o" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextDocument.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextDocument.cpp new file mode 100644 index 00000000000..95c3be2b676 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextDocument.cpp @@ -0,0 +1,5 @@ +#include "TextDocument.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextDocument.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextDocument.hpp new file mode 100644 index 00000000000..9eb3b1f154f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextDocument.hpp @@ -0,0 +1,184 @@ +#ifndef TextDocument_hpp +#define TextDocument_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +enum class TextJustification: int { + Left = 0, + Right = 1, + Center = 2 +}; + +class TextDocument { +public: + TextDocument( + std::string const &text_, + double fontSize_, + std::string const &fontFamily_, + TextJustification justification_, + int tracking_, + double lineHeight_, + std::optional baseline_, + std::optional fillColorData_, + std::optional strokeColorData_, + std::optional strokeWidth_, + std::optional strokeOverFill_, + std::optional textFramePosition_, + std::optional textFrameSize_ + ) : + text(text_), + fontSize(fontSize_), + fontFamily(fontFamily_), + justification(justification_), + tracking(tracking_), + lineHeight(lineHeight_), + baseline(baseline_), + fillColorData(fillColorData_), + strokeColorData(strokeColorData_), + strokeWidth(strokeWidth_), + strokeOverFill(strokeOverFill_), + textFramePosition(textFramePosition_), + textFrameSize(textFrameSize_) { + } + + explicit TextDocument(json11::Json const &jsonAny) noexcept(false) : + text(""), + fontSize(0.0), + fontFamily(""), + justification(TextJustification::Left), + tracking(0), + lineHeight(0.0) { + if (!jsonAny.is_object()) { + throw LottieParsingException(); + } + + json11::Json::object const &json = jsonAny.object_items(); + + text = getString(json, "t"); + fontSize = getDouble(json, "s"); + fontFamily = getString(json, "f"); + + auto justificationRawValue = getInt(json, "j"); + switch (justificationRawValue) { + case 0: + justification = TextJustification::Left; + break; + case 1: + justification = TextJustification::Right; + break; + case 2: + justification = TextJustification::Center; + break; + default: + throw LottieParsingException(); + } + + tracking = getInt(json, "tr"); + lineHeight = getDouble(json, "lh"); + baseline = getOptionalDouble(json, "ls"); + + if (const auto fillColorDataValue = getOptionalAny(json, "fc")) { + fillColorData = Color(fillColorDataValue.value()); + } + + if (const auto strokeColorDataValue = getOptionalAny(json, "sc")) { + strokeColorData = Color(strokeColorDataValue.value()); + } + + strokeWidth = getOptionalDouble(json, "sw"); + strokeOverFill = getOptionalBool(json, "of"); + + if (const auto textFramePositionData = getOptionalAny(json, "ps")) { + textFramePosition = Vector3D(textFramePositionData.value()); + } + if (const auto textFrameSizeData = getOptionalAny(json, "sz")) { + textFrameSize = Vector3D(textFrameSizeData.value()); + } + } + + json11::Json::object toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("t", text)); + result.insert(std::make_pair("s", fontSize)); + result.insert(std::make_pair("f", fontFamily)); + result.insert(std::make_pair("j", (int)justification)); + result.insert(std::make_pair("tr", tracking)); + result.insert(std::make_pair("lh", lineHeight)); + + if (baseline.has_value()) { + result.insert(std::make_pair("ls", baseline.value())); + } + + if (fillColorData.has_value()) { + result.insert(std::make_pair("fc", fillColorData->toJson())); + } + if (strokeColorData.has_value()) { + result.insert(std::make_pair("sc", strokeColorData->toJson())); + } + + if (strokeWidth.has_value()) { + result.insert(std::make_pair("sw", strokeWidth.value())); + } + if (strokeOverFill.has_value()) { + result.insert(std::make_pair("of", strokeOverFill.value())); + } + if (textFramePosition.has_value()) { + result.insert(std::make_pair("ps", textFramePosition->toJson())); + } + if (textFrameSize.has_value()) { + result.insert(std::make_pair("sz", textFrameSize->toJson())); + } + + return result; + } + +public: + /// The Text + std::string text; + + /// The Font size + double fontSize; + + /// The Font Family + std::string fontFamily; + + /// Justification + TextJustification justification; + + /// Tracking + int tracking; + + /// Line Height + double lineHeight; + + /// Baseline + std::optional baseline; + + /// Fill Color data + std::optional fillColorData; + + /// Scroke Color data + std::optional strokeColorData; + + /// Stroke Width + std::optional strokeWidth; + + /// Stroke Over Fill + std::optional strokeOverFill; + + std::optional textFramePosition; + + std::optional textFrameSize; +}; + +} + +#endif /* TextDocument_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextDocument.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextDocument.swift new file mode 100644 index 00000000000..c32c0428637 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Model/Text/TextDocument.swift @@ -0,0 +1,123 @@ +// +// TextDocument.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +// MARK: - TextJustification + +enum TextJustification: Int, Codable { + case left + case right + case center +} + +// MARK: - TextDocument + +final class TextDocument: Codable, DictionaryInitializable, AnyInitializable { + + // MARK: Lifecycle + + init(dictionary: [String: Any]) throws { + text = try dictionary.value(for: CodingKeys.text) + fontSize = try dictionary.value(for: CodingKeys.fontSize) + fontFamily = try dictionary.value(for: CodingKeys.fontFamily) + let justificationValue: Int = try dictionary.value(for: CodingKeys.justification) + guard let justification = TextJustification(rawValue: justificationValue) else { + throw InitializableError.invalidInput + } + self.justification = justification + tracking = try dictionary.value(for: CodingKeys.tracking) + lineHeight = try dictionary.value(for: CodingKeys.lineHeight) + baseline = try dictionary.value(for: CodingKeys.baseline) + if let fillColorRawValue = dictionary[CodingKeys.fillColorData.rawValue] { + fillColorData = try? Color(value: fillColorRawValue) + } else { + fillColorData = nil + } + if let strokeColorRawValue = dictionary[CodingKeys.strokeColorData.rawValue] { + strokeColorData = try? Color(value: strokeColorRawValue) + } else { + strokeColorData = nil + } + strokeWidth = try? dictionary.value(for: CodingKeys.strokeWidth) + strokeOverFill = try? dictionary.value(for: CodingKeys.strokeOverFill) + if let textFramePositionRawValue = dictionary[CodingKeys.textFramePosition.rawValue] { + textFramePosition = try? Vector3D(value: textFramePositionRawValue) + } else { + textFramePosition = nil + } + if let textFrameSizeRawValue = dictionary[CodingKeys.textFrameSize.rawValue] { + textFrameSize = try? Vector3D(value: textFrameSizeRawValue) + } else { + textFrameSize = nil + } + } + + convenience init(value: Any) throws { + guard let dictionary = value as? [String: Any] else { + throw InitializableError.invalidInput + } + try self.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The Text + let text: String + + /// The Font size + let fontSize: Double + + /// The Font Family + let fontFamily: String + + /// Justification + let justification: TextJustification + + /// Tracking + let tracking: Int + + /// Line Height + let lineHeight: Double + + /// Baseline + let baseline: Double? + + /// Fill Color data + let fillColorData: Color? + + /// Scroke Color data + let strokeColorData: Color? + + /// Stroke Width + let strokeWidth: Double? + + /// Stroke Over Fill + let strokeOverFill: Bool? + + let textFramePosition: Vector3D? + + let textFrameSize: Vector3D? + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case text = "t" + case fontSize = "s" + case fontFamily = "f" + case justification = "j" + case tracking = "tr" + case lineHeight = "lh" + case baseline = "ls" + case fillColorData = "fc" + case strokeColorData = "sc" + case strokeWidth = "sw" + case strokeOverFill = "of" + case textFramePosition = "ps" + case textFrameSize = "sz" + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Parsing/JsonParsing.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Parsing/JsonParsing.cpp new file mode 100644 index 00000000000..da1dcb51e60 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Parsing/JsonParsing.cpp @@ -0,0 +1,219 @@ +#include "JsonParsing.hpp" + +#include + +namespace lottie { + +thread_local int isExceptionExpectedLevel = 0; + +LottieParsingException::Guard::Guard() { + assert(isExceptionExpectedLevel >= 0); + isExceptionExpectedLevel++; +} + +LottieParsingException::Guard::~Guard() { + assert(isExceptionExpectedLevel - 1 >= 0); + isExceptionExpectedLevel--; +} + +LottieParsingException::LottieParsingException() { + if (isExceptionExpectedLevel == 0) { + assert(true); + } +} + +const char* LottieParsingException::what() const throw() { + return "Lottie parsing exception"; +} + +json11::Json getAny(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + return value->second; +} + +std::optional getOptionalAny(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + return value->second; +} + +json11::Json::object getObject(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_object()) { + throw LottieParsingException(); + } + return value->second.object_items(); +} + +std::optional getOptionalObject(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_object()) { + throw LottieParsingException(); + } + return value->second.object_items(); +} + +std::vector getObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + std::vector result; + for (const auto &item : value->second.array_items()) { + if (!item.is_object()) { + throw LottieParsingException(); + } + result.push_back(item.object_items()); + } + + return result; +} + +std::optional> getOptionalObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + std::vector result; + for (const auto &item : value->second.array_items()) { + if (!item.is_object()) { + throw LottieParsingException(); + } + result.push_back(item.object_items()); + } + + return result; +} + +std::vector getAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + return value->second.array_items(); +} + +std::optional> getOptionalAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw std::nullopt; + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + return value->second.array_items(); +} + +std::string getString(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_string()) { + throw LottieParsingException(); + } + return value->second.string_value(); +} + +std::optional getOptionalString(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_string()) { + throw LottieParsingException(); + } + return value->second.string_value(); +} + +int32_t getInt(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.int_value(); +} + +std::optional getOptionalInt(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.int_value(); +} + +double getDouble(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.number_value(); +} + +std::optional getOptionalDouble(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.number_value(); +} + +bool getBool(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_bool()) { + throw LottieParsingException(); + } + return value->second.bool_value(); +} + +std::optional getOptionalBool(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_bool()) { + throw LottieParsingException(); + } + return value->second.bool_value(); +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Parsing/JsonParsing.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Parsing/JsonParsing.hpp new file mode 100644 index 00000000000..9d4c760d470 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Parsing/JsonParsing.hpp @@ -0,0 +1,52 @@ +#ifndef JsonParsing_hpp +#define JsonParsing_hpp + +#include "json11/json11.hpp" + +#include +#include +#include + +namespace lottie { + +class LottieParsingException: public std::exception { +public: + class Guard { + public: + Guard(); + ~Guard(); + }; + +public: + LottieParsingException(); + + virtual const char* what() const throw(); +}; + +json11::Json getAny(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalAny(json11::Json::object const &object, std::string const &key) noexcept(false); + +json11::Json::object getObject(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalObject(json11::Json::object const &object, std::string const &key) noexcept(false); + +std::vector getObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional> getOptionalObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false); + +std::vector getAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional> getOptionalAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false); + +std::string getString(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalString(json11::Json::object const &object, std::string const &key) noexcept(false); + +int32_t getInt(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalInt(json11::Json::object const &object, std::string const &key) noexcept(false); + +double getDouble(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalDouble(json11::Json::object const &object, std::string const &key) noexcept(false); + +bool getBool(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalBool(json11::Json::object const &object, std::string const &key) noexcept(false); + +} + +#endif /* JsonParsing_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/RootAnimationLayer.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/RootAnimationLayer.swift new file mode 100644 index 00000000000..d3ab01085e5 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/RootAnimationLayer.swift @@ -0,0 +1,51 @@ +// Created by Cal Stephens on 12/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - RootAnimationLayer + +/// A root `CALayer` responsible for playing a Lottie animation +protocol RootAnimationLayer: CALayer { + var currentFrame: AnimationFrameTime { get set } + var renderScale: CGFloat { get set } + var respectAnimationFrameRate: Bool { get set } + + var _animationLayers: [CALayer] { get } + var imageProvider: AnimationImageProvider { get set } + var textProvider: AnimationTextProvider { get set } + var fontProvider: AnimationFontProvider { get set } + + /// The `CAAnimation` key corresponding to the primary animation. + /// - `AnimationView` uses this key to check if the animation is still active + var primaryAnimationKey: AnimationKey { get } + + /// Whether or not this layer is currently playing an animation + /// - If the layer returns `nil`, `AnimationView` determines if an animation + /// is playing by checking if there is an active animation for `primaryAnimationKey` + var isAnimationPlaying: Bool? { get } + + /// Instructs this layer to remove all `CAAnimation`s, + /// other than the `CAAnimation` managed by `AnimationView` (if applicable) + func removeAnimations() + + func reloadImages() + func forceDisplayUpdate() + func logHierarchyKeypaths() + + func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) + func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? + func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? + + func layer(for keypath: AnimationKeypath) -> CALayer? + func animatorNodes(for keypath: AnimationKeypath) -> [AnimatorNode]? +} + +// MARK: - AnimationKey + +enum AnimationKey { + /// The primary animation and its key should be managed by `AnimationView` + case managed + /// The primary animation always uses the given key + case specific(String) +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Debugging/AnimatorNodeDebugging.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Debugging/AnimatorNodeDebugging.swift new file mode 100644 index 00000000000..3e8a5114812 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Debugging/AnimatorNodeDebugging.swift @@ -0,0 +1,25 @@ +// +// AnimatorNodeDebugging.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/18/19. +// + +import Foundation + +extension AnimatorNode { + + func printNodeTree() { + parentNode?.printNodeTree() + print(String(describing: type(of: self))) + + if let group = self as? GroupNode { + print("* |Children") + group.rootNode?.printNodeTree() + print("*") + } else { + print("|") + } + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Debugging/LayerDebugging.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Debugging/LayerDebugging.swift new file mode 100644 index 00000000000..c8298ddff9b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Debugging/LayerDebugging.swift @@ -0,0 +1,228 @@ +// +// LayerDebugging.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/24/19. +// + +import Foundation +import QuartzCore + +// MARK: - LayerDebugStyle + +struct LayerDebugStyle { + let anchorColor: CGColor + let boundsColor: CGColor + let anchorWidth: CGFloat + let boundsWidth: CGFloat +} + +// MARK: - LayerDebugging + +protocol LayerDebugging { + var debugStyle: LayerDebugStyle { get } +} + +// MARK: - CustomLayerDebugging + +protocol CustomLayerDebugging { + func layerForDebugging() -> CALayer +} + +// MARK: - DebugLayer + +class DebugLayer: CALayer { + init(style: LayerDebugStyle) { + super.init() + zPosition = 1000 + bounds = CGRect(x: 0, y: 0, width: style.anchorWidth, height: style.anchorWidth) + backgroundColor = style.anchorColor + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension CALayer { + + @nonobjc + public func logLayerTree(withIndent: Int = 0) { + var string = "" + for _ in 0...withIndent { + string = string + " " + } + string = string + "|_" + String(describing: self) + print(string) + if let sublayers = sublayers { + for sublayer in sublayers { + sublayer.logLayerTree(withIndent: withIndent + 1) + } + } + } + +} + +// MARK: - CompositionLayer + CustomLayerDebugging + +extension CompositionLayer: CustomLayerDebugging { + func layerForDebugging() -> CALayer { + contentsLayer + } +} + +extension CALayer { + + @nonobjc + func setDebuggingState(visible: Bool) { + + var sublayers = self.sublayers + if let cust = self as? CustomLayerDebugging { + sublayers = cust.layerForDebugging().sublayers + } + + if let sublayers = sublayers { + for i in 0.. LayerDebugStyle { + let colorSpace = CGColorSpaceCreateDeviceRGB() + + let anchorColor = CGColor(colorSpace: colorSpace, components: [1, 0, 0, 1])! + let boundsColor = CGColor(colorSpace: colorSpace, components: [1, 1, 0, 1])! + return LayerDebugStyle( + anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func topLayerStyle() -> LayerDebugStyle { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let anchorColor = CGColor(colorSpace: colorSpace, components: [1, 0.5, 0, 0])! + let boundsColor = CGColor(colorSpace: colorSpace, components: [0, 1, 0, 1])! + + return LayerDebugStyle( + anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func nullLayerStyle() -> LayerDebugStyle { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let anchorColor = CGColor(colorSpace: colorSpace, components: [0, 0, 1, 0])! + let boundsColor = CGColor(colorSpace: colorSpace, components: [0, 1, 0, 1])! + + return LayerDebugStyle( + anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func shapeLayerStyle() -> LayerDebugStyle { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let anchorColor = CGColor(colorSpace: colorSpace, components: [0, 1, 0, 0])! + let boundsColor = CGColor(colorSpace: colorSpace, components: [0, 1, 0, 1])! + + return LayerDebugStyle( + anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func shapeRenderLayerStyle() -> LayerDebugStyle { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let anchorColor = CGColor(colorSpace: colorSpace, components: [0, 1, 1, 0])! + let boundsColor = CGColor(colorSpace: colorSpace, components: [0, 1, 0, 1])! + + return LayerDebugStyle( + anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } +} + +extension Array where Element == LayerModel { + + var parents: [Int] { + var array = [Int]() + for layer in self { + if let parent = layer.parent { + array.append(parent) + } else { + array.append(-1) + } + } + return array + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Debugging/TestHelpers.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Debugging/TestHelpers.swift new file mode 100644 index 00000000000..cfe5d4813e7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Debugging/TestHelpers.swift @@ -0,0 +1,10 @@ +// Created by Cal Stephens on 1/28/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +enum TestHelpers { + /// Whether or not snapshot tests are currently running in a test target + static var snapshotTestsAreRunning = false + + /// Whether or not performance tests are currently running in a test target + static var performanceTestsAreRunning = false +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift new file mode 100644 index 00000000000..08b6e3aa6da --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift @@ -0,0 +1,266 @@ +// +// KeypathSearchableExtension.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +import QuartzCore + +extension KeypathSearchable { + + func animatorNodes(for keyPath: AnimationKeypath) -> [AnimatorNode]? { + // Make sure there is a current key path. + guard let currentKey = keyPath.currentKey else { return nil } + + // Now try popping the keypath for wildcard / child search + guard let nextKeypath = keyPath.popKey(keypathName) else { + // We may be on the final keypath. Check for match. + if + let node = self as? AnimatorNode, + currentKey.equalsKeypath(keypathName) + { + // This is the final keypath and matches self. Return.s + return [node] + } + /// Nope. Stop Search + return nil + } + + var results: [AnimatorNode] = [] + + if + let node = self as? AnimatorNode, + nextKeypath.currentKey == nil + { + // Keypath matched self and was the final keypath. + results.append(node) + } + + for childNode in childKeypaths { + // Check if the child has any nodes matching the next keypath. + if let foundNodes = childNode.animatorNodes(for: nextKeypath) { + results.append(contentsOf: foundNodes) + } + + // In this case the current key is fuzzy, and both child and self match the next keyname. Keep digging! + if + currentKey.keyPathType == .fuzzyWildcard, + let nextKeypath = keyPath.nextKeypath, + nextKeypath.equalsKeypath(childNode.keypathName), + let foundNodes = childNode.animatorNodes(for: keyPath) + { + results.append(contentsOf: foundNodes) + } + } + + guard results.count > 0 else { + return nil + } + + return results + } + + func nodeProperties(for keyPath: AnimationKeypath) -> [AnyNodeProperty]? { + guard let nextKeypath = keyPath.popKey(keypathName) else { + /// Nope. Stop Search + return nil + } + + /// Keypath matches in some way. Continue the search. + var results: [AnyNodeProperty] = [] + + /// Check if we have a property keypath yet + if + let propertyKey = nextKeypath.propertyKey, + let property = keypathProperties[propertyKey] + { + /// We found a property! + results.append(property) + } + + if nextKeypath.nextKeypath != nil { + /// Now check child keypaths. + for child in childKeypaths { + if let childProperties = child.nodeProperties(for: nextKeypath) { + results.append(contentsOf: childProperties) + } + } + } + + guard results.count > 0 else { + return nil + } + + return results + } + + func layer(for keyPath: AnimationKeypath) -> CALayer? { + if keyPath.nextKeypath == nil, let layerKey = keyPath.currentKey, layerKey.equalsKeypath(keypathName) { + /// We found our layer! + return keypathLayer + } + guard let nextKeypath = keyPath.popKey(keypathName) else { + /// Nope. Stop Search + return nil + } + + /// Now check child keypaths. + for child in childKeypaths { + if let layer = child.layer(for: nextKeypath) { + return layer + } + } + return nil + } + + func logKeypaths(for keyPath: AnimationKeypath?) { + let newKeypath: AnimationKeypath + if let previousKeypath = keyPath { + newKeypath = previousKeypath.appendingKey(keypathName) + } else { + newKeypath = AnimationKeypath(keys: [keypathName]) + } + print(newKeypath.fullPath) + for key in keypathProperties.keys { + print(newKeypath.appendingKey(key).fullPath) + } + for child in childKeypaths { + child.logKeypaths(for: newKeypath) + } + } +} + +extension AnimationKeypath { + var currentKey: String? { + keys.first + } + + var nextKeypath: String? { + guard keys.count > 1 else { + return nil + } + return keys[1] + } + + var propertyKey: String? { + if nextKeypath == nil { + /// There are no more keypaths. This is a property key. + return currentKey + } + if keys.count == 2, currentKey?.keyPathType == .fuzzyWildcard { + /// The next keypath is the last and the current is a fuzzy key. + return nextKeypath + } + return nil + } + + var fullPath: String { + keys.joined(separator: ".") + } + + // Pops the top keypath from the stack if the keyname matches. + func popKey(_ keyname: String) -> AnimationKeypath? { + guard + let currentKey = currentKey, + currentKey.equalsKeypath(keyname), + keys.count > 1 else + { + // Current key either doesnt match or we are on the last key. + return nil + } + + // Pop the keypath from the stack and return the new stack. + let newKeys: [String] + + if currentKey.keyPathType == .fuzzyWildcard { + /// Dont remove if current key is a fuzzy wildcard, and if the next keypath doesnt equal keypathname + if + let nextKeypath = nextKeypath, + nextKeypath.equalsKeypath(keyname) + { + /// Remove next two keypaths. This keypath breaks the wildcard. + var oldKeys = keys + oldKeys.remove(at: 0) + oldKeys.remove(at: 0) + newKeys = oldKeys + } else { + newKeys = keys + } + } else { + var oldKeys = keys + oldKeys.remove(at: 0) + newKeys = oldKeys + } + + return AnimationKeypath(keys: newKeys) + } + + func appendingKey(_ key: String) -> AnimationKeypath { + var newKeys = keys + newKeys.append(key) + return AnimationKeypath(keys: newKeys) + } +} + +extension String { + var keyPathType: KeyType { + switch self { + case "*": + return .wildcard + case "**": + return .fuzzyWildcard + default: + return .specific + } + } + + func equalsKeypath(_ keyname: String) -> Bool { + if keyPathType == .wildcard || keyPathType == .fuzzyWildcard { + return true + } + if self == keyname { + return true + } + if let index = firstIndex(of: "*") { + // Wildcard search. + let prefix = String(self.prefix(upTo: index)) + let suffix = String(self.suffix(from: self.index(after: index))) + + if prefix.count > 0 { + // Match prefix. + if keyname.count < prefix.count { + return false + } + let testPrefix = String(keyname.prefix(upTo: keyname.index(keyname.startIndex, offsetBy: prefix.count))) + if testPrefix != prefix { + // Prefix doesnt match + return false + } + } + if suffix.count > 0 { + // Match suffix. + if keyname.count < suffix.count { + // Suffix doesnt match + return false + } + let index = keyname.index(keyname.endIndex, offsetBy: -suffix.count) + let testSuffix = String(keyname.suffix(from: index)) + if testSuffix != suffix { + return false + } + } + return true + } + return false + } +} + +// MARK: - KeyType + +enum KeyType { + case specific + case wildcard + case fuzzyWildcard +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/BlendMode+Filter.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/BlendMode+Filter.swift new file mode 100644 index 00000000000..ef93a39c25d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/BlendMode+Filter.swift @@ -0,0 +1,31 @@ +// +// File.swift +// +// +// Created by Denis Koryttsev on 10.05.2022. +// + +extension BlendMode { + /// The Core Image filter name for this `BlendMode`, that can be applied to a `CALayer`'s `compositingFilter`. + /// Supported compositing filters are defined here: https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/uid/TP30000136-SW71 + var filterName: String? { + switch self { + case .normal: return nil + case .multiply: return "multiplyBlendMode" + case .screen: return "screenBlendMode" + case .overlay: return "overlayBlendMode" + case .darken: return "darkenBlendMode" + case .lighten: return "lightenBlendMode" + case .colorDodge: return "colorDodgeBlendMode" + case .colorBurn: return "colorBurnBlendMode" + case .hardLight: return "hardLightBlendMode" + case .softLight: return "softLightBlendMode" + case .difference: return "differenceBlendMode" + case .exclusion: return "exclusionBlendMode" + case .hue: return "hueBlendMode" + case .saturation: return "saturationBlendMode" + case .color: return "colorBlendMode" + case .luminosity: return "luminosityBlendMode" + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/CGColor+RGB.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/CGColor+RGB.swift new file mode 100644 index 00000000000..c1e2a5c47d4 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/CGColor+RGB.swift @@ -0,0 +1,22 @@ +// Created by Cal Stephens on 1/7/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CGColor { + /// Initializes a `CGColor` using the given `RGB` values + static func rgb(_ red: CGFloat, _ green: CGFloat, _ blue: CGFloat) -> CGColor { + if #available(iOS 13.0, tvOS 13.0, macOS 10.5, *) { + return CGColor(red: red, green: green, blue: blue, alpha: 1) + } else { + return CGColor( + colorSpace: CGColorSpaceCreateDeviceRGB(), + components: [red, green, blue])! + } + } + + /// Initializes a `CGColor` using the given `RGBA` values + static func rgba(_ red: CGFloat, _ green: CGFloat, _ blue: CGFloat, _ alpha: CGFloat) -> CGColor { + CGColor.rgb(red, green, blue).copy(alpha: alpha)! + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/CGFloatExtensions.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/CGFloatExtensions.swift new file mode 100644 index 00000000000..939725a331a --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/CGFloatExtensions.swift @@ -0,0 +1,156 @@ +// +// CGFloatExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import Foundation +import QuartzCore + +extension CGFloat { + + // MARK: Internal + + var squared: CGFloat { + self * self + } + + var cubed: CGFloat { + self * self * self + } + + var cubicRoot: CGFloat { + CGFloat(pow(Double(self), 1.0 / 3.0)) + } + + func isInRangeOrEqual(_ from: CGFloat, _ to: CGFloat) -> Bool { + let from = Float(from) + let to = Float(to) + return from <= Float(self) && Float(self) <= to + } + + func isInRange(_ from: CGFloat, _ to: CGFloat) -> Bool { + let from = Float(from) + let to = Float(to) + return from < Float(self) && Float(self) < to + } + + func cubicBezierInterpolate(_ P0: CGPoint, _ P1: CGPoint, _ P2: CGPoint, _ P3: CGPoint) -> CGFloat { + var t: CGFloat + if self == P0.x { + // Handle corner cases explicitly to prevent rounding errors + t = 0 + } else if self == P3.x { + t = 1 + } else { + // Calculate t + let a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x; + let b = 3 * P0.x - 6 * P1.x + 3 * P2.x; + let c = -3 * P0.x + 3 * P1.x; + let d = P0.x - self; + let tTemp = CGFloat.SolveCubic(a, b, c, d); + if tTemp == -1 { + return -1; + } + t = tTemp + } + + // Calculate y from t + return (1 - t).cubed * P0.y + 3 * t * (1 - t).squared * P1.y + 3 * t.squared * (1 - t) * P2.y + t.cubed * P3.y; + } + + func cubicBezier(_ t: CGFloat, _ c1: CGFloat, _ c2: CGFloat, _ end: CGFloat) -> CGFloat { + let t_ = (1.0 - t) + let tt_ = t_ * t_ + let ttt_ = t_ * t_ * t_ + let tt = t * t + let ttt = t * t * t + + return self * ttt_ + + 3.0 * c1 * tt_ * t + + 3.0 * c2 * t_ * tt + + end * ttt; + } + + // MARK: Fileprivate + + fileprivate static func SolveQuadratic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat) -> CGFloat { + var result = (-b + sqrt(b.squared - 4 * a * c)) / (2 * a); + guard !result.isInRangeOrEqual(0, 1) else { + return result + } + + result = (-b - sqrt(b.squared - 4 * a * c)) / (2 * a); + guard !result.isInRangeOrEqual(0, 1) else { + return result + } + + return -1; + } + + fileprivate static func SolveCubic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat, _ d: CGFloat) -> CGFloat { + if a == 0 { + return SolveQuadratic(b, c, d) + } + if d == 0 { + return 0 + } + let a = a + var b = b + var c = c + var d = d + b /= a + c /= a + d /= a + var q = (3.0 * c - b.squared) / 9.0 + let r = (-27.0 * d + b * (9.0 * c - 2.0 * b.squared)) / 54.0 + let disc = q.cubed + r.squared + let term1 = b / 3.0 + + if disc > 0 { + var s = r + sqrt(disc) + s = (s < 0) ? -((-s).cubicRoot) : s.cubicRoot + var t = r - sqrt(disc) + t = (t < 0) ? -((-t).cubicRoot) : t.cubicRoot + + let result = -term1 + s + t; + if result.isInRangeOrEqual(0, 1) { + return result + } + } else if disc == 0 { + let r13 = (r < 0) ? -((-r).cubicRoot) : r.cubicRoot; + + var result = -term1 + 2.0 * r13; + if result.isInRangeOrEqual(0, 1) { + return result + } + + result = -(r13 + term1); + if result.isInRangeOrEqual(0, 1) { + return result + } + + } else { + q = -q; + var dum1 = q * q * q; + dum1 = acos(r / sqrt(dum1)); + let r13 = 2.0 * sqrt(q); + + var result = -term1 + r13 * cos(dum1 / 3.0); + if result.isInRangeOrEqual(0, 1) { + return result + } + result = -term1 + r13 * cos((dum1 + 2.0 * .pi) / 3.0); + if result.isInRangeOrEqual(0, 1) { + return result + } + result = -term1 + r13 * cos((dum1 + 4.0 * .pi) / 3.0); + if result.isInRangeOrEqual(0, 1) { + return result + } + } + + return -1; + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/DataExtension.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/DataExtension.swift new file mode 100644 index 00000000000..e029be93dd8 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/DataExtension.swift @@ -0,0 +1,27 @@ +// +// DataExtension.swift +// Lottie +// +// Created by René Fouquet on 03.05.21. +// + +import Foundation +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +extension Data { + + static func jsonData(from assetName: String, in bundle: Bundle) -> Data? { + #if canImport(UIKit) + return NSDataAsset(name: assetName, bundle: bundle)?.data + #else + if #available(macOS 10.11, *) { + return NSDataAsset(name: assetName, bundle: bundle)?.data + } + return nil + #endif + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/MathKit.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/MathKit.swift new file mode 100644 index 00000000000..63d5465a0bf --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/MathKit.swift @@ -0,0 +1,451 @@ +// +// MathKit.swift +// UIToolBox +// +// Created by Brandon Withrow on 10/10/18. +// +// From https://github.com/buba447/UIToolBox + +import CoreGraphics +import Foundation + +extension Int { + var cgFloat: CGFloat { + CGFloat(self) + } +} + +extension Double { + var cgFloat: CGFloat { + CGFloat(self) + } +} + +// MARK: - CGFloat + Interpolatable + +extension CGFloat { + + func remap(fromLow: CGFloat, fromHigh: CGFloat, toLow: CGFloat, toHigh: CGFloat) -> CGFloat { + guard (fromHigh - fromLow) != 0 else { + // Would produce NAN + return 0 + } + return toLow + (self - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + } + + /// Returns a value that is clamped between the two numbers + /// + /// 1. The order of arguments does not matter. + func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat { + CGFloat(Double(self).clamp(Double(a), Double(b))) + } + + /// Returns the difference between the receiver and the given number. + /// - Parameter absolute: If *true* (Default) the returned value will always be positive. + func diff(_ a: CGFloat, absolute: Bool = true) -> CGFloat { + absolute ? abs(a - self) : a - self + } + + func toRadians() -> CGFloat { self * .pi / 180 } + func toDegrees() -> CGFloat { self * 180 / .pi } + +} + +// MARK: - Double + +extension Double { + + func remap(fromLow: Double, fromHigh: Double, toLow: Double, toHigh: Double) -> Double { + toLow + (self - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + } + + /// Returns a value that is clamped between the two numbers + /// + /// 1. The order of arguments does not matter. + func clamp(_ a: Double, _ b: Double) -> Double { + let minValue = a <= b ? a : b + let maxValue = a <= b ? b : a + return max(min(self, maxValue), minValue) + } + +} + +public extension CGRect { + + // MARK: Lifecycle + + /// Initializes a new CGRect with a center point and size. + init(center: CGPoint, size: CGSize) { + self.init( + x: center.x - (size.width * 0.5), + y: center.y - (size.height * 0.5), + width: size.width, + height: size.height) + } + + // MARK: Internal + + /// Returns the total area of the rect. + var area: CGFloat { + width * height + } + + /// The center point of the rect. Settable. + var center: CGPoint { + get { + CGPoint(x: midX, y: midY) + } + set { + origin = CGPoint( + x: newValue.x - (size.width * 0.5), + y: newValue.y - (size.height * 0.5)) + } + } + + /// The top left point of the rect. Settable. + var topLeft: CGPoint { + get { + CGPoint(x: minX, y: minY) + } + set { + origin = CGPoint( + x: newValue.x, + y: newValue.y) + } + } + + /// The bottom left point of the rect. Settable. + var bottomLeft: CGPoint { + get { + CGPoint(x: minX, y: maxY) + } + set { + origin = CGPoint( + x: newValue.x, + y: newValue.y - size.height) + } + } + + /// The top right point of the rect. Settable. + var topRight: CGPoint { + get { + CGPoint(x: maxX, y: minY) + } + set { + origin = CGPoint( + x: newValue.x - size.width, + y: newValue.y) + } + } + + /// The bottom right point of the rect. Settable. + var bottomRight: CGPoint { + get { + CGPoint(x: maxX, y: maxY) + } + set { + origin = CGPoint( + x: newValue.x - size.width, + y: newValue.y - size.height) + } + } + +} + +public extension CGSize { + + /// Operator convenience to add sizes with + + static func +(left: CGSize, right: CGSize) -> CGSize { + left.add(right) + } + + /// Operator convenience to subtract sizes with - + static func -(left: CGSize, right: CGSize) -> CGSize { + left.subtract(right) + } + + /// Operator convenience to multiply sizes with * + static func *(left: CGSize, right: CGFloat) -> CGSize { + CGSize(width: left.width * right, height: left.height * right) + } + + /// Returns the scale float that will fit the receive inside of the given size. + func scaleThatFits(_ size: CGSize) -> CGFloat { + CGFloat.minimum(width / size.width, height / size.height) + } + + /// Adds receiver size to give size. + func add(_ size: CGSize) -> CGSize { + CGSize(width: width + size.width, height: height + size.height) + } + + /// Subtracts given size from receiver size. + func subtract(_ size: CGSize) -> CGSize { + CGSize(width: width - size.width, height: height - size.height) + } + + /// Multiplies receiver size by the given size. + func multiply(_ size: CGSize) -> CGSize { + CGSize(width: width * size.width, height: height * size.height) + } +} + +// MARK: - CGLine + +/// A struct that defines a line segment with two CGPoints +struct CGLine { + + // MARK: Lifecycle + + /// Initializes a line segment with start and end points + init(start: CGPoint, end: CGPoint) { + self.start = start + self.end = end + } + + // MARK: Internal + + /// The Start of the line segment. + var start: CGPoint + /// The End of the line segment. + var end: CGPoint + + /// The length of the line segment. + var length: CGFloat { + end.distanceTo(start) + } + + /// Returns a line segment that is normalized to a length of 1 + func normalize() -> CGLine { + let len = length + guard len > 0 else { + return self + } + let relativeEnd = end - start + let relativeVector = CGPoint(x: relativeEnd.x / len, y: relativeEnd.y / len) + let absoluteVector = relativeVector + start + return CGLine(start: start, end: absoluteVector) + } + + /// Trims a line segment to the given length + func trimmedToLength(_ toLength: CGFloat) -> CGLine { + let len = length + guard len > 0 else { + return self + } + let relativeEnd = end - start + let relativeVector = CGPoint(x: relativeEnd.x / len, y: relativeEnd.y / len) + let sizedVector = CGPoint(x: relativeVector.x * toLength, y: relativeVector.y * toLength) + let absoluteVector = sizedVector + start + return CGLine(start: start, end: absoluteVector) + } + + /// Flips a line vertically and horizontally from the start point. + func flipped() -> CGLine { + let relativeEnd = end - start + let flippedEnd = CGPoint(x: relativeEnd.x * -1, y: relativeEnd.y * -1) + return CGLine(start: start, end: flippedEnd + start) + } + + /// Move the line to the new start point. + func transpose(_ toPoint: CGPoint) -> CGLine { + let diff = toPoint - start + let newEnd = end + diff + return CGLine(start: toPoint, end: newEnd) + } + +} + +infix operator +| +infix operator +- + +extension CGPoint { + + /// Returns the length between the receiver and *CGPoint.zero* + var vectorLength: CGFloat { + distanceTo(.zero) + } + + var isZero: Bool { + x == 0 && y == 0 + } + + /// Operator convenience to divide points with / + static func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint { + CGPoint(x: lhs.x / CGFloat(rhs), y: lhs.y / CGFloat(rhs)) + } + + /// Operator convenience to multiply points with * + static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint { + CGPoint(x: lhs.x * CGFloat(rhs), y: lhs.y * CGFloat(rhs)) + } + + /// Operator convenience to add points with + + static func +(left: CGPoint, right: CGPoint) -> CGPoint { + left.add(right) + } + + /// Operator convenience to subtract points with - + static func -(left: CGPoint, right: CGPoint) -> CGPoint { + left.subtract(right) + } + + static func +|(left: CGPoint, right: CGFloat) -> CGPoint { + CGPoint(x: left.x, y: left.y + right) + } + + static func +-(left: CGPoint, right: CGFloat) -> CGPoint { + CGPoint(x: left.x + right, y: left.y) + } + + /// Returns the distance between the receiver and the given point. + func distanceTo(_ a: CGPoint) -> CGFloat { + let xDist = a.x - x + let yDist = a.y - y + return CGFloat(sqrt((xDist * xDist) + (yDist * yDist))) + } + + func rounded(decimal: CGFloat) -> CGPoint { + CGPoint(x: round(decimal * x) / decimal, y: round(decimal * y) / decimal) + } + + func interpolate( + _ to: CGPoint, + outTangent: CGPoint, + inTangent: CGPoint, + amount: CGFloat, + maxIterations: Int = 3, + samples: Int = 20, + accuracy: CGFloat = 1) + -> CGPoint + { + if amount == 0 { + return self + } + if amount == 1 { + return to + } + + if + colinear(outTangent, inTangent) == true, + outTangent.colinear(inTangent, to) == true + { + return interpolate(to: to, amount: amount) + } + + let step = 1 / CGFloat(samples) + + var points: [(point: CGPoint, distance: CGFloat)] = [(point: self, distance: 0)] + var totalLength: CGFloat = 0 + + var previousPoint = self + var previousAmount = CGFloat(0) + + var closestPoint = 0 + + while previousAmount < 1 { + + previousAmount = previousAmount + step + + if previousAmount < amount { + closestPoint = closestPoint + 1 + } + + let newPoint = pointOnPath(to, outTangent: outTangent, inTangent: inTangent, amount: previousAmount) + let distance = previousPoint.distanceTo(newPoint) + totalLength = totalLength + distance + points.append((point: newPoint, distance: totalLength)) + previousPoint = newPoint + } + + let accurateDistance = amount * totalLength + var point = points[closestPoint] + + var foundPoint = false + + var pointAmount = CGFloat(closestPoint) * step + var nextPointAmount: CGFloat = pointAmount + step + + var refineIterations = 0 + while foundPoint == false { + refineIterations = refineIterations + 1 + /// First see if the next point is still less than the projected length. + let nextPoint = points[closestPoint + 1] + if nextPoint.distance < accurateDistance { + point = nextPoint + closestPoint = closestPoint + 1 + pointAmount = CGFloat(closestPoint) * step + nextPointAmount = pointAmount + step + if closestPoint == points.count { + foundPoint = true + } + continue + } + if accurateDistance < point.distance { + closestPoint = closestPoint - 1 + if closestPoint < 0 { + foundPoint = true + continue + } + point = points[closestPoint] + pointAmount = CGFloat(closestPoint) * step + nextPointAmount = pointAmount + step + continue + } + + /// Now we are certain the point is the closest point under the distance + let pointDiff = nextPoint.distance - point.distance + let proposedPointAmount = ((accurateDistance - point.distance) / pointDiff) + .remap(fromLow: 0, fromHigh: 1, toLow: pointAmount, toHigh: nextPointAmount) + + let newPoint = pointOnPath(to, outTangent: outTangent, inTangent: inTangent, amount: proposedPointAmount) + let newDistance = point.distance + point.point.distanceTo(newPoint) + pointAmount = proposedPointAmount + point = (point: newPoint, distance: newDistance) + if + accurateDistance - newDistance <= accuracy || + newDistance - accurateDistance <= accuracy + { + foundPoint = true + } + + if refineIterations == maxIterations { + foundPoint = true + } + } + return point.point + } + + func pointOnPath(_ to: CGPoint, outTangent: CGPoint, inTangent: CGPoint, amount: CGFloat) -> CGPoint { + let a = interpolate(to: outTangent, amount: amount) + let b = outTangent.interpolate(to: inTangent, amount: amount) + let c = inTangent.interpolate(to: to, amount: amount) + let d = a.interpolate(to: b, amount: amount) + let e = b.interpolate(to: c, amount: amount) + let f = d.interpolate(to: e, amount: amount) + return f + } + + func colinear(_ a: CGPoint, _ b: CGPoint) -> Bool { + let area = x * (a.y - b.y) + a.x * (b.y - y) + b.x * (y - a.y); + let accuracy: CGFloat = 0.05 + if area < accuracy && area > -accuracy { + return true + } + return false + } + + /// Subtracts the given point from the receiving point. + func subtract(_ point: CGPoint) -> CGPoint { + CGPoint( + x: x - point.x, + y: y - point.y) + } + + /// Adds the given point from the receiving point. + func add(_ point: CGPoint) -> CGPoint { + CGPoint( + x: x + point.x, + y: y + point.y) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/StringExtensions.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/StringExtensions.swift new file mode 100644 index 00000000000..2a536fd23eb --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/StringExtensions.swift @@ -0,0 +1,39 @@ +// +// StringExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import CoreGraphics +import Foundation + +extension String { + + var cgColor: CGColor { + let (red, green, blue) = hexColorComponents() + return .rgb(red, green, blue) + } + + func hexColorComponents() -> (red: CGFloat, green: CGFloat, blue: CGFloat) { + + var cString: String = trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + if cString.hasPrefix("#") { + cString.remove(at: cString.startIndex) + } + + if (cString.count) != 6 { + return (red: 0, green: 0, blue: 0) + } + + var rgbValue: UInt64 = 0 + Scanner(string: cString).scanHexInt64(&rgbValue) + + return ( + red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, + green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(rgbValue & 0x0000FF) / 255.0) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Helpers/AnimationContext.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Helpers/AnimationContext.swift new file mode 100644 index 00000000000..b8c0ef433ac --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Helpers/AnimationContext.swift @@ -0,0 +1,91 @@ +// +// AnimationContext.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/1/19. +// + +import CoreGraphics +import Foundation +import QuartzCore + +/// A completion block for animations. `true` is passed in if the animation completed playing. +public typealias LottieCompletionBlock = (Bool) -> Void + +// MARK: - AnimationContext + +struct AnimationContext { + + init( + playFrom: AnimationFrameTime, + playTo: AnimationFrameTime, + closure: LottieCompletionBlock?) + { + self.playTo = playTo + self.playFrom = playFrom + self.closure = AnimationCompletionDelegate(completionBlock: closure) + } + + var playFrom: AnimationFrameTime + var playTo: AnimationFrameTime + var closure: AnimationCompletionDelegate + +} + +// MARK: Equatable + +extension AnimationContext: Equatable { + /// Whether or not the two given `AnimationContext`s are functionally equivalent + /// - This checks whether or not a completion handler was provided, + /// but does not check whether or not the two completion handlers are equivalent. + static func == (_ lhs: AnimationContext, _ rhs: AnimationContext) -> Bool { + lhs.playTo == rhs.playTo + && lhs.playFrom == rhs.playFrom + && (lhs.closure.completionBlock == nil) == (rhs.closure.completionBlock == nil) + } +} + +// MARK: - AnimationContextState + +enum AnimationContextState { + case playing + case cancelled + case complete +} + +// MARK: - AnimationCompletionDelegate + +class AnimationCompletionDelegate: NSObject, CAAnimationDelegate { + + // MARK: Lifecycle + + init(completionBlock: LottieCompletionBlock?) { + self.completionBlock = completionBlock + super.init() + } + + // MARK: Public + + public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { + guard ignoreDelegate == false else { return } + animationState = flag ? .complete : .cancelled + if let animationLayer = animationLayer, let key = animationKey { + animationLayer.removeAnimation(forKey: key) + if flag { + animationLayer.currentFrame = (anim as! CABasicAnimation).toValue as! CGFloat + } + } + if let completionBlock = completionBlock { + completionBlock(flag) + } + } + + // MARK: Internal + + var animationLayer: RootAnimationLayer? + var animationKey: String? + var ignoreDelegate = false + var animationState: AnimationContextState = .playing + + let completionBlock: LottieCompletionBlock? +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift new file mode 100644 index 00000000000..9a28f61017c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift @@ -0,0 +1,135 @@ +// +// InterpolatableExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import CoreGraphics +import Foundation + +extension Color { + + // MARK: Lifecycle + + /// Initialize a new color with Hue Saturation and Value + init(h: Double, s: Double, v: Double, a: Double) { + + let i = floor(h * 6) + let f = h * 6 - i + let p = v * (1 - s); + let q = v * (1 - f * s) + let t = v * (1 - (1 - f) * s) + + switch i.truncatingRemainder(dividingBy: 6) { + case 0: + r = v + g = t + b = p + case 1: + r = q + g = v + b = p + case 2: + r = p + g = v + b = t + case 3: + r = p + g = q + b = v + case 4: + r = t + g = p + b = v + case 5: + r = v + g = p + b = q + default: + r = 0 + g = 0 + b = 0 + } + self.a = a + } + + init(y: Double, u: Double, v: Double, a: Double) { + // From https://www.fourcc.org/fccyvrgb.php + r = y + 1.403 * v + g = y - 0.344 * u + b = y + 1.770 * u + self.a = a + } + + // MARK: Internal + + /// Hue Saturation Value of the color. + var hsva: (h: Double, s: Double, v: Double, a: Double) { + let maxValue = max(r, g, b) + let minValue = min(r, g, b) + + var h: Double, s: Double, v: Double = maxValue + + let d = maxValue - minValue + s = maxValue == 0 ? 0 : d / maxValue; + + if maxValue == minValue { + h = 0; // achromatic + } else { + switch maxValue { + case r: h = (g - b) / d + (g < b ? 6 : 0) + case g: h = (b - r) / d + 2 + case b: h = (r - g) / d + 4 + default: h = maxValue + } + h = h / 6 + } + return (h: h, s: s, v: v, a: a) + } + + var yuv: (y: Double, u: Double, v: Double, a: Double) { + /// From https://www.fourcc.org/fccyvrgb.php + let y = 0.299 * r + 0.587 * g + 0.114 * b + let u = -0.14713 * r - 0.28886 * g + 0.436 * b + let v = 0.615 * r - 0.51499 * g - 0.10001 * b + return (y: y, u: u, v: v, a: a) + } + +} + +// MARK: - CurveVertex + Interpolatable + +extension CurveVertex: Interpolatable { + func interpolate(to: CurveVertex, amount: CGFloat) -> CurveVertex { + CurveVertex( + point: point.interpolate(to: to.point, amount: amount), + inTangent: inTangent.interpolate(to: to.inTangent, amount: amount), + outTangent: outTangent.interpolate(to: to.outTangent, amount: amount)) + } +} + +// MARK: - BezierPath + Interpolatable + +extension BezierPath: Interpolatable { + func interpolate(to: BezierPath, amount: CGFloat) -> BezierPath { + var newPath = BezierPath() + for i in 0.. TextDocument { + if amount == 1 { + return to + } + return self + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Interpolatable/KeyframeExtensions.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Interpolatable/KeyframeExtensions.swift new file mode 100644 index 00000000000..e6e0c1811ff --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Interpolatable/KeyframeExtensions.swift @@ -0,0 +1,46 @@ +// +// KeyframeExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import CoreGraphics +import Foundation + +extension Keyframe where T: AnyInterpolatable { + func interpolate(to: Keyframe, progress: CGFloat) -> T { + value._interpolate( + to: to.value, + amount: progress, + spatialOutTangent: spatialOutTangent?.pointValue, + spatialInTangent: to.spatialInTangent?.pointValue) + } +} + +extension Keyframe { + /// Interpolates the keyTime into a value from 0-1 + func interpolatedProgress(_ to: Keyframe, keyTime: CGFloat) -> CGFloat { + let startTime = time + let endTime = to.time + if keyTime <= startTime { + return 0 + } + if endTime <= keyTime { + return 1 + } + + if isHold { + return 0 + } + + let outTanPoint = outTangent?.pointValue ?? .zero + let inTanPoint = to.inTangent?.pointValue ?? CGPoint(x: 1, y: 1) + var progress: CGFloat = keyTime.remap(fromLow: startTime, fromHigh: endTime, toLow: 0, toHigh: 1) + if !outTanPoint.isZero || !inTanPoint.equalTo(CGPoint(x: 1, y: 1)) { + /// Cubic interpolation + progress = progress.cubicBezierInterpolate(.zero, outTanPoint, inTanPoint, CGPoint(x: 1, y: 1)) + } + return progress + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/BezierPath.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/BezierPath.cpp new file mode 100644 index 00000000000..571bd152d82 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/BezierPath.cpp @@ -0,0 +1,134 @@ +#include "BezierPath.hpp" + +#include +#include + +namespace lottie { + +CGRect calculateBoundingRectOpt(float const *pointsX, float const *pointsY, int count) { + float minX = 0.0; + float maxX = 0.0; + vDSP_minv(pointsX, 1, &minX, count); + vDSP_maxv(pointsX, 1, &maxX, count); + + float minY = 0.0; + float maxY = 0.0; + vDSP_minv(pointsY, 1, &minY, count); + vDSP_maxv(pointsY, 1, &maxY, count); + + return CGRect(minX, minY, maxX - minX, maxY - minY); +} + +CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector const &paths) { + int pointCount = 0; + + float *pointsX = context.pointsX; + float *pointsY = context.pointsY; + int pointsSize = context.pointsSize; + + for (const auto &path : paths) { + PathElement const *pathElements = path.elements().data(); + int pathElementCount = (int)path.elements().size(); + + for (int i = 0; i < pathElementCount; i++) { + const auto &element = pathElements[i]; + + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 1) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)element.vertex.point.x; + pointsY[pointCount] = (float)element.vertex.point.y; + pointCount++; + + if (i != 0) { + const auto &previousElement = pathElements[i - 1]; + if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + } else { + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 2) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)previousElement.vertex.outTangent.x; + pointsY[pointCount] = (float)previousElement.vertex.outTangent.y; + pointCount++; + pointsX[pointCount] = (float)element.vertex.inTangent.x; + pointsY[pointCount] = (float)element.vertex.inTangent.y; + pointCount++; + } + } + } + } + + context.pointsX = pointsX; + context.pointsY = pointsY; + context.pointsSize = pointsSize; + + if (pointCount == 0) { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + + return calculateBoundingRectOpt(pointsX, pointsY, pointCount); +} + +CGRect bezierPathsBoundingBox(std::vector const &paths) { + int pointCount = 0; + + float *pointsX = (float *)malloc(128 * 4); + float *pointsY = (float *)malloc(128 * 4); + int pointsSize = 128; + + for (const auto &path : paths) { + PathElement const *pathElements = path.elements().data(); + int pathElementCount = (int)path.elements().size(); + + for (int i = 0; i < pathElementCount; i++) { + const auto &element = pathElements[i]; + + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 1) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)element.vertex.point.x; + pointsY[pointCount] = (float)element.vertex.point.y; + pointCount++; + + if (i != 0) { + const auto &previousElement = pathElements[i - 1]; + if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + } else { + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 2) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)previousElement.vertex.outTangent.x; + pointsY[pointCount] = (float)previousElement.vertex.outTangent.y; + pointCount++; + pointsX[pointCount] = (float)element.vertex.inTangent.x; + pointsY[pointCount] = (float)element.vertex.inTangent.y; + pointCount++; + } + } + } + } + + if (pointCount == 0) { + free(pointsX); + free(pointsY); + + return CGRect(0.0, 0.0, 0.0, 0.0); + } + + auto result = calculateBoundingRectOpt(pointsX, pointsY, pointCount); + + free(pointsX); + free(pointsY); + + return result; +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/BezierPath.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/BezierPath.hpp new file mode 100644 index 00000000000..c7717cbd2d8 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/BezierPath.hpp @@ -0,0 +1,594 @@ +#ifndef BezierPath_hpp +#define BezierPath_hpp + +#include "Lottie/Private/Utility/Primitives/CurveVertex.hpp" +#include "Lottie/Private/Utility/Primitives/PathElement.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Primitives/CGPath.hpp" + +#include + +namespace lottie { + +struct BezierTrimPathPosition { + double start; + double end; + + explicit BezierTrimPathPosition(double start_, double end_) : + start(start_), + end(end_) { + } +}; + +class BezierPathContents: public std::enable_shared_from_this { +public: + /// Initializes a new Bezier Path. + explicit BezierPathContents(CurveVertex const &startPoint) : + elements({ PathElement(startPoint) }) { + } + + BezierPathContents() : + elements({}), + closed(false) { + } + + explicit BezierPathContents(json11::Json const &jsonAny) noexcept(false) : + elements({}) { + json11::Json::object const *json = nullptr; + if (jsonAny.is_object()) { + json = &jsonAny.object_items(); + } else if (jsonAny.is_array()) { + if (jsonAny.array_items().empty()) { + throw LottieParsingException(); + } + if (!jsonAny.array_items()[0].is_object()) { + throw LottieParsingException(); + } + json = &jsonAny.array_items()[0].object_items(); + } + + if (const auto closedData = getOptionalBool(*json, "c")) { + closed = closedData.value(); + } + + auto vertexContainer = getAnyArray(*json, "v"); + auto inPointsContainer = getAnyArray(*json, "i"); + auto outPointsContainer = getAnyArray(*json, "o"); + + if (vertexContainer.size() != inPointsContainer.size() || inPointsContainer.size() != outPointsContainer.size()) { + throw LottieParsingException(); + } + if (vertexContainer.empty()) { + return; + } + + /// Create first point + Vector2D firstPoint(vertexContainer[0]); + Vector2D firstInPoint(inPointsContainer[0]); + Vector2D firstOutPoint(outPointsContainer[0]); + CurveVertex firstVertex = CurveVertex::relative( + firstPoint, + firstInPoint, + firstOutPoint + ); + PathElement previousElement(firstVertex); + elements.push_back(previousElement); + + for (size_t i = 1; i < vertexContainer.size(); i++) { + Vector2D point(vertexContainer[i]); + Vector2D inPoint(inPointsContainer[i]); + Vector2D outPoint(outPointsContainer[i]); + CurveVertex vertex = CurveVertex::relative( + point, + inPoint, + outPoint + ); + auto pathElement = previousElement.pathElementTo(vertex); + elements.push_back(pathElement); + previousElement = pathElement; + } + + if (closed.value_or(false)) { + auto closeElement = previousElement.pathElementTo(firstVertex); + elements.push_back(closeElement); + } + } + + BezierPathContents(const BezierPathContents&) = delete; + BezierPathContents& operator=(BezierPathContents&) = delete; + + json11::Json toJson() const { + json11::Json::object result; + + json11::Json::array vertices; + json11::Json::array inPoints; + json11::Json::array outPoints; + + for (const auto &element : elements) { + vertices.push_back(element.vertex.point.toJson()); + inPoints.push_back(element.vertex.inTangentRelative().toJson()); + outPoints.push_back(element.vertex.outTangentRelative().toJson()); + } + + result.insert(std::make_pair("v", vertices)); + result.insert(std::make_pair("i", inPoints)); + result.insert(std::make_pair("o", outPoints)); + + if (closed.has_value()) { + result.insert(std::make_pair("c", closed.value())); + } + + return json11::Json(result); + } + + std::shared_ptr cgPath() const { + auto cgPath = CGPath::makePath(); + + std::optional previousElement; + for (const auto &element : elements) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + cgPath->addLineTo(element.vertex.point); + } else { + cgPath->addCurveTo(element.vertex.point, previousElement->vertex.outTangent, element.vertex.inTangent); + } + } else { + cgPath->moveTo(element.vertex.point); + } + previousElement = element; + } + if (closed.value_or(true)) { + cgPath->closeSubpath(); + } + return cgPath; + } + +public: + std::vector elements; + std::optional closed; + + double length() { + if (_length.has_value()) { + return _length.value(); + } else { + double result = 0.0; + for (size_t i = 1; i < elements.size(); i++) { + result += elements[i].length(elements[i - 1]); + } + _length = result; + return result; + } + } + +private: + std::optional _length; + +public: + void moveToStartPoint(CurveVertex const &vertex) { + elements = { PathElement(vertex) }; + _length = std::nullopt; + } + + void addVertex(CurveVertex const &vertex) { + addElement(PathElement(vertex)); + } + + void reserveCapacity(size_t capacity) { + elements.reserve(capacity); + } + + void setElementCount(size_t count) { + elements.resize(count, PathElement(CurveVertex::absolute(Vector2D(0.0, 0.0), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0)))); + } + + void invalidateLength() { + _length.reset(); + } + + void addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) { + if (elements.empty()) { + return; + } + auto previous = elements[elements.size() - 1]; + auto newVertex = CurveVertex::absolute(toPoint, inTangent, toPoint); + updateVertex( + CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, outTangent), + (int)elements.size() - 1, + false + ); + addVertex(newVertex); + } + + void addLine(Vector2D const &toPoint) { + if (elements.empty()) { + return; + } + auto previous = elements[elements.size() - 1]; + auto newVertex = CurveVertex::relative(toPoint, Vector2D::Zero(), Vector2D::Zero()); + updateVertex( + CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, previous.vertex.point), + (int)elements.size() - 1, + false + ); + addVertex(newVertex); + } + + void close() { + closed = true; + } + + void addElement(PathElement const &pathElement) { + elements.push_back(pathElement); + } + + void updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) { + if (remeasure) { + PathElement newElement(CurveVertex::absolute(Vector2D::Zero(), Vector2D::Zero(), Vector2D::Zero())); + if (atIndex > 0) { + auto previousElement = elements[atIndex - 1]; + newElement = previousElement.pathElementTo(vertex); + } else { + newElement = PathElement(vertex); + } + elements[atIndex] = newElement; + + if (atIndex + 1 < elements.size()) { + auto nextElement = elements[atIndex + 1]; + elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex); + } + + } else { + auto oldElement = elements[atIndex]; + elements[atIndex] = oldElement.updateVertex(vertex); + } + } + + /// Trims a path fromLength toLength with an offset. + /// + /// Length and offset are defined in the length coordinate space. + /// If any argument is outside the range of this path, then it will be looped over the path from finish to start. + /// + /// Cutting the curve when fromLength is less than toLength + /// x x x x + /// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- + /// |Offset |fromLength toLength| | + /// + /// Cutting the curve when from Length is greater than toLength + /// x x x x x + /// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo + /// | toLength| |Offset |fromLength | + /// + std::vector> trim(double fromLength, double toLength, double offsetLength) { + if (elements.size() <= 1) { + return {}; + } + + if (fromLength == toLength) { + return {}; + } + + double lengthValue = length(); + + /// Normalize lengths to the curve length. + auto start = fmod(fromLength + offsetLength, lengthValue); + auto end = fmod(toLength + offsetLength, lengthValue); + + if (start < 0.0) { + start = lengthValue + start; + } + + if (end < 0.0) { + end = lengthValue + end; + } + + if (start == lengthValue) { + start = 0.0; + } + if (end == 0.0) { + end = lengthValue; + } + + if ( + (start == 0.0 && end == lengthValue) || + start == end || + (start == lengthValue && end == 0.0) + ) { + /// The trim encompasses the entire path. Return. + return { shared_from_this() }; + } + + if (start > end) { + // Start is greater than end. Two paths are returned. + return trimPathAtLengths({ + BezierTrimPathPosition(0.0, end), + BezierTrimPathPosition(start, lengthValue) + }); + } + + return trimPathAtLengths({ BezierTrimPathPosition(start, end) }); + } + + // MARK: Private + + std::vector> trimPathAtLengths(std::vector const &positions) { + if (positions.empty()) { + return {}; + } + auto remainingPositions = positions; + + auto trim = remainingPositions[0]; + remainingPositions.erase(remainingPositions.begin()); + + std::vector> paths; + + double runningLength = 0.0; + bool finishedTrimming = false; + auto pathElements = elements; + + auto currentPath = std::make_shared(); + int i = 0; + + while (!finishedTrimming) { + if (pathElements.size() <= i) { + /// Do this for rounding errors + paths.push_back(currentPath); + finishedTrimming = true; + continue; + } + /// Loop through and add elements within start->end range. + /// Get current element + auto element = pathElements[i]; + double elementLength = 0.0; + if (i != 0) { + elementLength = element.length(pathElements[i - 1]); + } + + /// Calculate new running length. + auto newLength = runningLength + elementLength; + + if (newLength < trim.start) { + /// Element is not included in the trim, continue. + runningLength = newLength; + i = i + 1; + /// Increment index, we are done with this element. + continue; + } + + if (newLength == trim.start) { + /// Current element IS the start element. + /// For start we want to add a zero length element. + currentPath->moveToStartPoint(element.vertex); + runningLength = newLength; + i = i + 1; + /// Increment index, we are done with this element. + continue; + } + + if (runningLength < trim.start && trim.start < newLength && currentPath->elements.size() == 0) { + /// The start of the trim is between this element and the previous, trim. + /// Get previous element. + auto previousElement = pathElements[i - 1]; + /// Trim it + auto trimLength = trim.start - runningLength; + auto trimResults = element.splitElementAtPosition(previousElement, trimLength); + /// Add the right span start. + currentPath->moveToStartPoint(trimResults.rightSpan.start.vertex); + + pathElements[i] = trimResults.rightSpan.end; + pathElements[i - 1] = trimResults.rightSpan.start; + runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start); + /// Dont increment index or the current length, the end of this path can be within this span. + continue; + } + + if (trim.start < newLength && newLength < trim.end) { + /// Element lies within the trim span. + currentPath->addElement(element); + runningLength = newLength; + i = i + 1; + continue; + } + + if (newLength == trim.end) { + /// Element is the end element. + /// The element could have a new length if it's added right after the start node. + currentPath->addElement(element); + /// We are done with this span. + runningLength = newLength; + i = i + 1; + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + if (runningLength < trim.end && trim.end < newLength) { + /// New element must be cut for end. + /// Get previous element. + auto previousElement = pathElements[i - 1]; + /// Trim it + auto trimLength = trim.end - runningLength; + auto trimResults = element.splitElementAtPosition(previousElement, trimLength); + /// Add the left span end. + + currentPath->updateVertex(trimResults.leftSpan.start.vertex, (int)currentPath->elements.size() - 1, false); + currentPath->addElement(trimResults.leftSpan.end); + + pathElements[i] = trimResults.rightSpan.end; + pathElements[i - 1] = trimResults.rightSpan.start; + runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start); + /// Dont increment index or the current length, the start of the next path can be within this span. + /// We are done with this span. + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + paths.push_back(currentPath); + currentPath = std::make_shared(); + if (remainingPositions.size() > 0) { + trim = remainingPositions[0]; + remainingPositions.erase(remainingPositions.begin()); + } else { + finishedTrimming = true; + } + } + return paths; + } +}; + +class BezierPath { +public: + /// Initializes a new Bezier Path. + explicit BezierPath(CurveVertex const &startPoint) : + _contents(std::make_shared(startPoint)) { + } + + BezierPath() : + _contents(std::make_shared()) { + } + + explicit BezierPath(json11::Json const &jsonAny) noexcept(false) : + _contents(std::make_shared(jsonAny)) { + } + + json11::Json toJson() const { + return _contents->toJson(); + } + + double length() { + return _contents->length(); + } + + void moveToStartPoint(CurveVertex const &vertex) { + _contents->moveToStartPoint(vertex); + } + + void addVertex(CurveVertex const &vertex) { + _contents->addVertex(vertex); + } + + void reserveCapacity(size_t capacity) { + _contents->reserveCapacity(capacity); + } + + void setElementCount(size_t count) { + _contents->setElementCount(count); + } + + void invalidateLength() { + _contents->invalidateLength(); + } + + void addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) { + _contents->addCurve(toPoint, outTangent, inTangent); + } + + void addLine(Vector2D const &toPoint) { + _contents->addLine(toPoint); + } + + void close() { + _contents->close(); + } + + void addElement(PathElement const &pathElement) { + _contents->addElement(pathElement); + } + + void updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) { + _contents->updateVertex(vertex, atIndex, remeasure); + } + + /// Trims a path fromLength toLength with an offset. + /// + /// Length and offset are defined in the length coordinate space. + /// If any argument is outside the range of this path, then it will be looped over the path from finish to start. + /// + /// Cutting the curve when fromLength is less than toLength + /// x x x x + /// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- + /// |Offset |fromLength toLength| | + /// + /// Cutting the curve when from Length is greater than toLength + /// x x x x x + /// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo + /// | toLength| |Offset |fromLength | + /// + std::vector trim(double fromLength, double toLength, double offsetLength) { + std::vector result; + + auto resultContents = _contents->trim(fromLength, toLength, offsetLength); + for (const auto &resultContent : resultContents) { + result.emplace_back(resultContent); + } + + return result; + } + + // MARK: Private + + std::vector const &elements() const { + return _contents->elements; + } + + std::vector &mutableElements() { + return _contents->elements; + } + + std::optional const &closed() const { + return _contents->closed; + } + void setClosed(std::optional const &closed) { + _contents->closed = closed; + } + + std::shared_ptr cgPath() const { + return _contents->cgPath(); + } + + BezierPath copyUsingTransform(CATransform3D const &transform) const { + if (transform == CATransform3D::identity()) { + return (*this); + } + BezierPath result; + result._contents->closed = _contents->closed; + result.reserveCapacity(_contents->elements.size()); + for (const auto &element : _contents->elements) { + result._contents->elements.emplace_back(element.vertex.transformed(transform)); + } + return result; + } + +public: + BezierPath(std::shared_ptr contents) : + _contents(contents) { + } + +private: + std::shared_ptr _contents; +}; + +class BezierPathsBoundingBoxContext { +public: + BezierPathsBoundingBoxContext() : + pointsX((float *)malloc(1024 * 4)), + pointsY((float *)malloc(1024 * 4)), + pointsSize(1024) { + } + + ~BezierPathsBoundingBoxContext() { + free(pointsX); + free(pointsY); + } + +public: + float *pointsX = nullptr; + float *pointsY = nullptr; + int pointsSize = 0; +}; + +CGRect bezierPathsBoundingBox(std::vector const &paths); +CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector const &paths); +CGRect calculateBoundingRectOpt(float const *pointsX, float const *pointsY, int count); + +} + +#endif /* BezierPath_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/BezierPath.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/BezierPath.swift new file mode 100644 index 00000000000..39efa0ab9d3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/BezierPath.swift @@ -0,0 +1,488 @@ +// +// Shape.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import CoreGraphics +import Foundation + +// MARK: - BezierPath + +/// A container that holds instructions for creating a single, unbroken Bezier Path. +struct BezierPath { + + // MARK: Lifecycle + + /// Initializes a new Bezier Path. + init(startPoint: CurveVertex) { + elements = [PathElement(vertex: startPoint)] + length = 0 + closed = false + } + + init() { + elements = [] + length = 0 + closed = false + } + + // MARK: Internal + + /// The elements of the path + private(set) var elements: [PathElement] + + /// If the path is closed or not. + private(set) var closed: Bool + + /// The total length of the path. + private(set) var length: CGFloat + + mutating func moveToStartPoint(_ vertex: CurveVertex) { + elements = [PathElement(vertex: vertex)] + length = 0 + } + + mutating func addVertex(_ vertex: CurveVertex) { + guard let previous = elements.last else { + addElement(PathElement(vertex: vertex)) + return + } + addElement(previous.pathElementTo(vertex)) + } + + mutating func addCurve(toPoint: CGPoint, outTangent: CGPoint, inTangent: CGPoint) { + guard let previous = elements.last else { return } + let newVertex = CurveVertex(inTangent, toPoint, toPoint) + updateVertex( + CurveVertex(previous.vertex.inTangent, previous.vertex.point, outTangent), + atIndex: elements.endIndex - 1, + remeasure: false) + addVertex(newVertex) + } + + mutating func addLine(toPoint: CGPoint) { + guard let previous = elements.last else { return } + let newVertex = CurveVertex(point: toPoint, inTangentRelative: .zero, outTangentRelative: .zero) + updateVertex( + CurveVertex(previous.vertex.inTangent, previous.vertex.point, previous.vertex.point), + atIndex: elements.endIndex - 1, + remeasure: false) + addVertex(newVertex) + } + + mutating func close() { + closed = true + } + + mutating func addElement(_ pathElement: PathElement) { + elements.append(pathElement) + length = length + pathElement.length + } + + mutating func updateVertex(_ vertex: CurveVertex, atIndex: Int, remeasure: Bool) { + if remeasure { + var newElement: PathElement + if atIndex > 0 { + let previousElement = elements[atIndex - 1] + newElement = previousElement.pathElementTo(vertex) + } else { + newElement = PathElement(vertex: vertex) + } + elements[atIndex] = newElement + + if atIndex + 1 < elements.count { + let nextElement = elements[atIndex + 1] + elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex) + } + + } else { + let oldElement = elements[atIndex] + elements[atIndex] = oldElement.updateVertex(newVertex: vertex) + } + } + + /// Trims a path fromLength toLength with an offset. + /// + /// Length and offset are defined in the length coordinate space. + /// If any argument is outside the range of this path, then it will be looped over the path from finish to start. + /// + /// Cutting the curve when fromLength is less than toLength + /// x x x x + /// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- + /// |Offset |fromLength toLength| | + /// + /// Cutting the curve when from Length is greater than toLength + /// x x x x x + /// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo + /// | toLength| |Offset |fromLength | + /// + func trim(fromLength: CGFloat, toLength: CGFloat, offsetLength: CGFloat) -> [BezierPath] { + guard elements.count > 1 else { + return [] + } + + if fromLength == toLength { + return [] + } + + /// Normalize lengths to the curve length. + var start = (fromLength + offsetLength).truncatingRemainder(dividingBy: length) + var end = (toLength + offsetLength).truncatingRemainder(dividingBy: length) + + if start < 0 { + start = length + start + } + + if end < 0 { + end = length + end + } + + if start == length { + start = 0 + } + if end == 0 { + end = length + } + + if + start == 0 && end == length || + start == end || + start == length && end == 0 + { + /// The trim encompasses the entire path. Return. + return [self] + } + + if start > end { + // Start is greater than end. Two paths are returned. + return trimPathAtLengths(positions: [(start: 0, end: end), (start: start, end: length)]) + } + + return trimPathAtLengths(positions: [(start: start, end: end)]) + } + + // MARK: Private + + private func trimPathAtLengths(positions: [(start: CGFloat, end: CGFloat)]) -> [BezierPath] { + guard positions.count > 0 else { + return [] + } + var remainingPositions = positions + + var trim = remainingPositions.remove(at: 0) + + var paths = [BezierPath]() + + var runningLength: CGFloat = 0 + var finishedTrimming = false + var pathElements = elements + + var currentPath = BezierPath() + var i = 0 + + while !finishedTrimming { + if pathElements.count <= i { + /// Do this for rounding errors + paths.append(currentPath) + finishedTrimming = true + continue + } + /// Loop through and add elements within start->end range. + /// Get current element + let element = pathElements[i] + + /// Calculate new running length. + let newLength = runningLength + element.length + + if newLength < trim.start { + /// Element is not included in the trim, continue. + runningLength = newLength + i = i + 1 + /// Increment index, we are done with this element. + continue + } + + if newLength == trim.start { + /// Current element IS the start element. + /// For start we want to add a zero length element. + currentPath.moveToStartPoint(element.vertex) + runningLength = newLength + i = i + 1 + /// Increment index, we are done with this element. + continue + } + + if runningLength < trim.start, trim.start < newLength, currentPath.elements.count == 0 { + /// The start of the trim is between this element and the previous, trim. + /// Get previous element. + let previousElement = pathElements[i - 1] + /// Trim it + let trimLength = trim.start - runningLength + let trimResults = element.splitElementAtPosition(fromElement: previousElement, atLength: trimLength) + /// Add the right span start. + currentPath.moveToStartPoint(trimResults.rightSpan.start.vertex) + + pathElements[i] = trimResults.rightSpan.end + pathElements[i - 1] = trimResults.rightSpan.start + runningLength = runningLength + trimResults.leftSpan.end.length + /// Dont increment index or the current length, the end of this path can be within this span. + continue + } + + if trim.start < newLength, newLength < trim.end { + /// Element lies within the trim span. + currentPath.addElement(element) + runningLength = newLength + i = i + 1 + continue + } + + if newLength == trim.end { + /// Element is the end element. + /// The element could have a new length if it's added right after the start node. + currentPath.addElement(element) + /// We are done with this span. + runningLength = newLength + i = i + 1 + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + if runningLength < trim.end, trim.end < newLength { + /// New element must be cut for end. + /// Get previous element. + let previousElement = pathElements[i - 1] + /// Trim it + let trimLength = trim.end - runningLength + let trimResults = element.splitElementAtPosition(fromElement: previousElement, atLength: trimLength) + /// Add the left span end. + + currentPath.updateVertex(trimResults.leftSpan.start.vertex, atIndex: currentPath.elements.count - 1, remeasure: false) + currentPath.addElement(trimResults.leftSpan.end) + + pathElements[i] = trimResults.rightSpan.end + pathElements[i - 1] = trimResults.rightSpan.start + runningLength = runningLength + trimResults.leftSpan.end.length + /// Dont increment index or the current length, the start of the next path can be within this span. + /// We are done with this span. + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + paths.append(currentPath) + currentPath = BezierPath() + if remainingPositions.count > 0 { + trim = remainingPositions.remove(at: 0) + } else { + finishedTrimming = true + } + } + return paths + } + +} + +// MARK: Codable + +extension BezierPath: Codable { + + // MARK: Lifecycle + + init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer + + if let keyedContainer = try? decoder.container(keyedBy: BezierPath.CodingKeys.self) { + container = keyedContainer + } else { + var unkeyedContainer = try decoder.unkeyedContainer() + container = try unkeyedContainer.nestedContainer(keyedBy: BezierPath.CodingKeys.self) + } + + closed = try container.decodeIfPresent(Bool.self, forKey: .closed) ?? true + + var vertexContainer = try container.nestedUnkeyedContainer(forKey: .vertices) + var inPointsContainer = try container.nestedUnkeyedContainer(forKey: .inPoints) + var outPointsContainer = try container.nestedUnkeyedContainer(forKey: .outPoints) + + guard vertexContainer.count == inPointsContainer.count, inPointsContainer.count == outPointsContainer.count else { + /// Will throw an error if vertex, inpoints, and outpoints are not the same length. + /// This error is to be expected. + throw DecodingError.dataCorruptedError( + forKey: CodingKeys.vertices, + in: container, + debugDescription: "Vertex data does not match In Tangents and Out Tangents") + } + + guard let count = vertexContainer.count, count > 0 else { + length = 0 + elements = [] + return + } + + var decodedElements = [PathElement]() + + /// Create first point + let firstVertex = CurveVertex( + point: try vertexContainer.decode(CGPoint.self), + inTangentRelative: try inPointsContainer.decode(CGPoint.self), + outTangentRelative: try outPointsContainer.decode(CGPoint.self)) + var previousElement = PathElement(vertex: firstVertex) + decodedElements.append(previousElement) + + var totalLength: CGFloat = 0 + while !vertexContainer.isAtEnd { + /// Get the next vertex data. + let vertex = CurveVertex( + point: try vertexContainer.decode(CGPoint.self), + inTangentRelative: try inPointsContainer.decode(CGPoint.self), + outTangentRelative: try outPointsContainer.decode(CGPoint.self)) + let pathElement = previousElement.pathElementTo(vertex) + decodedElements.append(pathElement) + previousElement = pathElement + totalLength = totalLength + pathElement.length + } + if closed { + let closeElement = previousElement.pathElementTo(firstVertex) + decodedElements.append(closeElement) + totalLength = totalLength + closeElement.length + } + length = totalLength + elements = decodedElements + } + + // MARK: Internal + + /// The BezierPath container is encoded and decoded from the JSON format + /// that defines points for a lottie animation. + /// + /// { + /// "c" = Bool + /// "i" = [[Double]], + /// "o" = [[Double]], + /// "v" = [[Double]] + /// } + /// + + enum CodingKeys: String, CodingKey { + case closed = "c" + case inPoints = "i" + case outPoints = "o" + case vertices = "v" + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: BezierPath.CodingKeys.self) + try container.encode(closed, forKey: .closed) + + var vertexContainer = container.nestedUnkeyedContainer(forKey: .vertices) + var inPointsContainer = container.nestedUnkeyedContainer(forKey: .inPoints) + var outPointsContainer = container.nestedUnkeyedContainer(forKey: .outPoints) + + /// If closed path, ignore the final element. + let finalIndex = closed ? elements.endIndex - 1 : elements.endIndex + for i in 0.. 0 else { + length = 0 + elements = [] + return + } + + var decodedElements = [PathElement]() + let firstVertexDictionary = vertexDictionaries.removeFirst() + let firstInPointsDictionary = inPointsDictionaries.removeFirst() + let firstOutPointsDictionary = outPointsDictionaries.removeFirst() + let firstVertex = CurveVertex( + point: try CGPoint(value: firstVertexDictionary), + inTangentRelative: try CGPoint(value: firstInPointsDictionary), + outTangentRelative: try CGPoint(value: firstOutPointsDictionary)) + var previousElement = PathElement(vertex: firstVertex) + decodedElements.append(previousElement) + + var totalLength: CGFloat = 0 + while vertexDictionaries.count > 0 { + let vertexDictionary = vertexDictionaries.removeFirst() + let inPointsDictionary = inPointsDictionaries.removeFirst() + let outPointsDictionary = outPointsDictionaries.removeFirst() + let vertex = CurveVertex( + point: try CGPoint(value: vertexDictionary), + inTangentRelative: try CGPoint(value: inPointsDictionary), + outTangentRelative: try CGPoint(value: outPointsDictionary)) + let pathElement = previousElement.pathElementTo(vertex) + decodedElements.append(pathElement) + previousElement = pathElement + totalLength = totalLength + pathElement.length + } + if closed { + let closeElement = previousElement.pathElementTo(firstVertex) + decodedElements.append(closeElement) + totalLength = totalLength + closeElement.length + } + + length = totalLength + elements = decodedElements + } + +} + +extension BezierPath { + + func cgPath() -> CGPath { + let cgPath = CGMutablePath() + + var previousElement: PathElement? + for element in elements { + if let previous = previousElement { + if previous.vertex.outTangentRelative.isZero && element.vertex.inTangentRelative.isZero { + cgPath.addLine(to: element.vertex.point) + } else { + cgPath.addCurve(to: element.vertex.point, control1: previous.vertex.outTangent, control2: element.vertex.inTangent) + } + } else { + cgPath.move(to: element.vertex.point) + } + previousElement = element + } + if closed { + cgPath.closeSubpath() + } + return cgPath + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CGPointExtension.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CGPointExtension.swift new file mode 100644 index 00000000000..b98b65a4681 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CGPointExtension.swift @@ -0,0 +1,35 @@ +// +// CGPointExtension.swift +// Lottie +// +// Created by Marcelo Fabri on 5/5/22. +// + +import CoreGraphics + +extension CGPoint: AnyInitializable { + + // MARK: Lifecycle + + init(value: Any) throws { + if let dictionary = value as? [String: CGFloat] { + let x: CGFloat = try dictionary.value(for: CodingKeys.x) + let y: CGFloat = try dictionary.value(for: CodingKeys.y) + self.init(x: x, y: y) + } else if + let array = value as? [CGFloat], + array.count > 1 + { + self.init(x: array[0], y: array[1]) + } else { + throw InitializableError.invalidInput + } + } + + // MARK: Private + + private enum CodingKeys: String { + case x + case y + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/ColorExtension.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/ColorExtension.swift new file mode 100644 index 00000000000..13c39b0eb9b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/ColorExtension.swift @@ -0,0 +1,112 @@ +// +// Color.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import CoreGraphics +import Foundation + +// MARK: - Color + Codable + +extension Color: Codable { + + // MARK: Lifecycle + + public init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + + var r1: Double + if !container.isAtEnd { + r1 = try container.decode(Double.self) + } else { + r1 = 0 + } + + var g1: Double + if !container.isAtEnd { + g1 = try container.decode(Double.self) + } else { + g1 = 0 + } + + var b1: Double + if !container.isAtEnd { + b1 = try container.decode(Double.self) + } else { + b1 = 0 + } + + var a1: Double + if !container.isAtEnd { + a1 = try container.decode(Double.self) + } else { + a1 = 1 + } + if r1 > 1, g1 > 1, b1 > 1, a1 > 1 { + r1 = r1 / 255 + g1 = g1 / 255 + b1 = b1 / 255 + a1 = a1 / 255 + } + r = r1 + g = g1 + b = b1 + a = a1 + } + + // MARK: Public + + public func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(r) + try container.encode(g) + try container.encode(b) + try container.encode(a) + } + +} + +// MARK: - Color + AnyInitializable + +extension Color: AnyInitializable { + + init(value: Any) throws { + guard var array = value as? [Double] else { + throw InitializableError.invalidInput + } + var r: Double = array.count > 0 ? array.removeFirst() : 0 + var g: Double = array.count > 0 ? array.removeFirst() : 0 + var b: Double = array.count > 0 ? array.removeFirst() : 0 + var a: Double = array.count > 0 ? array.removeFirst() : 1 + if r > 1, g > 1, b > 1, a > 1 { + r /= 255 + g /= 255 + b /= 255 + a /= 255 + } + self.r = r + self.g = g + self.b = b + self.a = a + } + +} + +extension Color { + + static var clearColor: CGColor { + CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 0])! + } + + var cgColorValue: CGColor { + // TODO: Fix color spaces + let colorspace = CGColorSpaceCreateDeviceRGB() + return CGColor(colorSpace: colorspace, components: [CGFloat(r), CGFloat(g), CGFloat(b), CGFloat(a)]) ?? Color.clearColor + } + + func cgColorValue(colorSpace: CGColorSpace) -> CGColor { + return CGColor(colorSpace: colorSpace, components: [CGFloat(r), CGFloat(g), CGFloat(b), CGFloat(a)]) ?? Color.clearColor + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CompoundBezierPath.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CompoundBezierPath.cpp new file mode 100644 index 00000000000..31df7039562 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CompoundBezierPath.cpp @@ -0,0 +1,5 @@ +#include "CompoundBezierPath.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CompoundBezierPath.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CompoundBezierPath.hpp new file mode 100644 index 00000000000..c9766c93ae9 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CompoundBezierPath.hpp @@ -0,0 +1,176 @@ +#ifndef CompoundBezierPath_hpp +#define CompoundBezierPath_hpp + +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" + +namespace lottie { + +/// A collection of BezierPath objects that can be trimmed and added. +/// +class CompoundBezierPath: public std::enable_shared_from_this { +public: + CompoundBezierPath() : + paths({}) { + } + + CompoundBezierPath(BezierPath const &path) : + paths({ path }) { + } + + CompoundBezierPath(std::vector paths_, std::optional length_) : + paths(paths_), _length(length_) { + } + + CompoundBezierPath(std::vector paths_) : + paths(paths_) { + } + +public: + std::vector paths; + + double length() { + if (_length.has_value()) { + return _length.value(); + } else { + double l = 0.0; + for (auto &path : paths) { + l += path.length(); + } + _length = l; + return l; + } + } + +private: + std::optional _length; + +public: + std::shared_ptr addingPath(BezierPath const &path) const { + auto newPaths = paths; + newPaths.push_back(path); + return std::make_shared(newPaths); + } + + void appendPath(BezierPath const &path) { + paths.push_back(path); + _length.reset(); + } + + std::shared_ptr combine(std::shared_ptr compoundBezier) { + auto newPaths = paths; + for (const auto &path : compoundBezier->paths) { + newPaths.push_back(path); + } + return std::make_shared(newPaths); + } + + std::shared_ptr trim(double fromPosition, double toPosition, double offset) { + if (fromPosition == toPosition) { + return std::make_shared(); + } + + /*bool trimSimultaneously = false; + if (trimSimultaneously) { + /// Trim each path individually. + std::vector newPaths; + for (auto &path : paths) { + auto trimmedPaths = path.trim(fromPosition * path.length(), toPosition * path.length(), offset * path.length()); + for (const auto &trimmedPath : trimmedPaths) { + newPaths.push_back(trimmedPath); + } + } + return std::make_shared(newPaths); + }*/ + + double lengthValue = length(); + + /// Normalize lengths to the curve length. + double startPosition = fmod(fromPosition + offset, 1.0); + double endPosition = fmod(toPosition + offset, 1.0); + + if (startPosition < 0.0) { + startPosition = 1.0 + startPosition; + } + + if (endPosition < 0.0) { + endPosition = 1.0 + endPosition; + } + + if (startPosition == 1.0) { + startPosition = 0.0; + } + if (endPosition == 0.0) { + endPosition = 1.0; + } + + if ((startPosition == 0.0 && endPosition == 1.0) || + startPosition == endPosition || + (startPosition == 1.0 && endPosition == 0.0)) { + /// The trim encompasses the entire path. Return. + return shared_from_this(); + } + + std::vector positions; + if (endPosition < startPosition) { + positions = { + BezierTrimPathPosition(0.0, endPosition * lengthValue), + BezierTrimPathPosition(startPosition * lengthValue, lengthValue) + }; + } else { + positions = { BezierTrimPathPosition(startPosition * lengthValue, endPosition * lengthValue) }; + } + + auto compoundPath = std::make_shared(); + auto trim = positions[0]; + positions.erase(positions.begin()); + double pathStartPosition = 0.0; + + bool finishedTrimming = false; + int i = 0; + + while (!finishedTrimming) { + if (paths.size() <= i) { + /// Rounding errors + finishedTrimming = true; + continue; + } + auto path = paths[i]; + + auto pathEndPosition = pathStartPosition + path.length(); + + if (pathEndPosition < trim.start) { + /// Path is not included in the trim, continue. + pathStartPosition = pathEndPosition; + i = i + 1; + continue; + } else if (trim.start <= pathStartPosition && pathEndPosition <= trim.end) { + /// Full Path is inside of trim. Add full path. + compoundPath = compoundPath->addingPath(path); + } else { + auto trimPaths = path.trim(trim.start > pathStartPosition ? (trim.start - pathStartPosition) : 0, trim.end < pathEndPosition ? (trim.end - pathStartPosition) : path.length(), 0.0); + if (!trimPaths.empty()) { + compoundPath = compoundPath->addingPath(trimPaths[0]); + } + } + + if (trim.end <= pathEndPosition) { + /// We are done with the current trim. + /// Advance trim but remain on the same path in case the next trim overlaps it. + if (positions.size() > 0) { + trim = positions[0]; + positions.erase(positions.begin()); + } else { + finishedTrimming = true; + } + } else { + pathStartPosition = pathEndPosition; + i = i + 1; + } + } + return compoundPath; + } +}; + +} + +#endif /* CompoundBezierPath_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CompoundBezierPath.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CompoundBezierPath.swift new file mode 100644 index 00000000000..4d8a2e6ce53 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CompoundBezierPath.swift @@ -0,0 +1,172 @@ +// +// CompoundBezierPath.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import CoreGraphics +import Foundation + +/// A collection of BezierPath objects that can be trimmed and added. +/// +struct CompoundBezierPath { + + // MARK: Lifecycle + + init() { + paths = [] + length = 0 + } + + init(path: BezierPath) { + paths = [path] + length = path.length + } + + init(paths: [BezierPath], length: CGFloat) { + self.paths = paths + self.length = length + } + + init(paths: [BezierPath]) { + self.paths = paths + var l: CGFloat = 0 + for path in paths { + l = l + path.length + } + length = l + } + + // MARK: Internal + + let paths: [BezierPath] + + let length: CGFloat + + func addPath(path: BezierPath) -> CompoundBezierPath { + var newPaths = paths + newPaths.append(path) + return CompoundBezierPath(paths: newPaths, length: length + path.length) + } + + func combine(_ compoundBezier: CompoundBezierPath) -> CompoundBezierPath { + var newPaths = paths + newPaths.append(contentsOf: compoundBezier.paths) + return CompoundBezierPath(paths: newPaths, length: length + compoundBezier.length) + } + + func trim(fromPosition: CGFloat, toPosition: CGFloat, offset: CGFloat, trimSimultaneously: Bool) -> CompoundBezierPath { + if fromPosition == toPosition { + return CompoundBezierPath() + } + + if trimSimultaneously { + /// Trim each path individually. + var newPaths = [BezierPath]() + for path in paths { + newPaths.append(contentsOf: path.trim( + fromLength: fromPosition * path.length, + toLength: toPosition * path.length, + offsetLength: offset * path.length)) + } + return CompoundBezierPath(paths: newPaths) + } + + /// Normalize lengths to the curve length. + var startPosition = (fromPosition + offset).truncatingRemainder(dividingBy: 1) + + assert(fmod(fromPosition + offset, 1.0) == startPosition) + + var endPosition = (toPosition + offset).truncatingRemainder(dividingBy: 1) + + assert(fmod(toPosition + offset, 1.0) == endPosition) + + if startPosition < 0 { + startPosition = 1 + startPosition + } + + if endPosition < 0 { + endPosition = 1 + endPosition + } + + if startPosition == 1 { + startPosition = 0 + } + if endPosition == 0 { + endPosition = 1 + } + + if + startPosition == 0 && endPosition == 1 || + startPosition == endPosition || + startPosition == 1 && endPosition == 0 + { + /// The trim encompasses the entire path. Return. + return self + } + + var positions: [(start: CGFloat, end: CGFloat)] + if endPosition < startPosition { + positions = [ + (start: 0, end: endPosition * length), + (start: startPosition * length, end: length), + ] + } else { + positions = [(start: startPosition * length, end: endPosition * length)] + } + + var compoundPath = CompoundBezierPath() + var trim = positions.remove(at: 0) + var pathStartPosition: CGFloat = 0 + + var finishedTrimming = false + var i = 0 + + while !finishedTrimming { + if paths.count <= i { + /// Rounding errors + finishedTrimming = true + continue + } + let path = paths[i] + + let pathEndPosition = pathStartPosition + path.length + + if pathEndPosition < trim.start { + /// Path is not included in the trim, continue. + pathStartPosition = pathEndPosition + i = i + 1 + continue + + } else if trim.start <= pathStartPosition, pathEndPosition <= trim.end { + /// Full Path is inside of trim. Add full path. + compoundPath = compoundPath.addPath(path: path) + } else { + if + let trimPath = path.trim( + fromLength: trim.start > pathStartPosition ? (trim.start - pathStartPosition) : 0, + toLength: trim.end < pathEndPosition ? (trim.end - pathStartPosition) : path.length, + offsetLength: 0).first + { + compoundPath = compoundPath.addPath(path: trimPath) + } + } + + if trim.end <= pathEndPosition { + /// We are done with the current trim. + /// Advance trim but remain on the same path in case the next trim overlaps it. + if positions.count > 0 { + trim = positions.remove(at: 0) + } else { + finishedTrimming = true + } + } else { + pathStartPosition = pathEndPosition + i = i + 1 + } + } + return compoundPath + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CoordinateSpace.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CoordinateSpace.cpp new file mode 100644 index 00000000000..695b8dc4e91 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CoordinateSpace.cpp @@ -0,0 +1,5 @@ +#include "CoordinateSpace.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CoordinateSpace.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CoordinateSpace.hpp new file mode 100644 index 00000000000..37a7b7054ca --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CoordinateSpace.hpp @@ -0,0 +1,13 @@ +#ifndef CoordinateSpace_hpp +#define CoordinateSpace_hpp + +namespace lottie { + +enum class CoordinateSpace { + Type2d, + Type3d +}; + +} + +#endif /* CoordinateSpace_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CurveVertex.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CurveVertex.cpp new file mode 100644 index 00000000000..5c1d4a0cb0d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CurveVertex.cpp @@ -0,0 +1,5 @@ +#include "CurveVertex.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CurveVertex.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CurveVertex.hpp new file mode 100644 index 00000000000..ef67e0765c6 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CurveVertex.hpp @@ -0,0 +1,197 @@ +#ifndef CurveVertex_hpp +#define CurveVertex_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/CGPath.hpp" + +#include + +namespace lottie { + +template +struct CurveVertexSplitResult { + T start; + T trimPoint; + T end; + + explicit CurveVertexSplitResult( + T const &start_, + T const &trimPoint_, + T const &end_ + ) : + start(start_), + trimPoint(trimPoint_), + end(end_) { + } +}; + +/// A single vertex with an in and out tangent +struct CurveVertex { +private: + /// Initializes a curve point with absolute or relative values + explicit CurveVertex(Vector2D const &point_, Vector2D const &inTangent_, Vector2D const &outTangent_, bool isRelative_) : + point(point_), + inTangent(isRelative_ ? (point_ + inTangent_) : inTangent_), + outTangent(isRelative_ ? (point_ + outTangent_) : outTangent_) { + } + +public: + static CurveVertex absolute(Vector2D const &point_, Vector2D const &inTangent_, Vector2D const &outTangent_) { + return CurveVertex(point_, inTangent_, outTangent_, false); + } + + static CurveVertex relative(Vector2D const &point_, Vector2D const &inTangent_, Vector2D const &outTangent_) { + return CurveVertex(point_, inTangent_, outTangent_, true); + } + + Vector2D inTangentRelative() const { + Vector2D result = inTangent - point; + return result; + } + + Vector2D outTangentRelative() const { + Vector2D result = outTangent - point; + return result; + } + + CurveVertex reversed() const { + return CurveVertex(point, outTangent, inTangent, false); + } + + CurveVertex translated(Vector2D const &translation) const { + return CurveVertex(point + translation, inTangent + translation, outTangent + translation, false); + } + + CurveVertex transformed(CATransform3D const &transform) const { + return CurveVertex(transformVector(point, transform), transformVector(inTangent, transform), transformVector(outTangent, transform), false); + } + +public: + Vector2D point = Vector2D::Zero(); + + Vector2D inTangent = Vector2D::Zero(); + Vector2D outTangent = Vector2D::Zero(); + + /// Trims a path defined by two Vertices at a specific position, from 0 to 1 + /// + /// The path can be visualized below. + /// + /// F is fromVertex. + /// V is the vertex of the receiver. + /// P is the position from 0-1. + /// O is the outTangent of fromVertex. + /// F====O=========P=======I====V + /// + /// After trimming the curve can be visualized below. + /// + /// S is the returned Start vertex. + /// E is the returned End vertex. + /// T is the trim point. + /// TI and TO are the new tangents for the trimPoint + /// NO and NI are the new tangents for the startPoint and endPoints + /// S==NO=========TI==T==TO=======NI==E + CurveVertexSplitResult splitCurve(CurveVertex const &toVertex, double position) const { + /// If position is less than or equal to 0, trim at start. + if (position <= 0.0) { + return CurveVertexSplitResult( + CurveVertex(point, inTangentRelative(), Vector2D::Zero(), true), + CurveVertex(point, Vector2D::Zero(), outTangentRelative(), true), + toVertex + ); + } + + /// If position is greater than or equal to 1, trim at end. + if (position >= 1.0) { + return CurveVertexSplitResult( + *this, + CurveVertex(toVertex.point, toVertex.inTangentRelative(), Vector2D::Zero(), true), + CurveVertex(toVertex.point, Vector2D::Zero(), toVertex.outTangentRelative(), true) + ); + } + + if (outTangentRelative().isZero() && toVertex.inTangentRelative().isZero()) { + /// If both tangents are zero, then span to be trimmed is a straight line. + Vector2D trimPoint = interpolate(point, toVertex.point, position); + return CurveVertexSplitResult( + *this, + CurveVertex(trimPoint, Vector2D::Zero(), Vector2D::Zero(), true), + toVertex + ); + } + /// Cutting by amount gives incorrect length.... + /// One option is to cut by a stride until it gets close then edge it down. + /// Measuring a percentage of the spans does not equal the same as measuring a percentage of length. + /// This is where the historical trim path bugs come from. + Vector2D a = interpolate(point, outTangent, position); + Vector2D b = interpolate(outTangent, toVertex.inTangent, position); + Vector2D c = interpolate(toVertex.inTangent, toVertex.point, position); + Vector2D d = interpolate(a, b, position); + Vector2D e = interpolate(b, c, position); + Vector2D f = interpolate(d, e, position); + return CurveVertexSplitResult( + CurveVertex::absolute(point, inTangent, a), + CurveVertex::absolute(f, d, e), + CurveVertex::absolute(toVertex.point, c, toVertex.outTangent) + ); + } + + /// Trims a curve of a known length to a specific length and returns the points. + /// + /// There is not a performant yet accurate way to cut a curve to a specific length. + /// This calls splitCurve(toVertex: position:) to split the curve and then measures + /// the length of the new curve. The function then iterates through the samples, + /// adjusting the position of the cut for a more precise cut. + /// Usually a single iteration is enough to get within 0.5 points of the desired + /// length. + /// + /// This function should probably live in PathElement, since it deals with curve + /// lengths. + CurveVertexSplitResult trimCurve(CurveVertex const &toVertex, double atLength, double curveLength, int maxSamples, double accuracy = 1.0) const { + double currentPosition = atLength / curveLength; + auto results = splitCurve(toVertex, currentPosition); + + if (maxSamples == 0) { + return results; + } + + for (int i = 1; i <= maxSamples; i++) { + auto length = results.start.distanceTo(results.trimPoint); + auto lengthDiff = atLength - length; + /// Check if length is correct. + if (lengthDiff < accuracy) { + return results; + } + auto diffPosition = std::max(std::min((currentPosition / length) * lengthDiff, currentPosition * 0.5), currentPosition * (-0.5)); + currentPosition = diffPosition + currentPosition; + results = splitCurve(toVertex, currentPosition); + } + return results; + } + + /// The distance from the receiver to the provided vertex. + /// + /// For lines (zeroed tangents) the distance between the two points is measured. + /// For curves the curve is iterated over by sample count and the points are measured. + /// This is ~99% accurate at a sample count of 30 + double distanceTo(CurveVertex const &toVertex, int sampleCount = 25) const { + if (outTangentRelative().isZero() && toVertex.inTangentRelative().isZero()) { + /// Return a linear distance. + return point.distanceTo(toVertex.point); + } + + double distance = 0.0; + + auto previousPoint = point; + for (int i = 0; i < sampleCount; i++) { + auto pointOnCurve = splitCurve(toVertex, ((double)(i)) / ((double)(sampleCount))).trimPoint; + distance = distance + previousPoint.distanceTo(pointOnCurve.point); + previousPoint = pointOnCurve.point; + } + distance = distance + previousPoint.distanceTo(toVertex.point); + return distance; + } +}; + +} + +#endif /* CurveVertex_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CurveVertex.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CurveVertex.swift new file mode 100644 index 00000000000..51304a7dfaa --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/CurveVertex.swift @@ -0,0 +1,186 @@ +// +// CurveVertex.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/11/19. +// + +import CoreGraphics +import Foundation + +/// A single vertex with an in and out tangent +struct CurveVertex { + + // MARK: Lifecycle + + /// Initializes a curve point with absolute values + init(_ inTangent: CGPoint, _ point: CGPoint, _ outTangent: CGPoint) { + self.point = point + self.inTangent = inTangent + self.outTangent = outTangent + } + + /// Initializes a curve point with relative values + init(point: CGPoint, inTangentRelative: CGPoint, outTangentRelative: CGPoint) { + self.point = point + inTangent = point.add(inTangentRelative) + outTangent = point.add(outTangentRelative) + } + + /// Initializes a curve point with absolute values + init(point: CGPoint, inTangent: CGPoint, outTangent: CGPoint) { + self.point = point + self.inTangent = inTangent + self.outTangent = outTangent + } + + // MARK: Internal + + let point: CGPoint + + let inTangent: CGPoint + let outTangent: CGPoint + + var inTangentRelative: CGPoint { + return inTangent.subtract(point) + } + + var outTangentRelative: CGPoint { + outTangent.subtract(point) + } + + func reversed() -> CurveVertex { + CurveVertex(point: point, inTangent: outTangent, outTangent: inTangent) + } + + func translated(_ translation: CGPoint) -> CurveVertex { + CurveVertex(point: point + translation, inTangent: inTangent + translation, outTangent: outTangent + translation) + } + + /// Trims a path defined by two Vertices at a specific position, from 0 to 1 + /// + /// The path can be visualized below. + /// + /// F is fromVertex. + /// V is the vertex of the receiver. + /// P is the position from 0-1. + /// O is the outTangent of fromVertex. + /// F====O=========P=======I====V + /// + /// After trimming the curve can be visualized below. + /// + /// S is the returned Start vertex. + /// E is the returned End vertex. + /// T is the trim point. + /// TI and TO are the new tangents for the trimPoint + /// NO and NI are the new tangents for the startPoint and endPoints + /// S==NO=========TI==T==TO=======NI==E + func splitCurve(toVertex: CurveVertex, position: CGFloat) -> + (start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex) + { + + /// If position is less than or equal to 0, trim at start. + if position <= 0 { + return ( + start: CurveVertex(point: point, inTangentRelative: inTangentRelative, outTangentRelative: .zero), + trimPoint: CurveVertex(point: point, inTangentRelative: .zero, outTangentRelative: outTangentRelative), + end: toVertex) + } + + /// If position is greater than or equal to 1, trim at end. + if position >= 1 { + return ( + start: self, + trimPoint: CurveVertex( + point: toVertex.point, + inTangentRelative: toVertex.inTangentRelative, + outTangentRelative: .zero), + end: CurveVertex( + point: toVertex.point, + inTangentRelative: .zero, + outTangentRelative: toVertex.outTangentRelative)) + } + + if outTangentRelative.isZero && toVertex.inTangentRelative.isZero { + /// If both tangents are zero, then span to be trimmed is a straight line. + let trimPoint = point.interpolate(to: toVertex.point, amount: position) + return ( + start: self, + trimPoint: CurveVertex(point: trimPoint, inTangentRelative: .zero, outTangentRelative: .zero), + end: toVertex) + } + /// Cutting by amount gives incorrect length.... + /// One option is to cut by a stride until it gets close then edge it down. + /// Measuring a percentage of the spans does not equal the same as measuring a percentage of length. + /// This is where the historical trim path bugs come from. + let a = point.interpolate(to: outTangent, amount: position) + let b = outTangent.interpolate(to: toVertex.inTangent, amount: position) + let c = toVertex.inTangent.interpolate(to: toVertex.point, amount: position) + let d = a.interpolate(to: b, amount: position) + let e = b.interpolate(to: c, amount: position) + let f = d.interpolate(to: e, amount: position) + return ( + start: CurveVertex(point: point, inTangent: inTangent, outTangent: a), + trimPoint: CurveVertex(point: f, inTangent: d, outTangent: e), + end: CurveVertex(point: toVertex.point, inTangent: c, outTangent: toVertex.outTangent)) + } + + /// Trims a curve of a known length to a specific length and returns the points. + /// + /// There is not a performant yet accurate way to cut a curve to a specific length. + /// This calls splitCurve(toVertex: position:) to split the curve and then measures + /// the length of the new curve. The function then iterates through the samples, + /// adjusting the position of the cut for a more precise cut. + /// Usually a single iteration is enough to get within 0.5 points of the desired + /// length. + /// + /// This function should probably live in PathElement, since it deals with curve + /// lengths. + func trimCurve(toVertex: CurveVertex, atLength: CGFloat, curveLength: CGFloat, maxSamples: Int, accuracy: CGFloat = 1) -> + (start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex) + { + var currentPosition = atLength / curveLength + var results = splitCurve(toVertex: toVertex, position: currentPosition) + + if maxSamples == 0 { + return results + } + + for _ in 1...maxSamples { + let length = results.start.distanceTo(results.trimPoint) + let lengthDiff = atLength - length + /// Check if length is correct. + if lengthDiff < accuracy { + return results + } + let diffPosition = max(min((currentPosition / length) * lengthDiff, currentPosition * 0.5), currentPosition * -0.5) + currentPosition = diffPosition + currentPosition + results = splitCurve(toVertex: toVertex, position: currentPosition) + } + return results + } + + /// The distance from the receiver to the provided vertex. + /// + /// For lines (zeroed tangents) the distance between the two points is measured. + /// For curves the curve is iterated over by sample count and the points are measured. + /// This is ~99% accurate at a sample count of 30 + func distanceTo(_ toVertex: CurveVertex, sampleCount: Int = 25) -> CGFloat { + + if outTangentRelative.isZero && toVertex.inTangentRelative.isZero { + /// Return a linear distance. + return point.distanceTo(toVertex.point) + } + + var distance: CGFloat = 0 + + var previousPoint = point + for i in 0.. +struct PathSplitResultSpan { + T start; + T end; + + explicit PathSplitResultSpan(T const &start_, T const &end_) : + start(start_), end(end_) { + } +}; + +template +struct PathSplitResult { + PathSplitResultSpan leftSpan; + PathSplitResultSpan rightSpan; + + explicit PathSplitResult(PathSplitResultSpan const &leftSpan_, PathSplitResultSpan const &rightSpan_) : + leftSpan(leftSpan_), rightSpan(rightSpan_) { + } +}; + +/// A path section, containing one point and its length to the previous point. +/// +/// The relationship between this path element and the previous is implicit. +/// Ideally a path section would be defined by two vertices and a length. +/// We don't do this however, as it would effectively double the memory footprint +/// of path data. +/// +struct PathElement { + /// Initializes a new path with length of 0 + explicit PathElement(CurveVertex const &vertex_) : + vertex(vertex_) { + } + + /// Initializes a new path with length + explicit PathElement(std::optional length_, CurveVertex const &vertex_) : + vertex(vertex_) { + } + + /// The vertex of the element + CurveVertex vertex; + + /// Returns a new path element define the span from the receiver to the new vertex. + PathElement pathElementTo(CurveVertex const &toVertex) const { + return PathElement(std::nullopt, toVertex); + } + + PathElement updateVertex(CurveVertex const &newVertex) const { + return PathElement(newVertex); + } + + /// Splits an element span defined by the receiver and fromElement to a position 0-1 + PathSplitResult splitElementAtPosition(PathElement const &fromElement, double atLength) { + /// Trim the span. Start and trim go into the first, trim and end go into second. + auto trimResults = fromElement.vertex.trimCurve(vertex, atLength, length(fromElement), 3); + + /// Create the elements for the break + auto spanAStart = PathElement( + std::nullopt, + CurveVertex::absolute( + fromElement.vertex.point, + fromElement.vertex.inTangent, + trimResults.start.outTangent + )); + /// Recalculating the length here is a waste as the trimCurve function also accurately calculates this length. + auto spanAEnd = spanAStart.pathElementTo(trimResults.trimPoint); + + auto spanBStart = PathElement(trimResults.trimPoint); + auto spanBEnd = spanBStart.pathElementTo(trimResults.end); + return PathSplitResult( + PathSplitResultSpan(spanAStart, spanAEnd), + PathSplitResultSpan(spanBStart, spanBEnd) + ); + } + + double length(PathElement const &previous) { + double result = previous.vertex.distanceTo(vertex); + return result; + } +}; + +} + +#endif /* PathElement_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/PathElement.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/PathElement.swift new file mode 100644 index 00000000000..68f43b5f251 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/PathElement.swift @@ -0,0 +1,75 @@ +// +// PathElement.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/11/19. +// + +import CoreGraphics +import Foundation + +/// A path section, containing one point and its length to the previous point. +/// +/// The relationship between this path element and the previous is implicit. +/// Ideally a path section would be defined by two vertices and a length. +/// We don't do this however, as it would effectively double the memory footprint +/// of path data. +/// +struct PathElement { + + // MARK: Lifecycle + + /// Initializes a new path with length of 0 + init(vertex: CurveVertex) { + length = 0 + self.vertex = vertex + } + + /// Initializes a new path with length + private init(length: CGFloat, vertex: CurveVertex) { + self.length = length + self.vertex = vertex + } + + // MARK: Internal + + /// The absolute Length of the path element. + let length: CGFloat + + /// The vertex of the element + let vertex: CurveVertex + + /// Returns a new path element define the span from the receiver to the new vertex. + func pathElementTo(_ toVertex: CurveVertex) -> PathElement { + PathElement(length: vertex.distanceTo(toVertex), vertex: toVertex) + } + + func updateVertex(newVertex: CurveVertex) -> PathElement { + PathElement(length: length, vertex: newVertex) + } + + /// Splits an element span defined by the receiver and fromElement to a position 0-1 + func splitElementAtPosition(fromElement: PathElement, atLength: CGFloat) -> + (leftSpan: (start: PathElement, end: PathElement), rightSpan: (start: PathElement, end: PathElement)) + { + /// Trim the span. Start and trim go into the first, trim and end go into second. + let trimResults = fromElement.vertex.trimCurve(toVertex: vertex, atLength: atLength, curveLength: length, maxSamples: 3) + + /// Create the elements for the break + let spanAStart = PathElement( + length: fromElement.length, + vertex: CurveVertex( + point: fromElement.vertex.point, + inTangent: fromElement.vertex.inTangent, + outTangent: trimResults.start.outTangent)) + /// Recalculating the length here is a waste as the trimCurve function also accurately calculates this length. + let spanAEnd = spanAStart.pathElementTo(trimResults.trimPoint) + + let spanBStart = PathElement(vertex: trimResults.trimPoint) + let spanBEnd = spanBStart.pathElementTo(trimResults.end) + return ( + leftSpan: (start: spanAStart, end: spanAEnd), + rightSpan: (start: spanBStart, end: spanBEnd)) + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/VectorsExtensions.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/VectorsExtensions.swift new file mode 100644 index 00000000000..bfdf10021ab --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Primitives/VectorsExtensions.swift @@ -0,0 +1,345 @@ +// +// Vector.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import CoreGraphics +import Foundation +import QuartzCore + +// MARK: - Vector1D + Codable + +/// Single value container. Needed because lottie sometimes wraps a Double in an array. +extension Vector1D: Codable { + + // MARK: Lifecycle + + public init(from decoder: Decoder) throws { + /// Try to decode an array of doubles + do { + var container = try decoder.unkeyedContainer() + value = try container.decode(Double.self) + } catch { + value = try decoder.singleValueContainer().decode(Double.self) + } + } + + // MARK: Public + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(value) + } + + // MARK: Internal + + var cgFloatValue: CGFloat { + CGFloat(value) + } + +} + +// MARK: - Vector1D + AnyInitializable + +extension Vector1D: AnyInitializable { + + init(value: Any) throws { + if + let array = value as? [Double], + let double = array.first + { + self.value = double + } else if let double = value as? Double { + self.value = double + } else { + throw InitializableError.invalidInput + } + } + +} + +extension Double { + var vectorValue: Vector1D { + Vector1D(self) + } +} + +// MARK: - Vector2D + +/// Needed for decoding json {x: y:} to a CGPoint +public struct Vector2D: Codable, Hashable { + + // MARK: Lifecycle + + init(x: Double, y: Double) { + self.x = x + self.y = y + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Vector2D.CodingKeys.self) + + do { + let xValue: [Double] = try container.decode([Double].self, forKey: .x) + x = xValue[0] + } catch { + x = try container.decode(Double.self, forKey: .x) + } + + do { + let yValue: [Double] = try container.decode([Double].self, forKey: .y) + y = yValue[0] + } catch { + y = try container.decode(Double.self, forKey: .y) + } + } + + // MARK: Public + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: Vector2D.CodingKeys.self) + try container.encode(x, forKey: .x) + try container.encode(y, forKey: .y) + } + + // MARK: Internal + + var x: Double + var y: Double + + var pointValue: CGPoint { + CGPoint(x: x, y: y) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case x + case y + } +} + +// MARK: AnyInitializable + +extension Vector2D: AnyInitializable { + + init(value: Any) throws { + guard let dictionary = value as? [String: Any] else { + throw InitializableError.invalidInput + } + + if + let array = dictionary[CodingKeys.x.rawValue] as? [Double], + let double = array.first + { + x = double + } else if let double = dictionary[CodingKeys.x.rawValue] as? Double { + x = double + } else { + throw InitializableError.invalidInput + } + if + let array = dictionary[CodingKeys.y.rawValue] as? [Double], + let double = array.first + { + y = double + } else if let double = dictionary[CodingKeys.y.rawValue] as? Double { + y = double + } else { + throw InitializableError.invalidInput + } + } +} + +extension CGPoint { + var vector2dValue: Vector2D { + Vector2D(x: Double(x), y: Double(y)) + } +} + +// MARK: - Vector3D + Codable + +/// A three dimensional vector. +/// These vectors are encoded and decoded from [Double] + +extension Vector3D: Codable { + + // MARK: Lifecycle + + init(x: CGFloat, y: CGFloat, z: CGFloat) { + self.x = Double(x) + self.y = Double(y) + self.z = Double(z) + } + + public init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + + if !container.isAtEnd { + x = try container.decode(Double.self) + } else { + x = 0 + } + + if !container.isAtEnd { + y = try container.decode(Double.self) + } else { + y = 0 + } + + if !container.isAtEnd { + z = try container.decode(Double.self) + } else { + z = 0 + } + } + + // MARK: Public + + public func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(x) + try container.encode(y) + try container.encode(z) + } + +} + +// MARK: - Vector3D + AnyInitializable + +extension Vector3D: AnyInitializable { + + init(value: Any) throws { + guard var array = value as? [Double] else { + throw InitializableError.invalidInput + } + x = array.count > 0 ? array.removeFirst() : 0 + y = array.count > 0 ? array.removeFirst() : 0 + z = array.count > 0 ? array.removeFirst() : 0 + } + +} + +extension Vector3D { + public var pointValue: CGPoint { + CGPoint(x: x, y: y) + } + + public var sizeValue: CGSize { + CGSize(width: x, height: y) + } +} + +extension CGPoint { + var vector3dValue: Vector3D { + Vector3D(x: x, y: y, z: 0) + } +} + +extension CGSize { + var vector3dValue: Vector3D { + Vector3D(x: width, y: height, z: 1) + } +} + +extension CATransform3D { + + static func makeSkew(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D { + let mCos = cos(skewAxis.toRadians()) + let mSin = sin(skewAxis.toRadians()) + let aTan = tan(skew.toRadians()) + + let transform1 = CATransform3D( + m11: mCos, + m12: mSin, + m13: 0, + m14: 0, + m21: -mSin, + m22: mCos, + m23: 0, + m24: 0, + m31: 0, + m32: 0, + m33: 1, + m34: 0, + m41: 0, + m42: 0, + m43: 0, + m44: 1) + + let transform2 = CATransform3D( + m11: 1, + m12: 0, + m13: 0, + m14: 0, + m21: aTan, + m22: 1, + m23: 0, + m24: 0, + m31: 0, + m32: 0, + m33: 1, + m34: 0, + m41: 0, + m42: 0, + m43: 0, + m44: 1) + + let transform3 = CATransform3D( + m11: mCos, + m12: -mSin, + m13: 0, + m14: 0, + m21: mSin, + m22: mCos, + m23: 0, + m24: 0, + m31: 0, + m32: 0, + m33: 1, + m34: 0, + m41: 0, + m42: 0, + m43: 0, + m44: 1) + return CATransform3DConcat(transform3, CATransform3DConcat(transform2, transform1)) + } + + static func makeTransform( + anchor: CGPoint, + position: CGPoint, + scale: CGSize, + rotation: CGFloat, + skew: CGFloat?, + skewAxis: CGFloat?) + -> CATransform3D + { + let result: CATransform3D + if let skew = skew, let skewAxis = skewAxis { + result = CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).skewed(skew: -skew, skewAxis: skewAxis) + .scaled(scale * 0.01).translated(anchor * -1) + } else { + result = CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).scaled(scale * 0.01).translated(anchor * -1) + } + + return result + } + + func rotated(_ degrees: CGFloat) -> CATransform3D { + CATransform3DRotate(self, degrees.toRadians(), 0, 0, 1) + } + + func translated(_ translation: CGPoint) -> CATransform3D { + CATransform3DTranslate(self, translation.x, translation.y, 0) + } + + func scaled(_ scale: CGSize) -> CATransform3D { + CATransform3DScale(self, scale.width, scale.height, 1) + } + + func skewed(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D { + CATransform3DConcat(CATransform3D.makeSkew(skew: skew, skewAxis: skewAxis), self) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Animation/AnimationPublic.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Animation/AnimationPublic.swift new file mode 100644 index 00000000000..ab5b7a61b72 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Animation/AnimationPublic.swift @@ -0,0 +1,269 @@ +// +// AnimationPublic.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/5/19. +// + +import CoreGraphics +import Foundation + +public extension Animation { + + /// A closure for an Animation download. The closure is passed `nil` if there was an error. + typealias DownloadClosure = (Animation?) -> Void + + /// The duration in seconds of the animation. + var duration: TimeInterval { + Double(endFrame - startFrame) / framerate + } + + /// The natural bounds in points of the animation. + var bounds: CGRect { + CGRect(x: 0, y: 0, width: width, height: height) + } + + /// The natural size in points of the animation. + var size: CGSize { + CGSize(width: width, height: height) + } + + // MARK: Animation (Loading) + + /// Loads an animation model from a bundle by its name. Returns `nil` if an animation is not found. + /// + /// - Parameter name: The name of the json file without the json extension. EG "StarAnimation" + /// - Parameter bundle: The bundle in which the animation is located. Defaults to `Bundle.main` + /// - Parameter subdirectory: A subdirectory in the bundle in which the animation is located. Optional. + /// - Parameter animationCache: A cache for holding loaded animations. Optional. + /// + /// - Returns: Deserialized `Animation`. Optional. + static func named( + _ name: String, + bundle: Bundle = Bundle.main, + subdirectory: String? = nil, + animationCache: AnimationCacheProvider? = nil) + -> Animation? + { + /// Create a cache key for the animation. + let cacheKey = bundle.bundlePath + (subdirectory ?? "") + "/" + name + + /// Check cache for animation + if + let animationCache = animationCache, + let animation = animationCache.animation(forKey: cacheKey) + { + /// If found, return the animation. + return animation + } + + do { + /// Decode animation. + guard let json = try bundle.getAnimationData(name, subdirectory: subdirectory) else { + return nil + } + let animation = try Animation.from(data: json) + animationCache?.setAnimation(animation, forKey: cacheKey) + return animation + } catch { + /// Decoding error. + LottieLogger.shared.warn("Error when decoding animation \"\(name)\": \(error)") + return nil + } + } + + /// Loads an animation from a specific filepath. + /// - Parameter filepath: The absolute filepath of the animation to load. EG "/User/Me/starAnimation.json" + /// - Parameter animationCache: A cache for holding loaded animations. Optional. + /// + /// - Returns: Deserialized `Animation`. Optional. + static func filepath( + _ filepath: String, + animationCache: AnimationCacheProvider? = nil) + -> Animation? + { + + /// Check cache for animation + if + let animationCache = animationCache, + let animation = animationCache.animation(forKey: filepath) + { + return animation + } + + do { + /// Decode the animation. + let json = try Data(contentsOf: URL(fileURLWithPath: filepath)) + let animation = try Animation.from(data: json) + animationCache?.setAnimation(animation, forKey: filepath) + return animation + } catch { + /// Decoding Error. + return nil + } + } + + /// Loads an animation model from the asset catalog by its name. Returns `nil` if an animation is not found. + /// - Parameter name: The name of the json file in the asset catalog. EG "StarAnimation" + /// - Parameter bundle: The bundle in which the animation is located. Defaults to `Bundle.main` + /// - Parameter animationCache: A cache for holding loaded animations. Optional. + /// - Returns: Deserialized `Animation`. Optional. + static func asset( + _ name: String, + bundle: Bundle = Bundle.main, + animationCache: AnimationCacheProvider? = nil) + -> Animation? + { + /// Create a cache key for the animation. + let cacheKey = bundle.bundlePath + "/" + name + + /// Check cache for animation + if + let animationCache = animationCache, + let animation = animationCache.animation(forKey: cacheKey) + { + /// If found, return the animation. + return animation + } + + /// Load jsonData from Asset + guard let json = Data.jsonData(from: name, in: bundle) else { + return nil + } + + do { + /// Decode animation. + let animation = try Animation.from(data: json) + animationCache?.setAnimation(animation, forKey: cacheKey) + return animation + } catch { + /// Decoding error. + return nil + } + } + + /// Loads a Lottie animation from a `Data` object containing a JSON animation. + /// + /// - Parameter data: The object to load the animation from. + /// - Parameter strategy: How the data should be decoded. Defaults to using the strategy set in `LottieConfiguration.shared`. + /// - Returns: Deserialized `Animation`. Optional. + /// + static func from( + data: Data, + strategy: DecodingStrategy = LottieConfiguration.shared.decodingStrategy) throws + -> Animation + { + switch strategy { + case .codable: + return try JSONDecoder().decode(Animation.self, from: data) + case .dictionaryBased: + let json = try JSONSerialization.jsonObject(with: data) + guard let dict = json as? [String: Any] else { + throw InitializableError.invalidInput + } + return try Animation(dictionary: dict) + } + } + + /// Loads a Lottie animation asynchronously from the URL. + /// + /// - Parameter url: The url to load the animation from. + /// - Parameter closure: A closure to be called when the animation has loaded. + /// - Parameter animationCache: A cache for holding loaded animations. + /// + static func loadedFrom( + url: URL, + closure: @escaping Animation.DownloadClosure, + animationCache: AnimationCacheProvider?) + { + + if let animationCache = animationCache, let animation = animationCache.animation(forKey: url.absoluteString) { + closure(animation) + } else { + let task = URLSession.shared.dataTask(with: url) { data, _, error in + guard error == nil, let jsonData = data else { + DispatchQueue.main.async { + closure(nil) + } + return + } + do { + let animation = try Animation.from(data: jsonData) + DispatchQueue.main.async { + animationCache?.setAnimation(animation, forKey: url.absoluteString) + closure(animation) + } + } catch { + DispatchQueue.main.async { + closure(nil) + } + } + + } + task.resume() + } + } + + // MARK: Animation (Helpers) + + /// Markers are a way to describe a point in time by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// Returns the Progress Time for the marker named. Returns nil if no marker found. + func progressTime(forMarker named: String) -> AnimationProgressTime? { + guard let markers = markerMap, let marker = markers[named] else { + return nil + } + return progressTime(forFrame: marker.frameTime) + } + + /// Markers are a way to describe a point in time by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// Returns the Frame Time for the marker named. Returns nil if no marker found. + func frameTime(forMarker named: String) -> AnimationFrameTime? { + guard let markers = markerMap, let marker = markers[named] else { + return nil + } + return marker.frameTime + } + + /// Converts Frame Time (Seconds * Framerate) into Progress Time + /// (optionally clamped to between 0 and 1). + func progressTime( + forFrame frameTime: AnimationFrameTime, + clamped: Bool = true) + -> AnimationProgressTime + { + let progressTime = ((frameTime - startFrame) / (endFrame - startFrame)) + + if clamped { + return progressTime.clamp(0, 1) + } else { + return progressTime + } + } + + /// Converts Progress Time (0 to 1) into Frame Time (Seconds * Framerate) + func frameTime(forProgress progressTime: AnimationProgressTime) -> AnimationFrameTime { + ((endFrame - startFrame) * progressTime) + startFrame + } + + /// Converts Frame Time (Seconds * Framerate) into Time (Seconds) + func time(forFrame frameTime: AnimationFrameTime) -> TimeInterval { + Double(frameTime - startFrame) / framerate + } + + /// Converts Time (Seconds) into Frame Time (Seconds * Framerate) + func frameTime(forTime time: TimeInterval) -> AnimationFrameTime { + CGFloat(time * framerate) + startFrame + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Animation/AnimationView.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Animation/AnimationView.swift new file mode 100644 index 00000000000..700cbfb229f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Animation/AnimationView.swift @@ -0,0 +1,1302 @@ +// +// AnimationView.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/23/19. +// + +import Foundation +import QuartzCore + +// MARK: - LottieBackgroundBehavior + +/// Describes the behavior of an AnimationView when the app is moved to the background. +public enum LottieBackgroundBehavior { + /// Stop the animation and reset it to the beginning of its current play time. The completion block is called. + case stop + + /// Pause the animation in its current state. The completion block is called. + /// - This is the default when using the Main Thread rendering engine. + case pause + + /// Pause the animation and restart it when the application moves to the foreground. The completion block is stored and called when the animation completes. + case pauseAndRestore + + /// Stops the animation and sets it to the end of its current play time. The completion block is called. + case forceFinish + + /// The animation continues playing in the background. + /// - This is the default when using the Core Animation rendering engine. + /// Playing an animation using the Core Animation engine doesn't come with any CPU overhead, + /// so using `.continuePlaying` avoids the need to stop and then resume the animation + /// (which does come with some CPU overhead). + /// - This mode should not be used with the Main Thread rendering engine. + case continuePlaying + + // MARK: Public + + /// The default background behavior, based on the rendering engine being used to play the animation. + /// - Playing an animation using the Main Thread rendering engine comes with CPU overhead, + /// so the animation should be paused or stopped when the `AnimationView` is not visible. + /// - Playing an animation using the Core Animation rendering engine does not come with any + /// CPU overhead, so these animations do not need to be paused in the background. + public static func `default`(for renderingEngine: RenderingEngine) -> LottieBackgroundBehavior { + switch renderingEngine { + case .mainThread: + return .pause + case .coreAnimation: + return .continuePlaying + } + } +} + +// MARK: - LottieLoopMode + +/// Defines animation loop behavior +public enum LottieLoopMode { + /// Animation is played once then stops. + case playOnce + /// Animation will loop from beginning to end until stopped. + case loop + /// Animation will play forward, then backwards and loop until stopped. + case autoReverse + /// Animation will loop from beginning to end up to defined amount of times. + case `repeat`(Float) + /// Animation will play forward, then backwards a defined amount of times. + case repeatBackwards(Float) +} + +// MARK: Equatable + +extension LottieLoopMode: Equatable { + public static func == (lhs: LottieLoopMode, rhs: LottieLoopMode) -> Bool { + switch (lhs, rhs) { + case (.repeat(let lhsAmount), .repeat(let rhsAmount)), + (.repeatBackwards(let lhsAmount), .repeatBackwards(let rhsAmount)): + return lhsAmount == rhsAmount + case (.playOnce, .playOnce), + (.loop, .loop), + (.autoReverse, .autoReverse): + return true + default: + return false + } + } +} + +// MARK: - AnimationView + +@IBDesignable +final public class AnimationView: AnimationViewBase { + + // MARK: Lifecycle + + // MARK: - Public (Initializers) + + /// Initializes an AnimationView with an animation. + public init( + animation: Animation?, + imageProvider: AnimationImageProvider? = nil, + textProvider: AnimationTextProvider = DefaultTextProvider(), + fontProvider: AnimationFontProvider = DefaultFontProvider(), + configuration: LottieConfiguration = .shared) + { + self.animation = animation + self.imageProvider = imageProvider ?? BundleImageProvider(bundle: Bundle.main, searchPath: nil) + self.textProvider = textProvider + self.fontProvider = fontProvider + self.configuration = configuration + super.init(frame: .zero) + commonInit() + makeAnimationLayer(usingEngine: configuration.renderingEngine) + if let animation = animation { + frame = animation.bounds + } + } + + public init(configuration: LottieConfiguration = .shared) { + animation = nil + imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil) + textProvider = DefaultTextProvider() + fontProvider = DefaultFontProvider() + self.configuration = configuration + super.init(frame: .zero) + commonInit() + } + + public override init(frame: CGRect) { + animation = nil + imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil) + textProvider = DefaultTextProvider() + fontProvider = DefaultFontProvider() + configuration = .shared + super.init(frame: frame) + commonInit() + } + + required public init?(coder aDecoder: NSCoder) { + imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil) + textProvider = DefaultTextProvider() + fontProvider = DefaultFontProvider() + configuration = .shared + super.init(coder: aDecoder) + commonInit() + } + + // MARK: Public + + /// The configuration that this `AnimationView` uses when playing its animation + public let configuration: LottieConfiguration + + /// Value Providers that have been registered using `setValueProvider(_:keypath:)` + public private(set) var valueProviders = [AnimationKeypath: AnyValueProvider]() + + /// Describes the behavior of an AnimationView when the app is moved to the background. + /// + /// The default for the Main Thread animation engine is `pause`, + /// which pauses the animation when the application moves to + /// the background. This prevents the animation from consuming CPU + /// resources when not on-screen. The completion block is called with + /// `false` for completed. + /// + /// The default for the Core Animation engine is `continuePlaying`, + /// since the Core Animation engine does not have any CPU overhead. + public var backgroundBehavior: LottieBackgroundBehavior { + get { + let currentBackgroundBehavior = _backgroundBehavior ?? .default(for: currentRenderingEngine ?? .mainThread) + + if + currentRenderingEngine == .mainThread, + _backgroundBehavior == .continuePlaying + { + LottieLogger.shared.assertionFailure(""" + `LottieBackgroundBehavior.continuePlaying` should not be used with the Main Thread + rendering engine, since this would waste CPU resources on playing an animation + that is not visible. Consider using a different background mode, or switching to + the Core Animation rendering engine (which does not have any CPU overhead). + """) + } + + return currentBackgroundBehavior + } + set { + _backgroundBehavior = newValue + } + } + + /// Sets the animation backing the animation view. Setting this will clear the + /// view's contents, completion blocks and current state. The new animation will + /// be loaded up and set to the beginning of its timeline. + public var animation: Animation? { + didSet { + makeAnimationLayer(usingEngine: configuration.renderingEngine) + } + } + + /// Sets the image provider for the animation view. An image provider provides the + /// animation with its required image data. + /// + /// Setting this will cause the animation to reload its image contents. + public var imageProvider: AnimationImageProvider { + didSet { + animationLayer?.imageProvider = imageProvider.cachedImageProvider + reloadImages() + } + } + + /// Sets the text provider for animation view. A text provider provides the + /// animation with values for text layers + public var textProvider: AnimationTextProvider { + didSet { + animationLayer?.textProvider = textProvider + } + } + + /// Sets the text provider for animation view. A text provider provides the + /// animation with values for text layers + public var fontProvider: AnimationFontProvider { + didSet { + animationLayer?.fontProvider = fontProvider + } + } + + /// Returns `true` if the animation is currently playing. + public var isAnimationPlaying: Bool { + guard let animationLayer = animationLayer else { + return false + } + + if let valueFromLayer = animationLayer.isAnimationPlaying { + return valueFromLayer + } else { + return animationLayer.animation(forKey: activeAnimationName) != nil + } + } + + /// Returns `true` if the animation will start playing when this view is added to a window. + public var isAnimationQueued: Bool { + animationContext != nil && waitingToPlayAnimation + } + + /// Sets the loop behavior for `play` calls. Defaults to `playOnce` + public var loopMode: LottieLoopMode = .playOnce { + didSet { + updateInFlightAnimation() + } + } + + /// When `true` the animation view will rasterize its contents when not animating. + /// Rasterizing will improve performance of static animations. + /// + /// Note: this will not produce crisp results at resolutions above the animations natural resolution. + /// + /// Defaults to `false` + public var shouldRasterizeWhenIdle = false { + didSet { + updateRasterizationState() + } + } + + /// Sets the current animation time with a Progress Time + /// + /// Note: Setting this will stop the current animation, if any. + /// Note 2: If `animation` is nil, setting this will fallback to 0 + public var currentProgress: AnimationProgressTime { + set { + if let animation = animation { + currentFrame = animation.frameTime(forProgress: newValue) + } else { + currentFrame = 0 + } + } + get { + if let animation = animation { + return animation.progressTime(forFrame: currentFrame) + } else { + return 0 + } + } + } + + /// Sets the current animation time with a time in seconds. + /// + /// Note: Setting this will stop the current animation, if any. + /// Note 2: If `animation` is nil, setting this will fallback to 0 + public var currentTime: TimeInterval { + set { + if let animation = animation { + currentFrame = animation.frameTime(forTime: newValue) + } else { + currentFrame = 0 + } + } + get { + if let animation = animation { + return animation.time(forFrame: currentFrame) + } else { + return 0 + } + } + } + + /// Sets the current animation time with a frame in the animations framerate. + /// + /// Note: Setting this will stop the current animation, if any. + public var currentFrame: AnimationFrameTime { + set { + removeCurrentAnimationIfNecessary() + updateAnimationFrame(newValue) + } + get { + animationLayer?.currentFrame ?? 0 + } + } + + /// Returns the current animation frame while an animation is playing. + public var realtimeAnimationFrame: AnimationFrameTime { + isAnimationPlaying ? animationLayer?.presentation()?.currentFrame ?? currentFrame : currentFrame + } + + /// Returns the current animation frame while an animation is playing. + public var realtimeAnimationProgress: AnimationProgressTime { + if let animation = animation { + return animation.progressTime(forFrame: realtimeAnimationFrame) + } + return 0 + } + + /// Sets the speed of the animation playback. Defaults to 1 + public var animationSpeed: CGFloat = 1 { + didSet { + updateInFlightAnimation() + } + } + + /// When `true` the animation will play back at the framerate encoded in the + /// `Animation` model. When `false` the animation will play at the framerate + /// of the device. + /// + /// Defaults to false + public var respectAnimationFrameRate = false { + didSet { + animationLayer?.respectAnimationFrameRate = respectAnimationFrameRate + } + } + + /// Controls the cropping of an Animation. Setting this property will crop the animation + /// to the current views bounds by the viewport frame. The coordinate space is specified + /// in the animation's coordinate space. + /// + /// Animatable. + public var viewportFrame: CGRect? = nil { + didSet { + + // This is really ugly, but is needed to trigger a layout pass within an animation block. + // Typically this happens automatically, when layout objects are UIView based. + // The animation layer is a CALayer which will not implicitly grab the animation + // duration of a UIView animation block. + // + // By setting bounds and then resetting bounds the UIView animation block's + // duration and curve are captured and added to the layer. This is used in the + // layout block to animate the animationLayer's position and size. + let rect = bounds + self.bounds = CGRect.zero + self.bounds = rect + self.setNeedsLayout() + } + } + + override public var intrinsicContentSize: CGSize { + if let animation = animation { + return animation.bounds.size + } + return .zero + } + + /// The rendering engine currently being used by this view. + /// - This will only be `nil` in cases where the configuration is `automatic` + /// but a `RootAnimationLayer` hasn't been constructed yet + public var currentRenderingEngine: RenderingEngine? { + switch configuration.renderingEngine { + case .specific(let engine): + return engine + + case .automatic: + guard let animationLayer = animationLayer else { + return nil + } + + if animationLayer is CoreAnimationLayer { + return .coreAnimation + } else { + return .mainThread + } + } + } + + /// Plays the animation from its current state to the end. + /// + /// - Parameter completion: An optional completion closure to be called when the animation completes playing. + public func play(completion: LottieCompletionBlock? = nil) { + guard let animation = animation else { + return + } + + /// Build a context for the animation. + let context = AnimationContext( + playFrom: CGFloat(animation.startFrame), + playTo: CGFloat(animation.endFrame), + closure: completion) + removeCurrentAnimationIfNecessary() + addNewAnimationForContext(context) + } + + /// Plays the animation from a progress (0-1) to a progress (0-1). + /// + /// - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress. + /// - Parameter toProgress: The end progress of the animation. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + public func play( + fromProgress: AnimationProgressTime? = nil, + toProgress: AnimationProgressTime, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + guard let animation = animation else { + return + } + + removeCurrentAnimationIfNecessary() + if let loopMode = loopMode { + /// Set the loop mode, if one was supplied + self.loopMode = loopMode + } + let context = AnimationContext( + playFrom: animation.frameTime(forProgress: fromProgress ?? currentProgress), + playTo: animation.frameTime(forProgress: toProgress), + closure: completion) + addNewAnimationForContext(context) + } + + /// Plays the animation from a start frame to an end frame in the animation's framerate. + /// + /// - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame. + /// - Parameter toFrame: The end frame of the animation. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + public func play( + fromFrame: AnimationFrameTime? = nil, + toFrame: AnimationFrameTime, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + removeCurrentAnimationIfNecessary() + if let loopMode = loopMode { + /// Set the loop mode, if one was supplied + self.loopMode = loopMode + } + + let context = AnimationContext( + playFrom: fromFrame ?? currentProgress, + playTo: toFrame, + closure: completion) + addNewAnimationForContext(context) + } + + /// Plays the animation from a named marker to another marker. + /// + /// Markers are point in time that are encoded into the Animation data and assigned + /// a name. + /// + /// NOTE: If markers are not found the play command will exit. + /// + /// - Parameter fromMarker: The start marker for the animation playback. If `nil` the + /// animation will start at the current progress. + /// - Parameter toMarker: The end marker for the animation playback. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + public func play( + fromMarker: String? = nil, + toMarker: String, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + + guard let animation = animation, let markers = animation.markerMap, let to = markers[toMarker] else { + return + } + + removeCurrentAnimationIfNecessary() + if let loopMode = loopMode { + /// Set the loop mode, if one was supplied + self.loopMode = loopMode + } + + let fromTime: CGFloat + if let fromName = fromMarker, let from = markers[fromName] { + fromTime = CGFloat(from.frameTime) + } else { + fromTime = currentFrame + } + + let context = AnimationContext( + playFrom: fromTime, + playTo: CGFloat(to.frameTime), + closure: completion) + addNewAnimationForContext(context) + } + + /// Stops the animation and resets the view to its start frame. + /// + /// The completion closure will be called with `false` + public func stop() { + removeCurrentAnimation() + currentFrame = 0 + } + + /// Pauses the animation in its current state. + /// + /// The completion closure will be called with `false` + public func pause() { + removeCurrentAnimation() + } + + /// Reloads the images supplied to the animation from the `imageProvider` + public func reloadImages() { + animationLayer?.reloadImages() + } + + /// Forces the AnimationView to redraw its contents. + public func forceDisplayUpdate() { + animationLayer?.forceDisplayUpdate() + } + + /// Sets a ValueProvider for the specified keypath. The value provider will be set + /// on all properties that match the keypath. + /// + /// Nearly all properties of a Lottie animation can be changed at runtime using a + /// combination of `Animation Keypaths` and `Value Providers`. + /// Setting a ValueProvider on a keypath will cause the animation to update its + /// contents and read the new Value Provider. + /// + /// A value provider provides a typed value on a frame by frame basis. + /// + /// - Parameter valueProvider: The new value provider for the properties. + /// - Parameter keypath: The keypath used to search for properties. + /// + /// Example: + /// ``` + /// /// A keypath that finds the color value for all `Fill 1` nodes. + /// let fillKeypath = AnimationKeypath(keypath: "**.Fill 1.Color") + /// /// A Color Value provider that returns a reddish color. + /// let redValueProvider = ColorValueProvider(Color(r: 1, g: 0.2, b: 0.3, a: 1)) + /// /// Set the provider on the animationView. + /// animationView.setValueProvider(redValueProvider, keypath: fillKeypath) + /// ``` + public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + guard let animationLayer = animationLayer else { return } + + valueProviders[keypath] = valueProvider + animationLayer.setValueProvider(valueProvider, keypath: keypath) + } + + /// Reads the value of a property specified by the Keypath. + /// Returns nil if no property is found. + /// + /// - Parameter for: The keypath used to search for the property. + /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used. + public func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? { + animationLayer?.getValue(for: keypath, atFrame: atFrame) + } + + /// Reads the original value of a property specified by the Keypath. + /// This will ignore any value providers and can be useful when implementing a value providers that makes change to the original value from the animation. + /// Returns nil if no property is found. + /// + /// - Parameter for: The keypath used to search for the property. + /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used. + public func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? { + animationLayer?.getOriginalValue(for: keypath, atFrame: atFrame) + } + + /// Logs all child keypaths. + public func logHierarchyKeypaths() { + animationLayer?.logHierarchyKeypaths() + } + + /// Searches for the nearest child layer to the first Keypath and adds the subview + /// to that layer. The subview will move and animate with the child layer. + /// Furthermore the subview will be in the child layers coordinate space. + /// + /// Note: if no layer is found for the keypath, then nothing happens. + /// + /// - Parameter subview: The subview to add to the found animation layer. + /// - Parameter keypath: The keypath used to find the animation layer. + /// + /// Example: + /// ``` + /// /// A keypath that finds `Layer 1` + /// let layerKeypath = AnimationKeypath(keypath: "Layer 1") + /// + /// /// Wrap the custom view in an `AnimationSubview` + /// let subview = AnimationSubview() + /// subview.addSubview(customView) + /// + /// /// Set the provider on the animationView. + /// animationView.addSubview(subview, forLayerAt: layerKeypath) + /// ``` + public func addSubview(_ subview: AnimationSubview, forLayerAt keypath: AnimationKeypath) { + guard let sublayer = animationLayer?.layer(for: keypath) else { + return + } + setNeedsLayout() + layoutIfNeeded() + forceDisplayUpdate() + addSubview(subview) + if let subViewLayer = subview.viewLayer { + sublayer.addSublayer(subViewLayer) + } + } + + /// Converts a CGRect from the AnimationView's coordinate space into the + /// coordinate space of the layer found at Keypath. + /// + /// If no layer is found, nil is returned + /// + /// - Parameter rect: The CGRect to convert. + /// - Parameter toLayerAt: The keypath used to find the layer. + public func convert(_ rect: CGRect, toLayerAt keypath: AnimationKeypath?) -> CGRect? { + guard let animationLayer = animationLayer else { return nil } + guard let keypath = keypath else { + return viewLayer?.convert(rect, to: animationLayer) + } + guard let sublayer = animationLayer.layer(for: keypath) else { + return nil + } + setNeedsLayout() + layoutIfNeeded() + forceDisplayUpdate() + return animationLayer.convert(rect, to: sublayer) + } + + /// Converts a CGPoint from the AnimationView's coordinate space into the + /// coordinate space of the layer found at Keypath. + /// + /// If no layer is found, nil is returned + /// + /// - Parameter point: The CGPoint to convert. + /// - Parameter toLayerAt: The keypath used to find the layer. + public func convert(_ point: CGPoint, toLayerAt keypath: AnimationKeypath?) -> CGPoint? { + guard let animationLayer = animationLayer else { return nil } + guard let keypath = keypath else { + return viewLayer?.convert(point, to: animationLayer) + } + guard let sublayer = animationLayer.layer(for: keypath) else { + return nil + } + setNeedsLayout() + layoutIfNeeded() + forceDisplayUpdate() + return animationLayer.convert(point, to: sublayer) + } + + /// Sets the enabled state of all animator nodes found with the keypath search. + /// This can be used to interactively enable / disable parts of the animation. + /// + /// - Parameter isEnabled: When true the animator nodes affect the rendering tree. When false the node is removed from the tree. + /// - Parameter keypath: The keypath used to find the node(s). + public func setNodeIsEnabled(isEnabled: Bool, keypath: AnimationKeypath) { + guard let animationLayer = animationLayer else { return } + let nodes = animationLayer.animatorNodes(for: keypath) + if let nodes = nodes { + for node in nodes { + node.isEnabled = isEnabled + } + forceDisplayUpdate() + } + } + + /// Markers are a way to describe a point in time by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// Returns the Progress Time for the marker named. Returns nil if no marker found. + public func progressTime(forMarker named: String) -> AnimationProgressTime? { + guard let animation = animation else { + return nil + } + return animation.progressTime(forMarker: named) + } + + /// Markers are a way to describe a point in time by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// Returns the Frame Time for the marker named. Returns nil if no marker found. + public func frameTime(forMarker named: String) -> AnimationFrameTime? { + guard let animation = animation else { + return nil + } + return animation.frameTime(forMarker: named) + } + + // MARK: Internal + + var animationLayer: RootAnimationLayer? = nil + + /// Set animation name from Interface Builder + @IBInspectable var animationName: String? { + didSet { + self.animation = animationName.flatMap { + Animation.named($0, animationCache: nil) + } + } + } + + override func layoutAnimation() { + guard let animation = animation, let animationLayer = animationLayer else { return } + var position = animation.bounds.center + let xform: CATransform3D + var shouldForceUpdates = false + + if let viewportFrame = viewportFrame { + shouldForceUpdates = contentMode == .redraw + + let compAspect = viewportFrame.size.width / viewportFrame.size.height + let viewAspect = bounds.size.width / bounds.size.height + let dominantDimension = compAspect > viewAspect ? bounds.size.width : bounds.size.height + let compDimension = compAspect > viewAspect ? viewportFrame.size.width : viewportFrame.size.height + let scale = dominantDimension / compDimension + + let viewportOffset = animation.bounds.center - viewportFrame.center + xform = CATransform3DTranslate(CATransform3DMakeScale(scale, scale, 1), viewportOffset.x, viewportOffset.y, 0) + position = bounds.center + } else { + switch contentMode { + case .scaleToFill: + position = bounds.center + xform = CATransform3DMakeScale( + bounds.size.width / animation.size.width, + bounds.size.height / animation.size.height, + 1); + case .scaleAspectFit: + position = bounds.center + let compAspect = animation.size.width / animation.size.height + let viewAspect = bounds.size.width / bounds.size.height + let dominantDimension = compAspect > viewAspect ? bounds.size.width : bounds.size.height + let compDimension = compAspect > viewAspect ? animation.size.width : animation.size.height + let scale = dominantDimension / compDimension + xform = CATransform3DMakeScale(scale, scale, 1) + case .scaleAspectFill: + position = bounds.center + let compAspect = animation.size.width / animation.size.height + let viewAspect = bounds.size.width / bounds.size.height + let scaleWidth = compAspect < viewAspect + let dominantDimension = scaleWidth ? bounds.size.width : bounds.size.height + let compDimension = scaleWidth ? animation.size.width : animation.size.height + let scale = dominantDimension / compDimension + xform = CATransform3DMakeScale(scale, scale, 1) + case .redraw: + shouldForceUpdates = true + xform = CATransform3DIdentity + case .center: + position = bounds.center + xform = CATransform3DIdentity + case .top: + position.x = bounds.center.x + xform = CATransform3DIdentity + case .bottom: + position.x = bounds.center.x + position.y = bounds.maxY - animation.bounds.midY + xform = CATransform3DIdentity + case .left: + position.y = bounds.center.y + xform = CATransform3DIdentity + case .right: + position.y = bounds.center.y + position.x = bounds.maxX - animation.bounds.midX + xform = CATransform3DIdentity + case .topLeft: + xform = CATransform3DIdentity + case .topRight: + position.x = bounds.maxX - animation.bounds.midX + xform = CATransform3DIdentity + case .bottomLeft: + position.y = bounds.maxY - animation.bounds.midY + xform = CATransform3DIdentity + case .bottomRight: + position.x = bounds.maxX - animation.bounds.midX + position.y = bounds.maxY - animation.bounds.midY + xform = CATransform3DIdentity + + #if os(iOS) || os(tvOS) + @unknown default: + LottieLogger.shared.assertionFailure("unsupported contentMode: \(contentMode.rawValue)") + xform = CATransform3DIdentity + #endif + } + } + + // UIView Animation does not implicitly set CAAnimation time or timing fuctions. + // If layout is changed in an animation we must get the current animation duration + // and timing function and then manually create a CAAnimation to match the UIView animation. + // If layout is changed without animation, explicitly set animation duration to 0.0 + // inside CATransaction to avoid unwanted artifacts. + /// Check if any animation exist on the view's layer, and match it. + if let key = viewLayer?.animationKeys()?.first, let animation = viewLayer?.animation(forKey: key) { + // The layout is happening within an animation block. Grab the animation data. + + let positionKey = "LayoutPositionAnimation" + let transformKey = "LayoutTransformAnimation" + animationLayer.removeAnimation(forKey: positionKey) + animationLayer.removeAnimation(forKey: transformKey) + + let positionAnimation = animation.copy() as? CABasicAnimation ?? CABasicAnimation(keyPath: "position") + positionAnimation.keyPath = "position" + positionAnimation.isAdditive = false + positionAnimation.fromValue = (animationLayer.presentation() ?? animationLayer).position + positionAnimation.toValue = position + positionAnimation.isRemovedOnCompletion = true + + let xformAnimation = animation.copy() as? CABasicAnimation ?? CABasicAnimation(keyPath: "transform") + xformAnimation.keyPath = "transform" + xformAnimation.isAdditive = false + xformAnimation.fromValue = (animationLayer.presentation() ?? animationLayer).transform + xformAnimation.toValue = xform + xformAnimation.isRemovedOnCompletion = true + + animationLayer.position = position + animationLayer.transform = xform + #if os(OSX) + animationLayer.anchorPoint = layer?.anchorPoint ?? CGPoint.zero + #else + animationLayer.anchorPoint = layer.anchorPoint + #endif + animationLayer.add(positionAnimation, forKey: positionKey) + animationLayer.add(xformAnimation, forKey: transformKey) + } else { + // In performance tests, we have to wrap the animation view setup + // in a `CATransaction` in order for the layers to be deallocated at + // the correct time. The `CATransaction`s in this method interfere + // with the ones managed by the performance test, and aren't actually + // necessary in a headless environment, so we disable them. + if TestHelpers.performanceTestsAreRunning { + animationLayer.position = position + animationLayer.transform = xform + } else { + CATransaction.begin() + CATransaction.setAnimationDuration(0.0) + CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .linear)) + animationLayer.position = position + animationLayer.transform = xform + CATransaction.commit() + } + } + + if shouldForceUpdates { + animationLayer.forceDisplayUpdate() + } + } + + func updateRasterizationState() { + if isAnimationPlaying { + animationLayer?.shouldRasterize = false + } else { + animationLayer?.shouldRasterize = shouldRasterizeWhenIdle + } + } + + /// Updates the animation frame. Does not affect any current animations + func updateAnimationFrame(_ newFrame: CGFloat) { + // In performance tests, we have to wrap the animation view setup + // in a `CATransaction` in order for the layers to be deallocated at + // the correct time. The `CATransaction`s in this method interfere + // with the ones managed by the performance test, and aren't actually + // necessary in a headless environment, so we disable them. + if TestHelpers.performanceTestsAreRunning { + animationLayer?.currentFrame = newFrame + animationLayer?.forceDisplayUpdate() + return + } + + CATransaction.begin() + CATransaction.setCompletionBlock { + self.animationLayer?.forceDisplayUpdate() + } + CATransaction.setDisableActions(true) + animationLayer?.currentFrame = newFrame + CATransaction.commit() + } + + @objc + override func animationWillMoveToBackground() { + updateAnimationForBackgroundState() + } + + @objc + override func animationWillEnterForeground() { + updateAnimationForForegroundState() + } + + override func animationMovedToWindow() { + /// Don't update any state if the `superview` is `nil` + /// When A viewA owns superViewB, it removes the superViewB from the window. At this point, viewA still owns superViewB and triggers the viewA method: -didmovetowindow + guard superview != nil else { return } + + if window != nil { + updateAnimationForForegroundState() + } else { + updateAnimationForBackgroundState() + } + } + + // MARK: Fileprivate + + fileprivate var animationContext: AnimationContext? + fileprivate var _activeAnimationName: String = AnimationView.animationName + fileprivate var animationID = 0 + + fileprivate var waitingToPlayAnimation = false + + fileprivate var activeAnimationName: String { + switch animationLayer?.primaryAnimationKey { + case .specific(let animationKey): + return animationKey + case .managed, nil: + return _activeAnimationName + } + } + + fileprivate func makeAnimationLayer(usingEngine renderingEngine: RenderingEngineOption) { + + /// Remove current animation if any + removeCurrentAnimation() + + if let oldAnimation = animationLayer { + oldAnimation.removeFromSuperlayer() + } + + invalidateIntrinsicContentSize() + + guard let animation = animation else { + return + } + + let rootAnimationLayer: RootAnimationLayer? + switch renderingEngine { + case .automatic: + rootAnimationLayer = makeAutomaticEngineLayer(for: animation) + case .specific(.coreAnimation): + rootAnimationLayer = makeCoreAnimationLayer(for: animation) + case .specific(.mainThread): + rootAnimationLayer = makeMainThreadAnimationLayer(for: animation) + } + + guard let animationLayer = rootAnimationLayer else { + return + } + + animationLayer.renderScale = screenScale + + viewLayer?.addSublayer(animationLayer) + self.animationLayer = animationLayer + reloadImages() + animationLayer.setNeedsDisplay() + setNeedsLayout() + currentFrame = CGFloat(animation.startFrame) + } + + fileprivate func makeMainThreadAnimationLayer(for animation: Animation) -> MainThreadAnimationLayer { + MainThreadAnimationLayer( + animation: animation, + imageProvider: imageProvider.cachedImageProvider, + textProvider: textProvider, + fontProvider: fontProvider) + } + + fileprivate func makeCoreAnimationLayer(for animation: Animation) -> CoreAnimationLayer? { + do { + let coreAnimationLayer = try CoreAnimationLayer( + animation: animation, + imageProvider: imageProvider.cachedImageProvider, + fontProvider: fontProvider, + compatibilityTrackerMode: .track) + + coreAnimationLayer.didSetUpAnimation = { compatibilityIssues in + LottieLogger.shared.assert( + compatibilityIssues.isEmpty, + "Encountered Core Animation compatibility issues while setting up animation:\n" + + compatibilityIssues.map { $0.description }.joined(separator: "\n") + "\n\n" + + """ + This animation cannot be rendered correctly by the Core Animation engine. + To resolve this issue, you can use `RenderingEngineOption.automatic`, which automatically falls back + to the Main Thread rendering engine when necessary, or just use `RenderingEngineOption.mainThread`. + + """) + } + + return coreAnimationLayer + } catch { + // This should never happen, because we initialize the `CoreAnimationLayer` with + // `CompatibilityTracker.Mode.track` (which reports errors in `didSetUpAnimation`, + // not by throwing). + LottieLogger.shared.assertionFailure("Encountered unexpected error \(error)") + return nil + } + } + + fileprivate func makeAutomaticEngineLayer(for animation: Animation) -> CoreAnimationLayer? { + do { + // Attempt to set up the Core Animation layer. This can either throw immediately in `init`, + // or throw an error later in `CALayer.display()` that will be reported in `didSetUpAnimation`. + let coreAnimationLayer = try CoreAnimationLayer( + animation: animation, + imageProvider: imageProvider.cachedImageProvider, + fontProvider: fontProvider, + compatibilityTrackerMode: .abort) + + coreAnimationLayer.didSetUpAnimation = { [weak self] issues in + self?.automaticEngineLayerDidSetUpAnimation(issues) + } + + return coreAnimationLayer + } catch { + if case CompatibilityTracker.Error.encounteredCompatibilityIssue(let compatibilityIssue) = error { + automaticEngineLayerDidSetUpAnimation([compatibilityIssue]) + } else { + // This should never happen, because we expect `CoreAnimationLayer` to only throw + // `CompatibilityTracker.Error.encounteredCompatibilityIssue` errors. + LottieLogger.shared.assertionFailure("Encountered unexpected error \(error)") + automaticEngineLayerDidSetUpAnimation([]) + } + + return nil + } + } + + // Handles any compatibility issues with the Core Animation engine + // by falling back to the Main Thread engine + fileprivate func automaticEngineLayerDidSetUpAnimation(_ compatibilityIssues: [CompatibilityIssue]) { + // If there weren't any compatibility issues, then there's nothing else to do + if compatibilityIssues.isEmpty { + return + } + + LottieLogger.shared.warn( + "Encountered Core Animation compatibility issue while setting up animation:\n" + + compatibilityIssues.map { $0.description }.joined(separator: "\n") + "\n" + + """ + This animation may have additional compatibility issues, but animation setup was cancelled early to avoid wasted work. + + Automatically falling back to Main Thread rendering engine. This fallback comes with some additional performance + overhead, which can be reduced by manually specifying that this animation should always use the Main Thread engine. + + """) + + let animationContext = self.animationContext + let currentFrame = self.currentFrame + + makeAnimationLayer(usingEngine: .mainThread) + + // Set up the Main Thread animation layer using the same configuration that + // was being used by the previous Core Animation layer + self.currentFrame = currentFrame + + if let animationContext = animationContext { + // `AnimationContext.closure` (`AnimationCompletionDelegate`) is a reference type + // that is the animation layer's `CAAnimationDelegate`, and holds a reference to + // the animation layer. Reusing a single instance across different animation layers + // can cause the animation setup to fail, so we create a copy of the `animationContext`: + addNewAnimationForContext(AnimationContext( + playFrom: animationContext.playFrom, + playTo: animationContext.playTo, + closure: animationContext.closure.completionBlock)) + } + } + + fileprivate func updateAnimationForBackgroundState() { + if let currentContext = animationContext { + switch backgroundBehavior { + case .stop: + removeCurrentAnimation() + updateAnimationFrame(currentContext.playFrom) + case .pause: + removeCurrentAnimation() + case .pauseAndRestore: + currentContext.closure.ignoreDelegate = true + removeCurrentAnimation() + /// Keep the stale context around for when the app enters the foreground. + animationContext = currentContext + case .forceFinish: + removeCurrentAnimation() + updateAnimationFrame(currentContext.playTo) + case .continuePlaying: + break + } + } + } + + fileprivate func updateAnimationForForegroundState() { + if let currentContext = animationContext { + if waitingToPlayAnimation { + waitingToPlayAnimation = false + addNewAnimationForContext(currentContext) + } else if backgroundBehavior == .pauseAndRestore { + /// Restore animation from saved state + updateInFlightAnimation() + } + } + } + + /// Removes the current animation and pauses the animation at the current frame + /// if necessary before setting up a new animation. + /// - This is not necessary with the Core Animation engine, and skipping + /// this step lets us avoid building the animations twice (once paused + /// and once again playing) + fileprivate func removeCurrentAnimationIfNecessary() { + switch currentRenderingEngine { + case .mainThread: + removeCurrentAnimation() + case .coreAnimation, nil: + break + } + } + + /// Stops the current in flight animation and freezes the animation in its current state. + fileprivate func removeCurrentAnimation() { + guard animationContext != nil else { return } + let pauseFrame = realtimeAnimationFrame + animationLayer?.removeAnimation(forKey: activeAnimationName) + updateAnimationFrame(pauseFrame) + animationContext = nil + } + + /// Updates an in flight animation. + fileprivate func updateInFlightAnimation() { + guard let animationContext = animationContext else { return } + + guard animationContext.closure.animationState != .complete else { + // Tried to re-add an already completed animation. Cancel. + self.animationContext = nil + return + } + + /// Tell existing context to ignore its closure + animationContext.closure.ignoreDelegate = true + + /// Make a new context, stealing the completion block from the previous. + let newContext = AnimationContext( + playFrom: animationContext.playFrom, + playTo: animationContext.playTo, + closure: animationContext.closure.completionBlock) + + /// Remove current animation, and freeze the current frame. + let pauseFrame = realtimeAnimationFrame + animationLayer?.removeAnimation(forKey: activeAnimationName) + animationLayer?.currentFrame = pauseFrame + + addNewAnimationForContext(newContext) + } + + /// Adds animation to animation layer and sets the delegate. If animation layer or animation are nil, exits. + fileprivate func addNewAnimationForContext(_ animationContext: AnimationContext) { + guard let animationlayer = animationLayer, let animation = animation else { + return + } + + self.animationContext = animationContext + + switch currentRenderingEngine { + case .mainThread: + guard window != nil else { + waitingToPlayAnimation = true + return + } + + case .coreAnimation, nil: + // The Core Animation engine automatically batches animation setup to happen + // in `CALayer.display()`, which won't be called until the layer is on-screen, + // so we don't need to defer animation setup at this layer. + break + } + + animationID = animationID + 1 + _activeAnimationName = AnimationView.animationName + String(animationID) + + if let coreAnimationLayer = animationlayer as? CoreAnimationLayer { + var animationContext = animationContext + var timingConfiguration = CoreAnimationLayer.CAMediaTimingConfiguration( + autoreverses: loopMode.caAnimationConfiguration.autoreverses, + repeatCount: loopMode.caAnimationConfiguration.repeatCount, + speed: Float(animationSpeed)) + + // The animation should start playing from the `currentFrame`, + // if `currentFrame` is included in the time range being played. + let lowerBoundTime = min(animationContext.playFrom, animationContext.playTo) + let upperBoundTime = max(animationContext.playFrom, animationContext.playTo) + if (lowerBoundTime ..< upperBoundTime).contains(round(currentFrame)) { + // We have to configure this differently depending on the loop mode: + switch loopMode { + // When playing exactly once (and not looping), we can just set the + // `playFrom` time to be the `currentFrame`. Since the animation duration + // is based on `playFrom` and `playTo`, this automatically truncates the + // duration (so the animation stops playing at `playFrom`). + case .playOnce: + animationContext.playFrom = currentFrame + + // When looping, we specifically _don't_ want to affect the duration of the animation, + // since that would affect the duration of all subsequent loops. We just want to adjust + // the duration of the _first_ loop. Instead of setting `playFrom`, we just add a `timeOffset` + // so the first loop begins at `currentTime` but all subsequent loops are the standard duration. + default: + timingConfiguration.timeOffset = currentTime - animation.time(forFrame: animationContext.playFrom) + } + } + + coreAnimationLayer.playAnimation( + context: animationContext, + timingConfiguration: timingConfiguration) + + return + } + + /// At this point there is no animation on animationLayer and its state is set. + + let framerate = animation.framerate + + let playFrom = animationContext.playFrom.clamp(animation.startFrame, animation.endFrame) + let playTo = animationContext.playTo.clamp(animation.startFrame, animation.endFrame) + + let duration = ((max(playFrom, playTo) - min(playFrom, playTo)) / CGFloat(framerate)) + + let playingForward: Bool = + ( + (animationSpeed > 0 && playFrom < playTo) || + (animationSpeed < 0 && playTo < playFrom)) + + var startFrame = currentFrame.clamp(min(playFrom, playTo), max(playFrom, playTo)) + if startFrame == playTo { + startFrame = playFrom + } + + let timeOffset: TimeInterval = playingForward + ? Double(startFrame - min(playFrom, playTo)) / framerate + : Double(max(playFrom, playTo) - startFrame) / framerate + + let layerAnimation = CABasicAnimation(keyPath: "currentFrame") + layerAnimation.fromValue = playFrom + layerAnimation.toValue = playTo + layerAnimation.speed = Float(animationSpeed) + layerAnimation.duration = TimeInterval(duration) + layerAnimation.fillMode = CAMediaTimingFillMode.both + layerAnimation.repeatCount = loopMode.caAnimationConfiguration.repeatCount + layerAnimation.autoreverses = loopMode.caAnimationConfiguration.autoreverses + + layerAnimation.isRemovedOnCompletion = false + if timeOffset != 0 { + let currentLayerTime = viewLayer?.convertTime(CACurrentMediaTime(), from: nil) ?? 0 + layerAnimation.beginTime = currentLayerTime - (timeOffset * 1 / Double(abs(animationSpeed))) + } + layerAnimation.delegate = animationContext.closure + animationContext.closure.animationLayer = animationlayer + animationContext.closure.animationKey = activeAnimationName + + animationlayer.add(layerAnimation, forKey: activeAnimationName) + updateRasterizationState() + } + + // MARK: Private + + static private let animationName = "Lottie" + + /// The `LottieBackgroundBehavior` that was specified manually by setting `self.backgroundBehavior` + private var _backgroundBehavior: LottieBackgroundBehavior? + +} + +// MARK: - LottieLoopMode + caAnimationConfiguration + +extension LottieLoopMode { + /// The `CAAnimation` configuration that reflects this mode + var caAnimationConfiguration: (repeatCount: Float, autoreverses: Bool) { + switch self { + case .playOnce: + return (repeatCount: 1, autoreverses: false) + case .loop: + return (repeatCount: .greatestFiniteMagnitude, autoreverses: false) + case .autoReverse: + return (repeatCount: .greatestFiniteMagnitude, autoreverses: true) + case .repeat(let amount): + return (repeatCount: amount, autoreverses: false) + case .repeatBackwards(let amount): + return (repeatCount: amount, autoreverses: true) + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Animation/AnimationViewInitializers.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Animation/AnimationViewInitializers.swift new file mode 100644 index 00000000000..848d8e8bb35 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Animation/AnimationViewInitializers.swift @@ -0,0 +1,107 @@ +// +// AnimationViewInitializers.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/6/19. +// + +import Foundation + +extension AnimationView { + + // MARK: Lifecycle + + /// Loads a Lottie animation from a JSON file in the supplied bundle. + /// + /// - Parameter name: The string name of the lottie animation with no file + /// extension provided. + /// - Parameter bundle: The bundle in which the animation is located. + /// Defaults to the Main bundle. + /// - Parameter imageProvider: An image provider for the animation's image data. + /// If none is supplied Lottie will search in the supplied bundle for images. + public convenience init( + name: String, + bundle: Bundle = Bundle.main, + imageProvider: AnimationImageProvider? = nil, + animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) + { + let animation = Animation.named(name, bundle: bundle, subdirectory: nil, animationCache: animationCache) + let provider = imageProvider ?? BundleImageProvider(bundle: bundle, searchPath: nil) + self.init(animation: animation, imageProvider: provider) + } + + /// Loads a Lottie animation from a JSON file in a specific path on disk. + /// + /// - Parameter name: The absolute path of the Lottie Animation. + /// - Parameter imageProvider: An image provider for the animation's image data. + /// If none is supplied Lottie will search in the supplied filepath for images. + public convenience init( + filePath: String, + imageProvider: AnimationImageProvider? = nil, + animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) + { + let animation = Animation.filepath(filePath, animationCache: animationCache) + let provider = imageProvider ?? + FilepathImageProvider(filepath: URL(fileURLWithPath: filePath).deletingLastPathComponent().path) + self.init(animation: animation, imageProvider: provider) + } + + /// Loads a Lottie animation asynchronously from the URL + /// + /// - Parameter url: The url to load the animation from. + /// - Parameter imageProvider: An image provider for the animation's image data. + /// If none is supplied Lottie will search in the main bundle for images. + /// - Parameter closure: A closure to be called when the animation has loaded. + public convenience init( + url: URL, + imageProvider: AnimationImageProvider? = nil, + closure: @escaping AnimationView.DownloadClosure, + animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) + { + + if let animationCache = animationCache, let animation = animationCache.animation(forKey: url.absoluteString) { + self.init(animation: animation, imageProvider: imageProvider) + closure(nil) + } else { + + self.init(animation: nil, imageProvider: imageProvider) + + Animation.loadedFrom(url: url, closure: { animation in + if let animation = animation { + self.animation = animation + closure(nil) + } else { + closure(LottieDownloadError.downloadFailed) + } + }, animationCache: animationCache) + } + } + + /// Loads a Lottie animation from a JSON file located in the Asset catalog of the supplied bundle. + /// - Parameter name: The string name of the lottie animation in the asset catalog. + /// - Parameter bundle: The bundle in which the animation is located. + /// Defaults to the Main bundle. + /// - Parameter imageProvider: An image provider for the animation's image data. + /// If none is supplied Lottie will search in the supplied bundle for images. + public convenience init( + asset name: String, + bundle: Bundle = Bundle.main, + imageProvider: AnimationImageProvider? = nil, + animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) + { + let animation = Animation.asset(name, bundle: bundle, animationCache: animationCache) + let provider = imageProvider ?? BundleImageProvider(bundle: bundle, searchPath: nil) + self.init(animation: animation, imageProvider: provider) + } + + // MARK: Public + + public typealias DownloadClosure = (Error?) -> Void + +} + +// MARK: - LottieDownloadError + +enum LottieDownloadError: Error { + case downloadFailed +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/AnimationCache/AnimationCacheProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/AnimationCache/AnimationCacheProvider.swift new file mode 100644 index 00000000000..bdf189dec47 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/AnimationCache/AnimationCacheProvider.swift @@ -0,0 +1,22 @@ +// +// AnimationCacheProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/5/19. +// + +import Foundation +/// `AnimationCacheProvider` is a protocol that describes an Animation Cache. +/// Animation Cache is used when loading `Animation` models. Using an Animation Cache +/// can increase performance when loading an animation multiple times. +/// +/// Lottie comes with a prebuilt LRU Animation Cache. +public protocol AnimationCacheProvider { + + func animation(forKey: String) -> Animation? + + func setAnimation(_ animation: Animation, forKey: String) + + func clearCache() + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/AnimationCache/LRUAnimationCache.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/AnimationCache/LRUAnimationCache.swift new file mode 100644 index 00000000000..d48ce8ff5dd --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/AnimationCache/LRUAnimationCache.swift @@ -0,0 +1,61 @@ +// +// LRUAnimationCache.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/5/19. +// + +import Foundation + +/// An Animation Cache that will store animations up to `cacheSize`. +/// +/// Once `cacheSize` is reached, the least recently used animation will be ejected. +/// The default size of the cache is 100. +public class LRUAnimationCache: AnimationCacheProvider { + + // MARK: Lifecycle + + public init() { } + + // MARK: Public + + /// The global shared Cache. + public static let sharedCache = LRUAnimationCache() + + /// The size of the cache. + public var cacheSize = 100 + + /// Clears the Cache. + public func clearCache() { + cacheMap.removeAll() + lruList.removeAll() + } + + public func animation(forKey: String) -> Animation? { + guard let animation = cacheMap[forKey] else { + return nil + } + if let index = lruList.firstIndex(of: forKey) { + lruList.remove(at: index) + lruList.append(forKey) + } + return animation + } + + public func setAnimation(_ animation: Animation, forKey: String) { + cacheMap[forKey] = animation + lruList.append(forKey) + if lruList.count > cacheSize { + let removed = lruList.remove(at: 0) + if removed != forKey { + cacheMap[removed] = nil + } + } + } + + // MARK: Fileprivate + + fileprivate var cacheMap: [String: Animation] = [:] + fileprivate var lruList: [String] = [] + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnimationKeypath.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnimationKeypath.cpp new file mode 100644 index 00000000000..8f90bfc95e5 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnimationKeypath.cpp @@ -0,0 +1,5 @@ +#include "AnimationKeypath.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnimationKeypath.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnimationKeypath.hpp new file mode 100644 index 00000000000..8679106d129 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnimationKeypath.hpp @@ -0,0 +1,51 @@ +#ifndef AnimationKeypath_hpp +#define AnimationKeypath_hpp + +#include +#include + +namespace lottie { + +/// `AnimationKeypath` is an object that describes a keypath search for nodes in the +/// animation JSON. `AnimationKeypath` matches views and properties inside of `AnimationView` +/// to their backing `Animation` model by name. +/// +/// A keypath can be used to set properties on an existing animation, or can be validated +/// with an existing `Animation`. +/// +/// `AnimationKeypath` can describe a specific object, or can use wildcards for fuzzy matching +/// of objects. Acceptable wildcards are either "*" (star) or "**" (double star). +/// Single star will search a single depth for the next object. +/// Double star will search any depth. +/// +/// Read More at https://airbnb.io/lottie/#/ios?id=dynamic-animation-properties +/// +/// EG: +/// @"Layer.Shape Group.Stroke 1.Color" +/// Represents a specific color node on a specific stroke. +/// +/// @"**.Stroke 1.Color" +/// Represents the color node for every Stroke named "Stroke 1" in the animation. +class AnimationKeypath { +public: + /// Creates a keypath from a dot-separated string. The string is separated by "." + /*public init(keypath: String) { + keys = keypath.components(separatedBy: ".") + }*/ + + /// Creates a keypath from a list of strings. + AnimationKeypath(std::vector const &keys) : + _keys(keys) { + } + + std::vector const &keys() const { + return _keys; + } + +private: + std::vector _keys; +}; + +} + +#endif /* AnimationKeypath_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnimationKeypath.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnimationKeypath.swift new file mode 100644 index 00000000000..f1b6748331e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnimationKeypath.swift @@ -0,0 +1,49 @@ +// +// AnimationKeypath.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation + +/// `AnimationKeypath` is an object that describes a keypath search for nodes in the +/// animation JSON. `AnimationKeypath` matches views and properties inside of `AnimationView` +/// to their backing `Animation` model by name. +/// +/// A keypath can be used to set properties on an existing animation, or can be validated +/// with an existing `Animation`. +/// +/// `AnimationKeypath` can describe a specific object, or can use wildcards for fuzzy matching +/// of objects. Acceptable wildcards are either "*" (star) or "**" (double star). +/// Single star will search a single depth for the next object. +/// Double star will search any depth. +/// +/// Read More at https://airbnb.io/lottie/#/ios?id=dynamic-animation-properties +/// +/// EG: +/// @"Layer.Shape Group.Stroke 1.Color" +/// Represents a specific color node on a specific stroke. +/// +/// @"**.Stroke 1.Color" +/// Represents the color node for every Stroke named "Stroke 1" in the animation. +public struct AnimationKeypath: Hashable, ExpressibleByStringLiteral { + + /// Creates a keypath from a dot-separated string. The string is separated by "." + public init(keypath: String) { + keys = keypath.components(separatedBy: ".") + } + + /// Creates a keypath from a dot-separated string + public init(stringLiteral: String) { + self.init(keypath: stringLiteral) + } + + /// Creates a keypath from a list of strings. + public init(keys: [String]) { + self.keys = keys + } + + var keys: [String] + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnyValueProvider.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnyValueProvider.cpp new file mode 100644 index 00000000000..f56124c957b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnyValueProvider.cpp @@ -0,0 +1,5 @@ +#include "AnyValueProvider.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnyValueProvider.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnyValueProvider.hpp new file mode 100644 index 00000000000..a9b708afbdc --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnyValueProvider.hpp @@ -0,0 +1,38 @@ +#ifndef AnyValueProvider_hpp +#define AnyValueProvider_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Public/Primitives/AnyValue.hpp" + +#include +#include + +namespace lottie { + +/// `AnyValueProvider` is a protocol that return animation data for a property at a +/// given time. Every frame an `AnimationView` queries all of its properties and asks +/// if their ValueProvider has an update. If it does the AnimationView will read the +/// property and update that portion of the animation. +/// +/// Value Providers can be used to dynamically set animation properties at run time. +class AnyValueProvider { +public: + /// The Type of the value provider + virtual AnyValue::Type valueType() const = 0; + + /// Asks the provider if it has an update for the given frame. + virtual bool hasUpdate(AnimationFrameTime frame) const = 0; +}; + +/// A base protocol for strongly-typed Value Providers +template +class ValueProvider: public AnyValueProvider { +public: + /// Asks the provider to update the container with its value for the frame. + virtual T value(AnimationFrameTime frame) = 0; +}; + +} + +#endif /* AnyValueProvider_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnyValueProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnyValueProvider.swift new file mode 100644 index 00000000000..deded9368eb --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/AnyValueProvider.swift @@ -0,0 +1,132 @@ +// +// AnyValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +// MARK: - AnyValueProvider + +/// `AnyValueProvider` is a protocol that return animation data for a property at a +/// given time. Every frame an `AnimationView` queries all of its properties and asks +/// if their ValueProvider has an update. If it does the AnimationView will read the +/// property and update that portion of the animation. +/// +/// Value Providers can be used to dynamically set animation properties at run time. +public protocol AnyValueProvider { + + /// The Type of the value provider + var valueType: Any.Type { get } + + /// The type-erased storage for this Value Provider + var typeErasedStorage: AnyValueProviderStorage { get } + + /// Asks the provider if it has an update for the given frame. + func hasUpdate(frame: AnimationFrameTime) -> Bool + +} + +extension AnyValueProvider { + /// Asks the provider to update the container with its value for the frame. + public func value(frame: AnimationFrameTime) -> Any { + typeErasedStorage.value(frame: frame) + } +} + +// MARK: - ValueProvider + +/// A base protocol for strongly-typed Value Providers +protocol ValueProvider: AnyValueProvider { + associatedtype Value: AnyInterpolatable + + /// The strongly-typed storage for this Value Provider + var storage: ValueProviderStorage { get } +} + +extension ValueProvider { + public var typeErasedStorage: AnyValueProviderStorage { + switch storage { + case .closure(let typedClosure): + return .closure(typedClosure) + + case .singleValue(let typedValue): + return .singleValue(typedValue) + + case .keyframes(let keyframes): + return .keyframes( + keyframes.map { keyframe in + keyframe.withValue(keyframe.value as Any) + }, + interpolate: storage.value(frame:)) + } + } +} + +// MARK: - ValueProviderStorage + +/// The underlying storage of a `ValueProvider` +public enum ValueProviderStorage { + /// The value provider stores a single value that is used on all frames + case singleValue(T) + + /// The value provider stores a group of keyframes + /// - The main-thread rendering engine interpolates values in these keyframes + /// using `T`'s `Interpolatable` implementation. + /// - The Core Animation rendering engine constructs a `CAKeyframeAnimation` + /// using these keyframes. The Core Animation render server performs + /// the interpolation, without calling `T`'s `Interpolatable` implementation. + case keyframes([Keyframe]) + + /// The value provider stores a closure that is invoked on every frame + /// - This is only supported by the main-thread rendering engine + case closure((AnimationFrameTime) -> T) + + // MARK: Internal + + func value(frame: AnimationFrameTime) -> T { + switch self { + case .singleValue(let value): + return value + + case .closure(let closure): + return closure(frame) + + case .keyframes(let keyframes): + return KeyframeInterpolator(keyframes: ContiguousArray(keyframes)).storage.value(frame: frame) + } + } +} + +// MARK: - AnyValueProviderStorage + +/// A type-erased representation of `ValueProviderStorage` +public enum AnyValueProviderStorage { + /// The value provider stores a single value that is used on all frames + case singleValue(Any) + + /// The value provider stores a group of keyframes + /// - Since we can't interpolate a type-erased `KeyframeGroup`, + /// the interpolation has to be performed in the `interpolate` closure. + case keyframes([Keyframe], interpolate: (AnimationFrameTime) -> Any) + + /// The value provider stores a closure that is invoked on every frame + case closure((AnimationFrameTime) -> Any) + + // MARK: Internal + + func value(frame: AnimationFrameTime) -> Any { + switch self { + case .singleValue(let value): + return value + + case .closure(let closure): + return closure(frame) + + case .keyframes(_, let valueForFrame): + return valueForFrame(frame) + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift new file mode 100644 index 00000000000..a991b1966aa --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift @@ -0,0 +1,84 @@ +// +// ColorValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import CoreGraphics +import Foundation + +/// A `ValueProvider` that returns a CGColor Value +public final class ColorValueProvider: ValueProvider { + + // MARK: Lifecycle + + /// Initializes with a block provider + public init(block: @escaping ColorValueBlock) { + self.block = block + color = Color(r: 0, g: 0, b: 0, a: 1) + keyframes = nil + } + + /// Initializes with a single color. + public init(_ color: Color) { + self.color = color + block = nil + keyframes = nil + hasUpdate = true + } + + /// Initializes with multiple colors, with timing information + public init(_ keyframes: [Keyframe]) { + self.keyframes = keyframes + color = Color(r: 0, g: 0, b: 0, a: 1) + block = nil + hasUpdate = true + } + + // MARK: Public + + /// Returns a Color for a CGColor(Frame Time) + public typealias ColorValueBlock = (CGFloat) -> Color + + /// The color value of the provider. + public var color: Color { + didSet { + hasUpdate = true + } + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + Color.self + } + + public var storage: ValueProviderStorage { + if let block = block { + return .closure { frame in + self.hasUpdate = false + return block(frame) + } + } else if let keyframes = keyframes { + return .keyframes(keyframes) + } else { + hasUpdate = false + return .singleValue(color) + } + } + + public func hasUpdate(frame _: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + // MARK: Private + + private var hasUpdate = true + + private var block: ColorValueBlock? + private var keyframes: [Keyframe]? +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift new file mode 100644 index 00000000000..0e60979d044 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift @@ -0,0 +1,70 @@ +// +// DoubleValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import CoreGraphics +import Foundation + +/// A `ValueProvider` that returns a CGFloat Value +public final class FloatValueProvider: ValueProvider { + + // MARK: Lifecycle + + /// Initializes with a block provider + public init(block: @escaping CGFloatValueBlock) { + self.block = block + float = 0 + } + + /// Initializes with a single float. + public init(_ float: CGFloat) { + self.float = float + block = nil + hasUpdate = true + } + + // MARK: Public + + /// Returns a CGFloat for a CGFloat(Frame Time) + public typealias CGFloatValueBlock = (CGFloat) -> CGFloat + + public var float: CGFloat { + didSet { + hasUpdate = true + } + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + Vector1D.self + } + + public var storage: ValueProviderStorage { + if let block = block { + return .closure { frame in + self.hasUpdate = false + return Vector1D(Double(block(frame))) + } + } else { + hasUpdate = false + return .singleValue(Vector1D(Double(float))) + } + } + + public func hasUpdate(frame _: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + // MARK: Private + + private var hasUpdate = true + + private var block: CGFloatValueBlock? +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift new file mode 100644 index 00000000000..22b4886037e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift @@ -0,0 +1,125 @@ +// +// GradientValueProvider.swift +// lottie-swift +// +// Created by Enrique Bermúdez on 10/27/19. +// + +import CoreGraphics +import Foundation + +/// A `ValueProvider` that returns a Gradient Color Value. +public final class GradientValueProvider: ValueProvider { + + // MARK: Lifecycle + + /// Initializes with a block provider. + public init( + block: @escaping ColorsValueBlock, + locations: ColorLocationsBlock? = nil) + { + self.block = block + locationsBlock = locations + colors = [] + self.locations = [] + } + + /// Initializes with an array of colors. + public init( + _ colors: [Color], + locations: [Double] = []) + { + self.colors = colors + self.locations = locations + updateValueArray() + hasUpdate = true + } + + // MARK: Public + + /// Returns a [Color] for a CGFloat(Frame Time). + public typealias ColorsValueBlock = (CGFloat) -> [Color] + /// Returns a [Double](Color locations) for a CGFloat(Frame Time). + public typealias ColorLocationsBlock = (CGFloat) -> [Double] + + /// The colors values of the provider. + public var colors: [Color] { + didSet { + updateValueArray() + hasUpdate = true + } + } + + /// The color location values of the provider. + public var locations: [Double] { + didSet { + updateValueArray() + hasUpdate = true + } + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + [Double].self + } + + public var storage: ValueProviderStorage<[Double]> { + .closure { [self] frame in + hasUpdate = false + + if let block = block { + let newColors = block(frame) + let newLocations = locationsBlock?(frame) ?? [] + value = value(from: newColors, locations: newLocations) + } + + return value + } + } + + public func hasUpdate(frame _: CGFloat) -> Bool { + if block != nil || locationsBlock != nil { + return true + } + return hasUpdate + } + + // MARK: Private + + private var hasUpdate = true + + private var block: ColorsValueBlock? + private var locationsBlock: ColorLocationsBlock? + private var value: [Double] = [] + + private func value(from colors: [Color], locations: [Double]) -> [Double] { + + var colorValues = [Double]() + var alphaValues = [Double]() + var shouldAddAlphaValues = false + + for i in 0.. i + ? locations[i] + : (Double(i) / Double(colors.count - 1)) + + colorValues.append(location) + colorValues.append(colors[i].r) + colorValues.append(colors[i].g) + colorValues.append(colors[i].b) + + alphaValues.append(location) + alphaValues.append(colors[i].a) + } + + return colorValues + (shouldAddAlphaValues ? alphaValues : []) + } + + private func updateValueArray() { + value = value(from: colors, locations: locations) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift new file mode 100644 index 00000000000..7e2d165cff5 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift @@ -0,0 +1,69 @@ +// +// PointValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import CoreGraphics +import Foundation +/// A `ValueProvider` that returns a CGPoint Value +public final class PointValueProvider: ValueProvider { + + // MARK: Lifecycle + + /// Initializes with a block provider + public init(block: @escaping PointValueBlock) { + self.block = block + point = .zero + } + + /// Initializes with a single point. + public init(_ point: CGPoint) { + self.point = point + block = nil + hasUpdate = true + } + + // MARK: Public + + /// Returns a CGPoint for a CGFloat(Frame Time) + public typealias PointValueBlock = (CGFloat) -> CGPoint + + public var point: CGPoint { + didSet { + hasUpdate = true + } + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + Vector3D.self + } + + public var storage: ValueProviderStorage { + if let block = block { + return .closure { frame in + self.hasUpdate = false + return block(frame).vector3dValue + } + } else { + hasUpdate = false + return .singleValue(point.vector3dValue) + } + } + + public func hasUpdate(frame _: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + // MARK: Private + + private var hasUpdate = true + + private var block: PointValueBlock? +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift new file mode 100644 index 00000000000..e1829403b71 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift @@ -0,0 +1,70 @@ +// +// SizeValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import CoreGraphics +import Foundation + +/// A `ValueProvider` that returns a CGSize Value +public final class SizeValueProvider: ValueProvider { + + // MARK: Lifecycle + + /// Initializes with a block provider + public init(block: @escaping SizeValueBlock) { + self.block = block + size = .zero + } + + /// Initializes with a single size. + public init(_ size: CGSize) { + self.size = size + block = nil + hasUpdate = true + } + + // MARK: Public + + /// Returns a CGSize for a CGFloat(Frame Time) + public typealias SizeValueBlock = (CGFloat) -> CGSize + + public var size: CGSize { + didSet { + hasUpdate = true + } + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + Vector3D.self + } + + public var storage: ValueProviderStorage { + if let block = block { + return .closure { frame in + self.hasUpdate = false + return block(frame).vector3dValue + } + } else { + hasUpdate = false + return .singleValue(size.vector3dValue) + } + } + + public func hasUpdate(frame _: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + // MARK: Private + + private var hasUpdate = true + + private var block: SizeValueBlock? +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/FontProvider/AnimationFontProvider.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/FontProvider/AnimationFontProvider.cpp new file mode 100644 index 00000000000..a6a4774df78 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/FontProvider/AnimationFontProvider.cpp @@ -0,0 +1,5 @@ +#include "AnimationFontProvider.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/FontProvider/AnimationFontProvider.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/FontProvider/AnimationFontProvider.hpp new file mode 100644 index 00000000000..2f11b5639a5 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/FontProvider/AnimationFontProvider.hpp @@ -0,0 +1,31 @@ +#ifndef AnimationFontProvider_hpp +#define AnimationFontProvider_hpp + +#include "Lottie/Public/Primitives/CTFont.hpp" + +#include + +namespace lottie { + +/// Font provider is a protocol that is used to supply fonts to `AnimationView`. +/// +class AnimationFontProvider { +public: + virtual std::shared_ptr fontFor(std::string const &family, double size) = 0; +}; + +/// Default Font provider. +class DefaultFontProvider: public AnimationFontProvider { +public: + DefaultFontProvider() { + } + + virtual std::shared_ptr fontFor(std::string const &family, double size) override { + //CTFontCreateWithName(family as CFString, size, nil) + return nullptr; + } +}; + +} + +#endif /* AnimationFontProvider_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/FontProvider/AnimationFontProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/FontProvider/AnimationFontProvider.swift new file mode 100644 index 00000000000..a908c73f71f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/FontProvider/AnimationFontProvider.swift @@ -0,0 +1,35 @@ +// +// AnimationFontProvider.swift +// Lottie +// +// Created by Brandon Withrow on 8/5/20. +// Copyright © 2020 YurtvilleProds. All rights reserved. +// + +import CoreGraphics +import CoreText +import Foundation + +// MARK: - AnimationFontProvider + +/// Font provider is a protocol that is used to supply fonts to `AnimationView`. +/// +public protocol AnimationFontProvider { + func fontFor(family: String, size: CGFloat) -> CTFont? +} + +// MARK: - DefaultFontProvider + +/// Default Font provider. +public final class DefaultFontProvider: AnimationFontProvider { + + // MARK: Lifecycle + + public init() {} + + // MARK: Public + + public func fontFor(family: String, size: CGFloat) -> CTFont? { + CTFontCreateWithName(family as CFString, size, nil) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/ImageProvider/AnimationImageProvider.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/ImageProvider/AnimationImageProvider.hpp new file mode 100644 index 00000000000..3c55ffd9449 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/ImageProvider/AnimationImageProvider.hpp @@ -0,0 +1,16 @@ +#ifndef AnimationImageProvider_hpp +#define AnimationImageProvider_hpp + +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Private/Model/Assets/ImageAsset.hpp" + +namespace lottie { + +class AnimationImageProvider { +public: + virtual std::shared_ptr imageForAsset(ImageAsset const &imageAsset) = 0; +}; + +} + +#endif /* AnimationImageProvider_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/ImageProvider/AnimationImageProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/ImageProvider/AnimationImageProvider.swift new file mode 100644 index 00000000000..c565921e0e4 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/ImageProvider/AnimationImageProvider.swift @@ -0,0 +1,21 @@ +// +// LottieImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import CoreGraphics +import Foundation + +/// Image provider is a protocol that is used to supply images to `AnimationView`. +/// +/// Some animations require a reference to an image. The image provider loads and +/// provides those images to the `AnimationView`. Lottie includes a couple of +/// prebuilt Image Providers that supply images from a Bundle, or from a FilePath. +/// +/// Additionally custom Image Providers can be made to load images from a URL, +/// or to Cache images. +public protocol AnimationImageProvider { + func imageForAsset(asset: ImageAsset) -> CGImage? +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Interpolatable.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Interpolatable.cpp new file mode 100644 index 00000000000..8165d87d710 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Interpolatable.cpp @@ -0,0 +1,15 @@ +#include "Interpolatable.hpp" + +namespace lottie { + +double remapDouble(double value, double fromLow, double fromHigh, double toLow, double toHigh) { + return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow); +} + +double clampDouble(double value, double a, double b) { + double minValue = a <= b ? a : b; + double maxValue = a <= b ? b : a; + return std::max(std::min(value, maxValue), minValue); +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Interpolatable.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Interpolatable.hpp new file mode 100644 index 00000000000..2bfabdca076 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Interpolatable.hpp @@ -0,0 +1,14 @@ +#ifndef Interpolatable_hpp +#define Interpolatable_hpp + +#include + +namespace lottie { + +double remapDouble(double value, double fromLow, double fromHigh, double toLow, double toHigh); + +double clampDouble(double value, double a, double b); + +} + +#endif /* Interpolatable_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Interpolatable.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Interpolatable.swift new file mode 100644 index 00000000000..2a7ab5b08ee --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Interpolatable.swift @@ -0,0 +1,253 @@ +// Created by Cal Stephens on 1/24/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import CoreGraphics + +// MARK: - Interpolatable + +/// A type that can be interpolated between two values +public protocol Interpolatable: AnyInterpolatable { + /// Interpolates the `self` to the given number by `amount`. + /// - Parameter to: The number to interpolate to. + /// - Parameter amount: The amount to interpolate, + /// relative to 0.0 (self) and 1.0 (to). + /// `amount` can be greater than one and less than zero, + /// and interpolation should not be clamped. + /// + /// ``` + /// let number = 5 + /// let interpolated = number.interpolateTo(10, amount: 0.5) + /// print(interpolated) // 7.5 + /// ``` + /// + /// ``` + /// let number = 5 + /// let interpolated = number.interpolateTo(10, amount: 1.5) + /// print(interpolated) // 12.5 + /// ``` + func interpolate(to: Self, amount: CGFloat) -> Self +} + +// MARK: - SpatialInterpolatable + +/// A type that can be interpolated between two values, +/// additionally using optional `spatialOutTangent` and `spatialInTangent` values. +/// - If your implementation doesn't use the `spatialOutTangent` and `spatialInTangent` +/// parameters, prefer implementing the simpler `Interpolatable` protocol. +public protocol SpatialInterpolatable: AnyInterpolatable { + /// Interpolates the `self` to the given number by `amount`. + /// - Parameter to: The number to interpolate to. + /// - Parameter amount: The amount to interpolate, + /// relative to 0.0 (self) and 1.0 (to). + /// `amount` can be greater than one and less than zero, + /// and interpolation should not be clamped. + func interpolate( + to: Self, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> Self +} + +// MARK: - AnyInterpolatable + +/// The base protocol that is implemented by both `Interpolatable` and `SpatialInterpolatable` +/// Types should not directly implement this protocol. +public protocol AnyInterpolatable { + /// Interpolates by calling either `Interpolatable.interpolate` + /// or `SpatialInterpolatable.interpolate`. + /// Should not be implemented or called by consumers. + func _interpolate( + to: Self, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> Self +} + +extension Interpolatable { + public func _interpolate( + to: Self, + amount: CGFloat, + spatialOutTangent _: CGPoint?, + spatialInTangent _: CGPoint?) + -> Self + { + interpolate(to: to, amount: amount) + } +} + +extension SpatialInterpolatable { + /// Helper that interpolates this `SpatialInterpolatable` + /// with `nil` spatial in/out tangents + public func interpolate(to: Self, amount: CGFloat) -> Self { + interpolate( + to: to, + amount: amount, + spatialOutTangent: nil, + spatialInTangent: nil) + } + + public func _interpolate( + to: Self, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> Self + { + interpolate( + to: to, + amount: amount, + spatialOutTangent: spatialOutTangent, + spatialInTangent: spatialInTangent) + } +} + +// MARK: - Double + Interpolatable + +extension Double: Interpolatable { } + +// MARK: - CGFloat + Interpolatable + +extension CGFloat: Interpolatable { } + +// MARK: - Float + Interpolatable + +extension Float: Interpolatable { } + +extension Interpolatable where Self: BinaryFloatingPoint { + public func interpolate(to: Self, amount: CGFloat) -> Self { + self + ((to - self) * Self(amount)) + } +} + +// MARK: - CGRect + Interpolatable + +extension CGRect: Interpolatable { + public func interpolate(to: CGRect, amount: CGFloat) -> CGRect { + CGRect( + x: origin.x.interpolate(to: to.origin.x, amount: amount), + y: origin.y.interpolate(to: to.origin.y, amount: amount), + width: width.interpolate(to: to.width, amount: amount), + height: height.interpolate(to: to.height, amount: amount)) + } +} + +// MARK: - CGSize + Interpolatable + +extension CGSize: Interpolatable { + public func interpolate(to: CGSize, amount: CGFloat) -> CGSize { + CGSize( + width: width.interpolate(to: to.width, amount: amount), + height: height.interpolate(to: to.height, amount: amount)) + } +} + +// MARK: - CGPoint + SpatialInterpolatable + +extension CGPoint: SpatialInterpolatable { + public func interpolate( + to: CGPoint, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> CGPoint + { + guard + let outTan = spatialOutTangent, + let inTan = spatialInTangent + else { + return CGPoint( + x: x.interpolate(to: to.x, amount: amount), + y: y.interpolate(to: to.y, amount: amount)) + } + + let cp1 = self + outTan + let cp2 = to + inTan + return interpolate(to, outTangent: cp1, inTangent: cp2, amount: amount) + } +} + +// MARK: - Color + Interpolatable + +extension Color: Interpolatable { + public func interpolate(to: Color, amount: CGFloat) -> Color { + Color( + r: r.interpolate(to: to.r, amount: amount), + g: g.interpolate(to: to.g, amount: amount), + b: b.interpolate(to: to.b, amount: amount), + a: a.interpolate(to: to.a, amount: amount)) + } +} + +// MARK: - Vector1D + Interpolatable + +extension Vector1D: Interpolatable { + public func interpolate(to: Vector1D, amount: CGFloat) -> Vector1D { + value.interpolate(to: to.value, amount: amount).vectorValue + } +} + +// MARK: - Vector2D + SpatialInterpolatable + +extension Vector2D: SpatialInterpolatable { + public func interpolate( + to: Vector2D, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> Vector2D + { + pointValue.interpolate( + to: to.pointValue, + amount: amount, + spatialOutTangent: spatialOutTangent, + spatialInTangent: spatialInTangent) + .vector2dValue + } +} + +// MARK: - Vector3D + SpatialInterpolatable + +extension Vector3D: SpatialInterpolatable { + public func interpolate( + to: Vector3D, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> Vector3D + { + if spatialInTangent != nil || spatialOutTangent != nil { + // TODO Support third dimension spatial interpolation + let point = pointValue.interpolate( + to: to.pointValue, + amount: amount, + spatialOutTangent: spatialOutTangent, + spatialInTangent: spatialInTangent) + + return Vector3D( + x: point.x, + y: point.y, + z: CGFloat(z.interpolate(to: to.z, amount: amount))) + } + + return Vector3D( + x: x.interpolate(to: to.x, amount: amount), + y: y.interpolate(to: to.y, amount: amount), + z: z.interpolate(to: to.z, amount: amount)) + } +} + +// MARK: - Array + Interpolatable, AnyInterpolatable + +extension Array: Interpolatable, AnyInterpolatable where Element: Interpolatable { + public func interpolate(to: [Element], amount: CGFloat) -> [Element] { + LottieLogger.shared.assert( + count == to.count, + "When interpolating Arrays, both array sound have the same element count.") + + return zip(self, to).map { lhs, rhs in + lhs.interpolate(to: rhs, amount: amount) + } + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Keyframe.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Keyframe.cpp new file mode 100644 index 00000000000..e699e6daf81 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Keyframe.cpp @@ -0,0 +1,5 @@ +#include "Keyframe.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Keyframe.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Keyframe.hpp new file mode 100644 index 00000000000..f219032d0c7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Keyframe.hpp @@ -0,0 +1,256 @@ +#ifndef Keyframe_hpp +#define Keyframe_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Keyframes/Interpolatable.hpp" +#include "Lottie/Public/Keyframes/ValueInterpolators.hpp" + +#include + +namespace lottie { + +/// A keyframe with a single value, and timing information +/// about when the value should be displayed and how it +/// should be interpolated. +template +class Keyframe { +public: + /// Initialize a value-only keyframe with no time data. + Keyframe( + T const &value_, + std::optional spatialInTangent_, + std::optional spatialOutTangent_ + ) : + value(value_), + spatialInTangent(spatialInTangent_), + spatialOutTangent(spatialOutTangent_), + time(0), + isHold(true), + inTangent(std::nullopt), + outTangent(std::nullopt) { + } + + /// Initialize a keyframe + Keyframe( + T value_, + AnimationFrameTime time_, + bool isHold_, + std::optional inTangent_, + std::optional outTangent_, + std::optional spatialInTangent_, + std::optional spatialOutTangent_ + ) : + value(value_), + time(time_), + isHold(isHold_), + inTangent(inTangent_), + outTangent(outTangent_), + spatialInTangent(spatialInTangent_), + spatialOutTangent(spatialOutTangent_) { + } + + bool operator==(Keyframe const &rhs) { + return value == rhs.value + && time == rhs.time + && isHold == rhs.isHold + && inTangent == rhs.inTangent + && outTangent == rhs.outTangent + && spatialInTangent == rhs.spatialInTangent + && spatialOutTangent == rhs.spatialOutTangent; + } + + bool operator!=(Keyframe const &rhs) { + return !(*this == rhs); + } + +public: + T interpolate(Keyframe const &to, double progress) { + std::optional spatialOutTangent2d; + if (spatialOutTangent) { + spatialOutTangent2d = Vector2D(spatialOutTangent->x, spatialOutTangent->y); + } + std::optional spatialInTangent2d; + if (to.spatialInTangent) { + spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y); + } + return ValueInterpolator::interpolate(value, to.value, progress, spatialOutTangent2d, spatialInTangent2d); + } + + /// Interpolates the keyTime into a value from 0-1 + double interpolatedProgress(Keyframe const &to, double keyTime) { + double startTime = time; + double endTime = to.time; + if (keyTime <= startTime) { + return 0.0; + } + if (endTime <= keyTime) { + return 1.0; + } + + if (isHold) { + return 0.0; + } + + Vector2D outTanPoint = Vector2D::Zero(); + if (outTangent.has_value()) { + outTanPoint = outTangent.value(); + } + Vector2D inTanPoint = Vector2D(1.0, 1.0); + if (to.inTangent.has_value()) { + inTanPoint = to.inTangent.value(); + } + double progress = remapDouble(keyTime, startTime, endTime, 0.0, 1.0); + if (!outTanPoint.isZero() || inTanPoint != Vector2D(1.0, 1.0)) { + /// Cubic interpolation + progress = cubicBezierInterpolate(progress, Vector2D::Zero(), outTanPoint, inTanPoint, Vector2D(1.0, 1.0)); + } + return progress; + } + +public: + /// The value of the keyframe + T value; + /// The time in frames of the keyframe. + AnimationFrameTime time; + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + bool isHold; + /// The in tangent for the time interpolation curve. + std::optional inTangent; + /// The out tangent for the time interpolation curve. + std::optional outTangent; + + /// The spatial in tangent of the vector. + std::optional spatialInTangent; + /// The spatial out tangent of the vector. + std::optional spatialOutTangent; +}; + +template +class KeyframeData { +public: + KeyframeData( + std::optional startValue_, + std::optional endValue_, + std::optional time_, + std::optional hold_, + std::optional inTangent_, + std::optional outTangent_, + std::optional spatialInTangent_, + std::optional spatialOutTangent_ + ) : + startValue(startValue_), + endValue(endValue_), + time(time_), + hold(hold_), + inTangent(inTangent_), + outTangent(outTangent_), + spatialInTangent(spatialInTangent_), + spatialOutTangent(spatialOutTangent_) { + } + + explicit KeyframeData(json11::Json const &json) noexcept(false) { + if (!json.is_object()) { + throw LottieParsingException(); + } + + if (const auto startValueData = getOptionalAny(json.object_items(), "s")) { + startValue = T(startValueData.value()); + } + + if (const auto endValueData = getOptionalAny(json.object_items(), "e")) { + endValue = T(endValueData.value()); + } + + time = getOptionalDouble(json.object_items(), "t"); + hold = getOptionalInt(json.object_items(), "h"); + + if (const auto inTangentData = getOptionalObject(json.object_items(), "i")) { + inTangent = Vector2D(inTangentData.value()); + } + + if (const auto outTangentData = getOptionalObject(json.object_items(), "o")) { + outTangent = Vector2D(outTangentData.value()); + } + + if (const auto spatialInTangentData = getOptionalAny(json.object_items(), "ti")) { + spatialInTangent = Vector3D(spatialInTangentData.value()); + } + + if (const auto spatialOutTangentData = getOptionalAny(json.object_items(), "to")) { + spatialOutTangent = Vector3D(spatialOutTangentData.value()); + } + + if (const auto nDataValue = getOptionalAny(json.object_items(), "n")) { + nData = nDataValue.value(); + } + } + + json11::Json::object toJson() const { + json11::Json::object result; + + if (startValue.has_value()) { + result.insert(std::make_pair("s", startValue->toJson())); + } + if (endValue.has_value()) { + result.insert(std::make_pair("e", endValue->toJson())); + } + if (time.has_value()) { + result.insert(std::make_pair("t", time.value())); + } + if (hold.has_value()) { + result.insert(std::make_pair("h", hold.value())); + } + if (inTangent.has_value()) { + result.insert(std::make_pair("i", inTangent->toJson())); + } + if (outTangent.has_value()) { + result.insert(std::make_pair("o", outTangent->toJson())); + } + if (spatialInTangent.has_value()) { + result.insert(std::make_pair("ti", spatialInTangent->toJson())); + } + if (spatialOutTangent.has_value()) { + result.insert(std::make_pair("to", spatialOutTangent->toJson())); + } + if (nData.has_value()) { + result.insert(std::make_pair("n", nData.value())); + } + + return result; + } + +public: + /// The start value of the keyframe + std::optional startValue; + /// The End value of the keyframe. Note: Newer versions animation json do not have this field. + std::optional endValue; + /// The time in frames of the keyframe. + std::optional time; + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + std::optional hold; + + /// The in tangent for the time interpolation curve. + std::optional inTangent; + /// The out tangent for the time interpolation curve. + std::optional outTangent; + + /// The spacial in tangent of the vector. + std::optional spatialInTangent; + /// The spacial out tangent of the vector. + std::optional spatialOutTangent; + + std::optional nData; + + bool isHold() const { + if (hold.has_value()) { + return hold.value() > 0; + } else { + return false; + } + } +}; + +} + +#endif /* Keyframe_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Keyframe.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Keyframe.swift new file mode 100644 index 00000000000..2e3befc53c8 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/Keyframe.swift @@ -0,0 +1,92 @@ +// Created by Cal Stephens on 1/24/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +// MARK: - Keyframe + +/// A keyframe with a single value, and timing information +/// about when the value should be displayed and how it +/// should be interpolated. +public final class Keyframe { + + // MARK: Lifecycle + + /// Initialize a value-only keyframe with no time data. + public init( + _ value: T, + spatialInTangent: Vector3D? = nil, + spatialOutTangent: Vector3D? = nil) + { + self.value = value + time = 0 + isHold = true + inTangent = nil + outTangent = nil + self.spatialInTangent = spatialInTangent + self.spatialOutTangent = spatialOutTangent + } + + /// Initialize a keyframe + public init( + value: T, + time: AnimationFrameTime, + isHold: Bool = false, + inTangent: Vector2D? = nil, + outTangent: Vector2D? = nil, + spatialInTangent: Vector3D? = nil, + spatialOutTangent: Vector3D? = nil) + { + self.value = value + self.time = time + self.isHold = isHold + self.outTangent = outTangent + self.inTangent = inTangent + self.spatialInTangent = spatialInTangent + self.spatialOutTangent = spatialOutTangent + } + + // MARK: Public + + /// The value of the keyframe + public let value: T + /// The time in frames of the keyframe. + public let time: AnimationFrameTime + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + public let isHold: Bool + /// The in tangent for the time interpolation curve. + public let inTangent: Vector2D? + /// The out tangent for the time interpolation curve. + public let outTangent: Vector2D? + + /// The spatial in tangent of the vector. + public let spatialInTangent: Vector3D? + /// The spatial out tangent of the vector. + public let spatialOutTangent: Vector3D? +} + +// MARK: Equatable + +extension Keyframe: Equatable where T: Equatable { + public static func == (lhs: Keyframe, rhs: Keyframe) -> Bool { + lhs.value == rhs.value + && lhs.time == rhs.time + && lhs.isHold == rhs.isHold + && lhs.inTangent == rhs.inTangent + && lhs.outTangent == rhs.outTangent + && lhs.spatialInTangent == rhs.spatialOutTangent + && lhs.spatialOutTangent == rhs.spatialOutTangent + } +} + +// MARK: Hashable + +extension Keyframe: Hashable where T: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(value) + hasher.combine(time) + hasher.combine(isHold) + hasher.combine(inTangent) + hasher.combine(outTangent) + hasher.combine(spatialInTangent) + hasher.combine(spatialOutTangent) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/ValueInterpolators.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/ValueInterpolators.cpp new file mode 100644 index 00000000000..55e4fac6885 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/ValueInterpolators.cpp @@ -0,0 +1,5 @@ +#include "ValueInterpolators.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/ValueInterpolators.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/ValueInterpolators.hpp new file mode 100644 index 00000000000..b2c063dccc1 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Keyframes/ValueInterpolators.hpp @@ -0,0 +1,231 @@ +#ifndef ValueInterpolators_hpp +#define ValueInterpolators_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include "Lottie/Private/Model/Text/TextDocument.hpp" +#include "Lottie/Public/Primitives/GradientColorSet.hpp" +#include "Lottie/Public/Primitives/DashPattern.hpp" + +#include +#include + +namespace lottie { + +template +struct ValueInterpolator { +}; + +template<> +struct ValueInterpolator { +public: + static double interpolate(double value, double to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + return value + ((to - value) * amount); + } +}; + +template<> +struct ValueInterpolator { +public: + static Vector1D interpolate(Vector1D const &value, Vector1D const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + return Vector1D(ValueInterpolator::interpolate(value.value, to.value, amount, spatialOutTangent, spatialInTangent)); + } +}; + +template<> +struct ValueInterpolator { +public: + static Vector2D interpolate(Vector2D const &value, Vector2D const &to, double amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) { + auto cp1 = value + spatialOutTangent; + auto cp2 = to + spatialInTangent; + + return value.interpolate(to, cp1, cp2, amount); + } + + static Vector2D interpolate(Vector2D const &value, Vector2D const &to, double amount) { + return value.interpolate(to, amount); + } +}; + +template<> +struct ValueInterpolator { +public: + static Vector3D interpolate(Vector3D const &value, Vector3D const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + if (spatialOutTangent && spatialInTangent) { + Vector2D from2d(value.x, value.y); + Vector2D to2d(to.x, to.y); + + auto cp1 = from2d + spatialOutTangent.value(); + auto cp2 = to2d + spatialInTangent.value(); + + Vector2D result2d = from2d.interpolate(to2d, cp1, cp2, amount); + + return Vector3D( + result2d.x, + result2d.y, + ValueInterpolator::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent) + ); + } + + return Vector3D( + ValueInterpolator::interpolate(value.x, to.x, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.y, to.y, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent) + ); + } +}; + +template<> +struct ValueInterpolator { +public: + static Color interpolate(Color const &value, Color const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + return Color( + ValueInterpolator::interpolate(value.r, to.r, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.g, to.g, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.b, to.b, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.a, to.a, amount, spatialOutTangent, spatialInTangent) + ); + } +}; + +template<> +struct ValueInterpolator { +public: + static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, double amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) { + return CurveVertex::absolute( + ValueInterpolator::interpolate(value.point, to.point, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.inTangent, to.inTangent, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.outTangent, to.outTangent, amount, spatialOutTangent, spatialInTangent) + ); + } + + static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, double amount) { + return CurveVertex::absolute( + ValueInterpolator::interpolate(value.point, to.point, amount), + ValueInterpolator::interpolate(value.inTangent, to.inTangent, amount), + ValueInterpolator::interpolate(value.outTangent, to.outTangent, amount) + ); + } +}; + +template<> +struct ValueInterpolator { +public: + static BezierPath interpolate(BezierPath const &value, BezierPath const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + BezierPath newPath; + newPath.reserveCapacity(std::max(value.elements().size(), to.elements().size())); + //TODO:probably a bug in the upstream code, uncomment + //newPath.setClosed(value.closed()); + size_t elementCount = std::min(value.elements().size(), to.elements().size()); + + if (spatialInTangent && spatialOutTangent) { + Vector2D spatialInTangentValue = spatialInTangent.value(); + Vector2D spatialOutTangentValue = spatialOutTangent.value(); + + for (size_t i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + newPath.addVertex(ValueInterpolator::interpolate(fromVertex, toVertex, amount, spatialOutTangentValue, spatialInTangentValue)); + } + } else { + for (size_t i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + newPath.addVertex(ValueInterpolator::interpolate(fromVertex, toVertex, amount)); + } + } + return newPath; + } + + static void setInplace(BezierPath const &value, BezierPath &resultPath) { + resultPath.reserveCapacity(value.elements().size()); + resultPath.setElementCount(value.elements().size()); + resultPath.invalidateLength(); + + memcpy(resultPath.mutableElements().data(), value.elements().data(), value.elements().size() * sizeof(PathElement)); + } + + static void interpolateInplace(BezierPath const &value, BezierPath const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent, BezierPath &resultPath) { + /*if (value.elements().size() != to.elements().size()) { + return to; + }*/ + + //TODO:probably a bug in the upstream code, uncomment + //newPath.setClosed(value.closed()); + int elementCount = (int)std::min(value.elements().size(), to.elements().size()); + + resultPath.reserveCapacity(std::max(value.elements().size(), to.elements().size())); + resultPath.setElementCount(elementCount); + resultPath.invalidateLength(); + + if (spatialInTangent && spatialOutTangent) { + Vector2D spatialInTangentValue = spatialInTangent.value(); + Vector2D spatialOutTangentValue = spatialOutTangent.value(); + + for (int i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + auto vertex = ValueInterpolator::interpolate(fromVertex, toVertex, amount, spatialOutTangentValue, spatialInTangentValue); + + resultPath.updateVertex(vertex, i, false); + } + } else { + for (int i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + auto vertex = ValueInterpolator::interpolate(fromVertex, toVertex, amount); + + resultPath.updateVertex(vertex, i, false); + } + } + } +}; + +template<> +struct ValueInterpolator { +public: + static TextDocument interpolate(TextDocument const &value, TextDocument const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + if (amount == 1.0) { + return to; + } else { + return value; + } + } +}; + +template<> +struct ValueInterpolator { +public: + static GradientColorSet interpolate(GradientColorSet const &value, GradientColorSet const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + assert(value.colors.size() == to.colors.size()); + std::vector colors; + size_t colorCount = std::min(value.colors.size(), to.colors.size()); + for (size_t i = 0; i < colorCount; i++) { + colors.push_back(ValueInterpolator::interpolate(value.colors[i], to.colors[i], amount, spatialOutTangent, spatialInTangent)); + } + return GradientColorSet(colors); + } +}; + +template<> +struct ValueInterpolator { +public: + static DashPattern interpolate(DashPattern const &value, DashPattern const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + assert(value.values.size() == to.values.size()); + std::vector values; + size_t colorCount = std::min(value.values.size(), to.values.size()); + for (size_t i = 0; i < colorCount; i++) { + values.push_back(ValueInterpolator::interpolate(value.values[i], to.values[i], amount, spatialOutTangent, spatialInTangent)); + } + return DashPattern(std::move(values)); + } +}; + +} + +#endif /* ValueInterpolators_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Logging/LottieLogger.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Logging/LottieLogger.swift new file mode 100644 index 00000000000..983eb23b869 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Logging/LottieLogger.swift @@ -0,0 +1,108 @@ +// Created by eric_horacek on 12/9/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - LottieLogger + +/// A shared logger that allows consumers to intercept Lottie assertions and warning messages to pipe +/// into their own logging systems. +public final class LottieLogger { + + // MARK: Lifecycle + + public init( + assert: @escaping Assert = Swift.assert, + assertionFailure: @escaping AssertionFailure = Swift.assertionFailure, + warn: @escaping Warn = { message, _, _ in + #if DEBUG + // swiftlint:disable:next no_direct_standard_out_logs + print(message()) + #endif + }) + { + _assert = assert + _assertionFailure = assertionFailure + _warn = warn + } + + // MARK: Public + + /// Logs that an assertion occurred. + public typealias Assert = ( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String, + _ fileID: StaticString, + _ line: UInt) + -> Void + + /// Logs that an assertion failure occurred. + public typealias AssertionFailure = ( + _ message: @autoclosure () -> String, + _ fileID: StaticString, + _ line: UInt) + -> Void + + /// Logs a warning message. + public typealias Warn = ( + _ message: @autoclosure () -> String, + _ fileID: StaticString, + _ line: UInt) + -> Void + + /// The shared instance used to log Lottie assertions and warnings. + /// + /// Set this to a new logger instance to intercept assertions and warnings logged by Lottie. + public static var shared = LottieLogger() + + /// Logs that an assertion occurred. + public func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + fileID: StaticString = #fileID, + line: UInt = #line) + { + _assert(condition(), message(), fileID, line) + } + + /// Logs that an assertion failure occurred. + public func assertionFailure( + _ message: @autoclosure () -> String = String(), + fileID: StaticString = #fileID, + line: UInt = #line) + { + _assertionFailure(message(), fileID, line) + } + + /// Logs a warning message. + public func warn( + _ message: @autoclosure () -> String = String(), + fileID: StaticString = #fileID, + line: UInt = #line) + { + _warn(message(), fileID, line) + } + + // MARK: Private + + private let _assert: Assert + private let _assertionFailure: AssertionFailure + private let _warn: Warn + +} + +// MARK: - LottieLogger + printToConsole + +extension LottieLogger { + /// A `LottieLogger` instance that always prints to the console (by calling `print`) + /// instead of calling `assert` / `assertionFailure`, which halt execution in debug builds. + public static var printToConsole: LottieLogger { + LottieLogger( + assert: { condition, message, _, _ in + if !condition() { + print(message()) + } + }, + assertionFailure: { message, _, _ in + print(message()) + }) + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/LottieConfiguration.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/LottieConfiguration.swift new file mode 100644 index 00000000000..188bd371a9d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/LottieConfiguration.swift @@ -0,0 +1,143 @@ +// Created by Cal Stephens on 12/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +// MARK: - LottieConfiguration + +/// Global configuration options for Lottie animations +public struct LottieConfiguration: Hashable { + + public init( + renderingEngine: RenderingEngineOption = .mainThread, + decodingStrategy: DecodingStrategy = .codable) + { + self.renderingEngine = renderingEngine + self.decodingStrategy = decodingStrategy + } + + /// The global configuration of Lottie, + /// which applies to all `AnimationView`s by default. + public static var shared = LottieConfiguration() + + /// The rendering engine implementation to use when displaying an animation + public var renderingEngine: RenderingEngineOption + + /// The decoding implementation to use when parsing an animation JSON file + public var decodingStrategy: DecodingStrategy + +} + +// MARK: - RenderingEngineOption + +public enum RenderingEngineOption: Hashable { + /// Uses the Core Animation engine for supported animations, and falls back to using + /// the Main Thread engine for animations that use features not supported by the + /// Core Animation engine. + case automatic + + /// Uses the specified rendering engine + case specific(RenderingEngine) + + /// The Main Thread rendering engine, which supports all Lottie features + /// but runs on the main thread, which comes with some CPU overhead and + /// can cause the animation to play at a low framerate when the CPU is busy. + public static var mainThread: RenderingEngineOption { .specific(.mainThread) } + + /// The Core Animation rendering engine, that animates using Core Animation + /// and has better performance characteristics than the Main Thread engine, + /// but doesn't support all Lottie features. + public static var coreAnimation: RenderingEngineOption { .specific(.coreAnimation) } +} + +// MARK: - RenderingEngine + +/// The rendering engine implementation to use when displaying an animation +public enum RenderingEngine: Hashable { + /// The Main Thread rendering engine, which supports all Lottie features + /// but runs on the main thread, which comes with some CPU overhead and + /// can cause the animation to play at a low framerate when the CPU is busy. + case mainThread + + /// The Core Animation rendering engine, that animates using Core Animation + /// and has better performance characteristics than the Main Thread engine, + /// but doesn't support all Lottie features. + case coreAnimation +} + +// MARK: - RenderingEngineOption + RawRepresentable, CustomStringConvertible + +extension RenderingEngineOption: RawRepresentable, CustomStringConvertible { + + // MARK: Lifecycle + + public init?(rawValue: String) { + if rawValue == "Automatic" { + self = .automatic + } else if let engine = RenderingEngine(rawValue: rawValue) { + self = .specific(engine) + } else { + return nil + } + } + + // MARK: Public + + public var rawValue: String { + switch self { + case .automatic: + return "Automatic" + case .specific(let engine): + return engine.rawValue + } + } + + public var description: String { + rawValue + } + +} + +// MARK: - RenderingEngine + RawRepresentable, CustomStringConvertible + +extension RenderingEngine: RawRepresentable, CustomStringConvertible { + + // MARK: Lifecycle + + public init?(rawValue: String) { + switch rawValue { + case "Main Thread": + self = .mainThread + case "Core Animation": + self = .coreAnimation + default: + return nil + } + } + + // MARK: Public + + public var rawValue: String { + switch self { + case .mainThread: + return "Main Thread" + case .coreAnimation: + return "Core Animation" + } + } + + public var description: String { + rawValue + } +} + +// MARK: - DecodingStrategy + +/// How animation files should be decoded +public enum DecodingStrategy: Hashable { + /// Use Codable. This is the default strategy introduced on Lottie 3. + case codable + + /// Manually deserialize a dictionary into an Animation. + /// This should be at least 2-3x faster than using Codable, + /// but since it's manually implemented, there might be issues while it's experimental. + case dictionaryBased +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnimationTime.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnimationTime.cpp new file mode 100644 index 00000000000..c804b75b106 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnimationTime.cpp @@ -0,0 +1,5 @@ +#include "AnimationTime.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnimationTime.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnimationTime.hpp new file mode 100644 index 00000000000..02a3e558d38 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnimationTime.hpp @@ -0,0 +1,14 @@ +#ifndef AnimationTime_hpp +#define AnimationTime_hpp + +namespace lottie { + +/// Defines animation time in Frames (Seconds * Framerate). +typedef double AnimationFrameTime; + +/// Defines animation time by a progress from 0 (beginning of the animation) to 1 (end of the animation) +typedef double AnimationProgressTime; + +} + +#endif /* AnimationTime_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnimationTime.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnimationTime.swift new file mode 100644 index 00000000000..2c33e2b4528 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnimationTime.swift @@ -0,0 +1,15 @@ +// +// AnimationTime.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/6/19. +// + +import CoreGraphics +import Foundation + +/// Defines animation time in Frames (Seconds * Framerate). +public typealias AnimationFrameTime = CGFloat + +/// Defines animation time by a progress from 0 (beginning of the animation) to 1 (end of the animation) +public typealias AnimationProgressTime = CGFloat diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnyValue.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnyValue.cpp new file mode 100644 index 00000000000..1af508b3de1 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnyValue.cpp @@ -0,0 +1,5 @@ +#include "AnyValue.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnyValue.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnyValue.hpp new file mode 100644 index 00000000000..516b2e7d10c --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/AnyValue.hpp @@ -0,0 +1,245 @@ +#ifndef AnyValue_hpp +#define AnyValue_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include "Lottie/Private/Model/Text/TextDocument.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" +#include "Lottie/Private/Model/Objects/DashElement.hpp" + +#include +#include + +namespace lottie { + +class AnyValue { +public: + enum class Type { + Double, + Vector1D, + Vector2D, + Vector3D, + Color, + BezierPath, + TextDocument, + GradientColorSet, + DashPattern + }; + +public: + AnyValue(double value) : + _type(Type::Double), + _doubleValue(value) { + } + + AnyValue(Vector1D const &value) : + _type(Type::Vector1D), + _vector1DValue(value) { + } + + AnyValue(Vector2D const &value) : + _type(Type::Vector2D), + _vector2DValue(value) { + } + + AnyValue(Vector3D const & value) : + _type(Type::Vector3D), + _vector3DValue(value) { + } + + AnyValue(Color const &value) : + _type(Type::Color), + _colorValue(value) { + } + + AnyValue(BezierPath const &value) : + _type(Type::BezierPath), + _bezierPathValue(value) { + } + + AnyValue(TextDocument const &value) : + _type(Type::TextDocument), + _textDocumentValue(value) { + } + + AnyValue(GradientColorSet const &value) : + _type(Type::GradientColorSet), + _gradientColorSetValue(value) { + } + + AnyValue(DashPattern const &value) : + _type(Type::DashPattern), + _dashPatternValue(value) { + } + + template::value>> + double get() { + return asDouble(); + } + + template::value>> + Vector1D get() { + return asVector1D(); + } + + template::value>> + Vector2D get() { + return asVector2D(); + } + + template::value>> + Vector3D get() { + return asVector3D(); + } + + template::value>> + Color get() { + return asColor(); + } + + template::value>> + BezierPath get() { + return asBezierPath(); + } + + template::value>> + TextDocument get() { + return asTextDocument(); + } + + template::value>> + GradientColorSet get() { + return asGradientColorSet(); + } + + template::value>> + DashPattern get() { + return asDashPattern(); + } + +public: + Type type() { + return _type; + } + + double asDouble() { + return _doubleValue.value(); + } + + Vector1D asVector1D() { + return _vector1DValue.value(); + } + + Vector2D asVector2D() { + return _vector2DValue.value(); + } + + Vector3D asVector3D() { + return _vector3DValue.value(); + } + + Color asColor() { + return _colorValue.value(); + } + + BezierPath asBezierPath() { + return _bezierPathValue.value(); + } + + TextDocument asTextDocument() { + return _textDocumentValue.value(); + } + + GradientColorSet asGradientColorSet() { + return _gradientColorSetValue.value(); + } + + DashPattern asDashPattern() { + return _dashPatternValue.value(); + } + +private: + Type _type; + + std::optional _doubleValue; + std::optional _vector1DValue; + std::optional _vector2DValue; + std::optional _vector3DValue; + std::optional _colorValue; + std::optional _bezierPathValue; + std::optional _textDocumentValue; + std::optional _gradientColorSetValue; + std::optional _dashPatternValue; +}; + +template +struct AnyValueType { +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Double; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Vector1D; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Vector2D; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Vector3D; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Color; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::BezierPath; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::TextDocument; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::GradientColorSet; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::DashPattern; + } +}; + +} + +#endif /* AnyValue_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CALayer.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CALayer.hpp new file mode 100644 index 00000000000..92cb5865083 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CALayer.hpp @@ -0,0 +1,760 @@ +#ifndef CALayer_hpp +#define CALayer_hpp + +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/CGPath.hpp" +#include "Lottie/Private/Model/ShapeItems/Fill.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Public/Primitives/DrawingAttributes.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" + +#include +#include +#include + +namespace lottie { + +enum class CGBlendMode { + Normal, + DestinationIn, + DestinationOut +}; + +class CGImage { +public: + virtual ~CGImage() = default; +}; + +class CGGradient { +public: + CGGradient(std::vector const &colors, std::vector const &locations) : + _colors(colors), + _locations(locations) { + assert(_colors.size() == _locations.size()); + } + + std::vector const &colors() const { + return _colors; + } + + std::vector const &locations() const { + return _locations; + } + +private: + std::vector _colors; + std::vector _locations; +}; + +class CGContext { +public: + virtual ~CGContext() = default; + + virtual int width() const = 0; + virtual int height() const = 0; + + virtual std::shared_ptr makeLayer(int width, int height) = 0; + + virtual void saveState() = 0; + virtual void restoreState() = 0; + + virtual void fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) = 0; + virtual void linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) = 0; + virtual void radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) = 0; + + virtual void strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) = 0; + virtual void linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) = 0; + virtual void radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) = 0; + + virtual void fill(CGRect const &rect, Color const &fillColor) = 0; + virtual void setBlendMode(CGBlendMode blendMode) = 0; + + virtual void setAlpha(double alpha) = 0; + + virtual void concatenate(CATransform3D const &transform) = 0; + + virtual void draw(std::shared_ptr const &other, CGRect const &rect) = 0; +}; + +class RenderableItem { +public: + enum class Type { + Shape, + GradientFill + }; + +public: + RenderableItem() { + } + + virtual ~RenderableItem() = default; + + virtual Type type() const = 0; + virtual CGRect boundingRect() const = 0; + + virtual bool isEqual(std::shared_ptr rhs) const = 0; +}; + +class ShapeRenderableItem: public RenderableItem { +public: + struct Fill { + Color color; + FillRule rule; + + Fill(Color color_, FillRule rule_) : + color(color_), rule(rule_) { + } + + bool operator==(Fill const &rhs) const { + if (color != rhs.color) { + return false; + } + if (rule != rhs.rule) { + return false; + } + return true; + } + + bool operator!=(Fill const &rhs) const { + return !(*this == rhs); + } + }; + + struct Stroke { + Color color; + double lineWidth = 0.0; + LineJoin lineJoin = LineJoin::Round; + LineCap lineCap = LineCap::Square; + double dashPhase = 0.0; + std::vector dashPattern; + + Stroke( + Color color_, + double lineWidth_, + LineJoin lineJoin_, + LineCap lineCap_, + double dashPhase_, + std::vector dashPattern_ + ) : + color(color_), + lineWidth(lineWidth_), + lineJoin(lineJoin_), + lineCap(lineCap_), + dashPhase(dashPhase_), + dashPattern(dashPattern_) { + } + + bool operator==(Stroke const &rhs) const { + if (color != rhs.color) { + return false; + } + if (lineWidth != rhs.lineWidth) { + return false; + } + if (lineJoin != rhs.lineJoin) { + return false; + } + if (lineCap != rhs.lineCap) { + return false; + } + if (dashPhase != rhs.dashPhase) { + return false; + } + if (dashPattern != rhs.dashPattern) { + return false; + } + return true; + } + + bool operator!=(Stroke const &rhs) const { + return !(*this == rhs); + } + }; + +public: + ShapeRenderableItem( + std::shared_ptr path_, + std::optional const &fill_, + std::optional const &stroke_ + ) : + path(path_), + fill(fill_), + stroke(stroke_) { + } + + virtual Type type() const override { + return Type::Shape; + } + + virtual CGRect boundingRect() const override { + if (path) { + CGRect shapeBounds = path->boundingBox(); + if (stroke) { + shapeBounds = shapeBounds.insetBy(-stroke->lineWidth / 2.0, -stroke->lineWidth / 2.0); + } + return shapeBounds; + } else { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + } + + virtual bool isEqual(std::shared_ptr rhs) const override { + if (rhs->type() != type()) { + return false; + } + ShapeRenderableItem *other = (ShapeRenderableItem *)rhs.get(); + if ((path == nullptr) != (other->path == nullptr)) { + return false; + } else if (path) { + if (!path->isEqual(other->path.get())) { + return false; + } + } + if (fill != other->fill) { + return false; + } + if (stroke != other->stroke) { + return false; + } + return false; + } + +public: + std::shared_ptr path; + std::optional fill; + std::optional stroke; +}; + +class GradientFillRenderableItem: public RenderableItem { +public: + GradientFillRenderableItem( + std::shared_ptr path_, + FillRule pathFillRule_, + GradientType gradientType_, + std::vector const &colors_, + std::vector const &locations_, + Vector2D const &start_, + Vector2D const &end_, + CGRect bounds_ + ) : + path(path_), + pathFillRule(pathFillRule_), + gradientType(gradientType_), + colors(colors_), + locations(locations_), + start(start_), + end(end_), + bounds(bounds_) { + } + + virtual Type type() const override { + return Type::GradientFill; + } + + virtual CGRect boundingRect() const override { + return bounds; + } + + virtual bool isEqual(std::shared_ptr rhs) const override { + if (rhs->type() != type()) { + return false; + } + GradientFillRenderableItem *other = (GradientFillRenderableItem *)rhs.get(); + + if (gradientType != other->gradientType) { + return false; + } + if (colors != other->colors) { + return false; + } + if (locations != other->locations) { + return false; + } + if (start != other->start) { + return false; + } + if (end != other->end) { + return false; + } + if (bounds != other->bounds) { + return false; + } + + return true; + } + +public: + std::shared_ptr path; + FillRule pathFillRule; + GradientType gradientType; + std::vector colors; + std::vector locations; + Vector2D start; + Vector2D end; + CGRect bounds; +}; + +class RenderTreeNodeContent { +public: + enum class ShadingType { + Solid, + Gradient + }; + + class Shading { + public: + Shading() { + } + + virtual ~Shading() = default; + + virtual ShadingType type() const = 0; + }; + + class SolidShading: public Shading { + public: + SolidShading(Color const &color_, double opacity_) : + color(color_), + opacity(opacity_) { + } + + virtual ShadingType type() const override { + return ShadingType::Solid; + } + + public: + Color color; + double opacity = 0.0; + }; + + class GradientShading: public Shading { + public: + GradientShading( + double opacity_, + GradientType gradientType_, + std::vector const &colors_, + std::vector const &locations_, + Vector2D const &start_, + Vector2D const &end_ + ) : + opacity(opacity_), + gradientType(gradientType_), + colors(colors_), + locations(locations_), + start(start_), + end(end_) { + } + + virtual ShadingType type() const override { + return ShadingType::Gradient; + } + + public: + double opacity = 0.0; + GradientType gradientType; + std::vector colors; + std::vector locations; + Vector2D start; + Vector2D end; + }; + + struct Stroke { + std::shared_ptr shading; + double lineWidth = 0.0; + LineJoin lineJoin = LineJoin::Round; + LineCap lineCap = LineCap::Square; + double miterLimit = 4.0; + double dashPhase = 0.0; + std::vector dashPattern; + + Stroke( + std::shared_ptr shading_, + double lineWidth_, + LineJoin lineJoin_, + LineCap lineCap_, + double miterLimit_, + double dashPhase_, + std::vector dashPattern_ + ) : + shading(shading_), + lineWidth(lineWidth_), + lineJoin(lineJoin_), + lineCap(lineCap_), + miterLimit(miterLimit_), + dashPhase(dashPhase_), + dashPattern(dashPattern_) { + } + }; + + struct Fill { + std::shared_ptr shading; + FillRule rule; + + Fill( + std::shared_ptr shading_, + FillRule rule_ + ) : + shading(shading_), + rule(rule_) { + } + }; + +public: + RenderTreeNodeContent( + std::vector paths_, + std::shared_ptr stroke_, + std::shared_ptr fill_ + ) : + paths(paths_), + stroke(stroke_), + fill(fill_) { + } + +public: + std::vector paths; + std::shared_ptr stroke; + std::shared_ptr fill; +}; + +class RenderTreeNode { +public: + RenderTreeNode( + CGRect bounds_, + Vector2D position_, + CATransform3D transform_, + double alpha_, + bool masksToBounds_, + bool isHidden_, + std::shared_ptr content_, + std::vector> subnodes_, + std::shared_ptr mask_, + bool invertMask_ + ) : + _bounds(bounds_), + _position(position_), + _transform(transform_), + _alpha(alpha_), + _masksToBounds(masksToBounds_), + _isHidden(isHidden_), + _content(content_), + _subnodes(subnodes_), + _mask(mask_), + _invertMask(invertMask_) { + } + + ~RenderTreeNode() { + } + +public: + CGRect const &bounds() const { + return _bounds; + } + + Vector2D const &position() const { + return _position; + } + + CATransform3D const &transform() const { + return _transform; + } + + double alpha() const { + return _alpha; + } + + bool masksToBounds() const { + return _masksToBounds; + } + + bool isHidden() const { + return _isHidden; + } + + std::shared_ptr const &content() const { + return _content; + } + + std::vector> const &subnodes() const { + return _subnodes; + } + + std::shared_ptr const &mask() const { + return _mask; + } + + bool invertMask() const { + return _invertMask; + } + +public: + CGRect _bounds; + Vector2D _position; + CATransform3D _transform = CATransform3D::identity(); + double _alpha = 1.0; + bool _masksToBounds = false; + bool _isHidden = false; + std::shared_ptr _content; + std::vector> _subnodes; + std::shared_ptr _mask; + bool _invertMask = false; +}; + +class CALayer: public std::enable_shared_from_this { +public: + CALayer() { + } + + void addSublayer(std::shared_ptr layer) { + if (layer->_superlayer) { + layer->_superlayer->removeSublayer(layer.get()); + } + layer->_superlayer = this; + _sublayers.push_back(layer); + } + + void insertSublayer(std::shared_ptr layer, int index) { + if (layer->_superlayer) { + layer->_superlayer->removeSublayer(layer.get()); + } + layer->_superlayer = this; + _sublayers.insert(_sublayers.begin() + index, layer); + } + + void removeFromSuperlayer() { + if (_superlayer) { + _superlayer->removeSublayer(this); + } + } + + bool needsDisplay() const { + return _needsDisplay; + } + void setNeedsDisplay(bool needsDisplay) { + _needsDisplay = true; + } + + virtual bool implementsDraw() const { + return false; + } + + virtual bool isInvertedMatte() const { + return false; + } + + virtual void draw(std::shared_ptr const &context) { + } + + virtual std::shared_ptr renderableItem() { + return nullptr; + } + + bool isHidden() const { + return _isHidden; + } + void setIsHidden(bool isHidden) { + _isHidden = isHidden; + } + + float opacity() const { + return _opacity; + } + void setOpacity(float opacity) { + _opacity = opacity; + } + + Vector2D const &position() const { + return _position; + } + void setPosition(Vector2D const &position) { + _position = position; + } + + CGRect const &bounds() const { + return _bounds; + } + void setBounds(CGRect const &bounds) { + _bounds = bounds; + } + + virtual CGRect effectiveBounds() const { + return bounds(); + } + + CATransform3D const &transform() const { + return _transform; + } + void setTransform(CATransform3D const &transform) { + _transform = transform; + } + + std::shared_ptr const &mask() const { + return _mask; + } + void setMask(std::shared_ptr mask) { + _mask = mask; + } + + bool masksToBounds() const { + return _masksToBounds; + } + void setMasksToBounds(bool masksToBounds) { + _masksToBounds = masksToBounds; + } + + std::vector> const &sublayers() const { + return _sublayers; + } + + std::optional const &compositingFilter() const { + return _compositingFilter; + } + void setCompositingFilter(std::optional const &compositingFilter) { + _compositingFilter = compositingFilter; + } + + std::shared_ptr const &contents() const { + return _contents; + } + void setContents(std::shared_ptr contents) { + _contents = contents; + } + +protected: + template + std::shared_ptr shared_from_base() { + return std::static_pointer_cast(shared_from_this()); + } + +private: + void removeSublayer(CALayer *layer) { + for (auto it = _sublayers.begin(); it != _sublayers.end(); it++) { + if (it->get() == layer) { + layer->_superlayer = nullptr; + _sublayers.erase(it); + break; + } + } + } + +private: + CALayer *_superlayer = nullptr; + std::vector> _sublayers; + bool _needsDisplay = false; + bool _isHidden = false; + float _opacity = 1.0; + Vector2D _position = Vector2D(0.0, 0.0); + CGRect _bounds = CGRect(0.0, 0.0, 0.0, 0.0); + CATransform3D _transform = CATransform3D::identity(); + std::shared_ptr _mask; + bool _masksToBounds = false; + std::optional _compositingFilter; + std::shared_ptr _contents; +}; + +class CAShapeLayer: public CALayer { +public: + CAShapeLayer() { + } + + std::optional const &strokeColor() { + return _strokeColor; + } + void setStrokeColor(std::optional const &strokeColor) { + _strokeColor = strokeColor; + } + + std::optional const &fillColor() { + return _fillColor; + } + void setFillColor(std::optional const &fillColor) { + _fillColor = fillColor; + } + + FillRule fillRule() { + return _fillRule; + } + void setFillRule(FillRule fillRule) { + _fillRule = fillRule; + } + + std::shared_ptr const &path() const { + return _path; + } + void setPath(std::shared_ptr const &path) { + _path = path; + } + + double lineWidth() const { + return _lineWidth; + } + void setLineWidth(double lineWidth) { + _lineWidth = lineWidth; + } + + LineJoin lineJoin() const { + return _lineJoin; + } + void setLineJoin(LineJoin lineJoin) { + _lineJoin = lineJoin; + } + + LineCap lineCap() const { + return _lineCap; + } + void setLineCap(LineCap lineCap) { + _lineCap = lineCap; + } + + double lineDashPhase() const { + return _lineDashPhase; + } + void setLineDashPhase(double lineDashPhase) { + _lineDashPhase = lineDashPhase; + } + + std::vector const &dashPattern() const { + return _dashPattern; + } + void setDashPattern(std::vector const &dashPattern) { + _dashPattern = dashPattern; + } + + virtual CGRect effectiveBounds() const override { + if (_path) { + CGRect boundingBox = _path->boundingBox(); + if (_strokeColor) { + boundingBox.x -= _lineWidth / 2.0; + boundingBox.y -= _lineWidth / 2.0; + boundingBox.width += _lineWidth; + boundingBox.height += _lineWidth; + } + return boundingBox; + } else { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + } + + /*virtual bool implementsDraw() const override { + return true; + } + + virtual void draw(std::shared_ptr const &context) override;*/ + + std::shared_ptr renderableItem() override; + +private: + std::optional _strokeColor; + std::optional _fillColor = Color(0.0, 0.0, 0.0, 1.0); + FillRule _fillRule = FillRule::NonZeroWinding; + std::shared_ptr _path; + double _lineWidth = 1.0; + LineJoin _lineJoin = LineJoin::Miter; + LineCap _lineCap = LineCap::Butt; + double _lineDashPhase = 0.0; + std::vector _dashPattern; +}; + +} + +#endif /* CALayer_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CALayer.mm b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CALayer.mm new file mode 100644 index 00000000000..1b7d55db3d3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CALayer.mm @@ -0,0 +1,497 @@ +#include "CALayer.hpp" +#include "Lottie/Public/Primitives/CALayerCocoa.h" + +#include "Lottie/Public/Primitives/VectorsCocoa.h" +#include "Lottie/Public/Primitives/CGPathCocoa.h" + +#import + +namespace lottie { + +namespace { + +int alignUp(int size, int align) { + assert(((align - 1) & align) == 0); + + int alignmentMask = align - 1; + return (size + alignmentMask) & ~alignmentMask; +} + +} + +CGImageImpl::CGImageImpl(::CGImageRef image) { + _image = CGImageRetain(image); +} + +CGImageImpl::~CGImageImpl() { + CFRelease(_image); +} + +::CGImageRef CGImageImpl::nativeImage() const { + return _image; +} + + +CGContextImpl::CGContextImpl(int width, int height) { + _width = width; + _height = height; + _bytesPerRow = alignUp(width * 4, 16); + _backingData.resize(_bytesPerRow * _height); + memset(_backingData.data(), 0, _backingData.size()); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; + _context = CGBitmapContextCreate(_backingData.data(), _width, _height, 8, _bytesPerRow, colorSpace, bitmapInfo); + + CGContextClearRect(_context, CGRectMake(0.0, 0.0, _width, _height)); + + //CGContextSetInterpolationQuality(_context, kCGInterpolationLow); + //CGContextSetAllowsAntialiasing(_context, true); + //CGContextSetShouldAntialias(_context, true); + + CFRelease(colorSpace); + + _topContext = CGContextRetain(_context); +} + +CGContextImpl::CGContextImpl(CGContextRef context, int width, int height) { + _topContext = CGContextRetain(context); + _layer = CGLayerCreateWithContext(context, CGSizeMake(width, height), nil); + _context = CGContextRetain(CGLayerGetContext(_layer)); + _width = width; + _height = height; +} + +CGContextImpl::~CGContextImpl() { + CFRelease(_context); + if (_topContext) { + CFRelease(_topContext); + } + if (_layer) { + CFRelease(_layer); + } +} + +int CGContextImpl::width() const { + return _width; +} + +int CGContextImpl::height() const { + return _height; +} + +std::shared_ptr CGContextImpl::makeLayer(int width, int height) { + return std::make_shared(_topContext, width, height); +} + +void CGContextImpl::saveState() { + CGContextSaveGState(_context); +} + +void CGContextImpl::restoreState() { + CGContextRestoreGState(_context); +} + +void CGContextImpl::fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) { + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + CGFloat components[4] = { color.r, color.g, color.b, color.a }; + CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); + CGContextSetFillColorWithColor(_context, nativeColor); + CFRelease(nativeColor); + + switch (fillRule) { + case FillRule::EvenOdd: { + CGContextEOFillPath(_context); + break; + } + default: { + CGContextFillPath(_context); + break; + } + } +} + +void CGContextImpl::linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) { + CGContextSaveGState(_context); + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + switch (fillRule) { + case FillRule::EvenOdd: { + CGContextEOClip(_context); + break; + } + default: { + CGContextClip(_context); + break; + } + } + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + if (nativeGradient) { + CGContextDrawLinearGradient(_context, nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CGContextImpl::radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) { + CGContextSaveGState(_context); + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + switch (fillRule) { + case FillRule::EvenOdd: { + CGContextEOClip(_context); + break; + } + default: { + CGContextClip(_context); + break; + } + } + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + if (nativeGradient) { + CGContextDrawRadialGradient(_context, nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CGContextImpl::strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) { + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + CGFloat components[4] = { color.r, color.g, color.b, color.a }; + CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); + CGContextSetStrokeColorWithColor(_context, nativeColor); + CFRelease(nativeColor); + + CGContextSetLineWidth(_context, lineWidth); + + switch (lineJoin) { + case LineJoin::Miter: { + CGContextSetLineJoin(_context, kCGLineJoinMiter); + break; + } + case LineJoin::Round: { + CGContextSetLineJoin(_context, kCGLineJoinRound); + break; + } + case LineJoin::Bevel: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + default: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + } + + switch (lineCap) { + case LineCap::Butt: { + CGContextSetLineCap(_context, kCGLineCapButt); + break; + } + case LineCap::Round: { + CGContextSetLineCap(_context, kCGLineCapRound); + break; + } + case LineCap::Square: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + default: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + } + + if (!dashPattern.empty()) { + CGContextSetLineDash(_context, dashPhase, dashPattern.data(), dashPattern.size()); + } + CGContextStrokePath(_context); +} + +void CGContextImpl::linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) { + CGContextSaveGState(_context); + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + CGContextSetLineWidth(_context, lineWidth); + + switch (lineJoin) { + case LineJoin::Miter: { + CGContextSetLineJoin(_context, kCGLineJoinMiter); + break; + } + case LineJoin::Round: { + CGContextSetLineJoin(_context, kCGLineJoinRound); + break; + } + case LineJoin::Bevel: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + default: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + } + + switch (lineCap) { + case LineCap::Butt: { + CGContextSetLineCap(_context, kCGLineCapButt); + break; + } + case LineCap::Round: { + CGContextSetLineCap(_context, kCGLineCapRound); + break; + } + case LineCap::Square: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + default: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + } + + if (!dashPattern.empty()) { + CGContextSetLineDash(_context, dashPhase, dashPattern.data(), dashPattern.size()); + } + + CGContextReplacePathWithStrokedPath(_context); + CGContextClip(_context); + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + if (nativeGradient) { + CGContextDrawLinearGradient(_context, nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CGContextImpl::radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) { + CGContextSaveGState(_context); + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + CGContextSetLineWidth(_context, lineWidth); + + switch (lineJoin) { + case LineJoin::Miter: { + CGContextSetLineJoin(_context, kCGLineJoinMiter); + break; + } + case LineJoin::Round: { + CGContextSetLineJoin(_context, kCGLineJoinRound); + break; + } + case LineJoin::Bevel: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + default: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + } + + switch (lineCap) { + case LineCap::Butt: { + CGContextSetLineCap(_context, kCGLineCapButt); + break; + } + case LineCap::Round: { + CGContextSetLineCap(_context, kCGLineCapRound); + break; + } + case LineCap::Square: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + default: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + } + + if (!dashPattern.empty()) { + CGContextSetLineDash(_context, dashPhase, dashPattern.data(), dashPattern.size()); + } + + CGContextReplacePathWithStrokedPath(_context); + CGContextClip(_context); + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + if (nativeGradient) { + CGContextDrawRadialGradient(_context, nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CGContextImpl::fill(CGRect const &rect, Color const &fillColor) { + CGFloat components[4] = { fillColor.r, fillColor.g, fillColor.b, fillColor.a }; + CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); + CGContextSetFillColorWithColor(_context, nativeColor); + CFRelease(nativeColor); + + CGContextFillRect(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height)); +} + +void CGContextImpl::setBlendMode(CGBlendMode blendMode) { + ::CGBlendMode nativeMode = kCGBlendModeNormal; + switch (blendMode) { + case CGBlendMode::Normal: { + nativeMode = kCGBlendModeNormal; + break; + } + case CGBlendMode::DestinationIn: { + nativeMode = kCGBlendModeDestinationIn; + break; + } + case CGBlendMode::DestinationOut: { + nativeMode = kCGBlendModeDestinationOut; + break; + } + } + CGContextSetBlendMode(_context, nativeMode); +} + +void CGContextImpl::setAlpha(double alpha) { + CGContextSetAlpha(_context, alpha); +} + +void CGContextImpl::concatenate(CATransform3D const &transform) { + CGContextConcatCTM(_context, CATransform3DGetAffineTransform(nativeTransform(transform))); +} + +std::shared_ptr CGContextImpl::makeImage() const { + ::CGImageRef nativeImage = CGBitmapContextCreateImage(_context); + if (nativeImage) { + auto image = std::make_shared(nativeImage); + CFRelease(nativeImage); + return image; + } else { + return nil; + } +} + +void CGContextImpl::draw(std::shared_ptr const &other, CGRect const &rect) { + CGContextImpl *impl = (CGContextImpl *)other.get(); + if (impl->_layer) { + CGContextDrawLayerInRect(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height), impl->_layer); + } else { + auto image = impl->makeImage(); + CGContextDrawImage(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height), ((CGImageImpl *)image.get())->nativeImage()); + } +} + +std::shared_ptr CAShapeLayer::renderableItem() { + if (!_path) { + return nullptr; + } + + std::optional fill; + if (_fillColor) { + fill = ShapeRenderableItem::Fill( + _fillColor.value(), + _fillRule + ); + } + + std::optional stroke; + if (_strokeColor) { + stroke = ShapeRenderableItem::Stroke( + _strokeColor.value(), + _lineWidth, + _lineJoin, + _lineCap, + _lineDashPhase, + _dashPattern + ); + } + + return std::make_shared( + _path, + fill, + stroke + ); +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CALayerCocoa.h b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CALayerCocoa.h new file mode 100644 index 00000000000..c04bbfcffec --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CALayerCocoa.h @@ -0,0 +1,74 @@ +#ifndef CALayerCocoa_h +#define CALayerCocoa_h + +#import + +#include "Lottie/Public/Primitives/CALayer.hpp" + +namespace lottie { + +class CGImageImpl: public CGImage { +public: + CGImageImpl(::CGImageRef image); + virtual ~CGImageImpl(); + ::CGImageRef nativeImage() const; + +private: + CGImageRef _image = nil; +}; + +class CGContextImpl: public CGContext { +public: + CGContextImpl(int width, int height); + CGContextImpl(CGContextRef context, int width, int height); + virtual ~CGContextImpl(); + + virtual int width() const override; + virtual int height() const override; + + std::shared_ptr makeLayer(int width, int height) override; + + virtual void saveState() override; + virtual void restoreState() override; + + virtual void fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) override; + virtual void linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) override; + virtual void radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) override; + + virtual void strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) override; + virtual void linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) override; + virtual void radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) override; + + virtual void fill(CGRect const &rect, Color const &fillColor) override; + virtual void setBlendMode(CGBlendMode blendMode) override; + virtual void setAlpha(double alpha) override; + virtual void concatenate(CATransform3D const &transform) override; + + virtual std::shared_ptr makeImage() const; + virtual void draw(std::shared_ptr const &other, CGRect const &rect) override; + + CGContextRef nativeContext() const { + return _context; + } + + std::vector &backingData() { + return _backingData; + } + + int bytesPerRow() { + return _bytesPerRow; + } + +private: + int _width = 0; + int _height = 0; + int _bytesPerRow = 0; + std::vector _backingData; + CGContextRef _context = nil; + CGContextRef _topContext = nil; + CGLayerRef _layer = nil; +}; + +} + +#endif /* CALayerCocoa_h */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextSkiaImpl.h b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextSkiaImpl.h new file mode 100644 index 00000000000..ef226134830 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextSkiaImpl.h @@ -0,0 +1,51 @@ +#ifndef CGContextSkiaImpl_h +#define CGContextSkiaImpl_h + +#include "Lottie/Public/Primitives/CALayer.hpp" + +#include "include/core/SkCanvas.h" +#include "include/core/SkSurface.h" + +namespace lottie { + +class CGContextSkiaImpl: public CGContext { +public: + CGContextSkiaImpl(int width, int height); + virtual ~CGContextSkiaImpl(); + + virtual int width() const override; + virtual int height() const override; + + virtual std::shared_ptr makeLayer(int width, int height) override; + + virtual void saveState() override; + virtual void restoreState() override; + + virtual void fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) override; + virtual void linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) override; + virtual void radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) override; + virtual void strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) override; + virtual void fill(CGRect const &rect, Color const &fillColor) override; + + virtual void setBlendMode(CGBlendMode blendMode) override; + + virtual void setAlpha(double alpha) override; + + virtual void concatenate(CATransform3D const &transform) override; + + virtual void draw(std::shared_ptr const &other, CGRect const &rect) override; + + sk_sp surface() const; + +private: + int _width = 0; + int _height = 0; + sk_sp _surface; + SkCanvas *_canvas = nullptr; + SkBlendMode _blendMode = SkBlendMode::kSrcOver; + double _alpha = 1.0; +}; + +} + +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextSkiaImpl.mm b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextSkiaImpl.mm new file mode 100644 index 00000000000..422327c18e7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextSkiaImpl.mm @@ -0,0 +1,286 @@ +#include "Lottie/Public/Primitives/CGContextSkiaImpl.h" + +#include "include/core/SkCanvas.h" +#include "include/core/SkColor.h" +#include "include/core/SkFont.h" +#include "include/core/SkFontTypes.h" +#include "include/core/SkGraphics.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPoint.h" +#include "include/core/SkRect.h" +#include "include/core/SkShader.h" +#include "include/core/SkString.h" +#include "include/core/SkSurface.h" +#include "include/core/SkTileMode.h" +#include "include/core/SkPath.h" +#include "include/core/SkPathEffect.h" +#include "include/effects/SkDashPathEffect.h" +#include "include/effects/SkGradientShader.h" + +#include "Lottie/Public/Primitives/CALayerCocoa.h" +#include "Lottie/Public/Primitives/CGPathCocoa.h" + +namespace lottie { + +namespace { + +SkColor skColor(Color const &color) { + return SkColorSetARGB((uint8_t)(color.a * 255.0), (uint8_t)(color.r * 255.0), (uint8_t)(color.g * 255.0), (uint8_t)(color.b * 255.0)); +} + +void skPath(std::shared_ptr const &path, SkPath &nativePath) { + path->enumerate([&nativePath](CGPathItem const &item) { + switch (item.type) { + case CGPathItem::Type::MoveTo: { + nativePath.moveTo(item.points[0].x, item.points[0].y); + break; + } + case CGPathItem::Type::LineTo: { + nativePath.lineTo(item.points[0].x, item.points[0].y); + break; + } + case CGPathItem::Type::CurveTo: { + nativePath.cubicTo(item.points[0].x, item.points[0].y, item.points[1].x, item.points[1].y, item.points[2].x, item.points[2].y); + break; + } + case CGPathItem::Type::Close: { + nativePath.close(); + break; + } + } + }); +} + +} + +CGContextSkiaImpl::CGContextSkiaImpl(int width, int height) : +_width(width), _height(height) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + SkGraphics::Init(); + }); + + //static sk_sp sharedSurface = SkSurface::MakeRasterN32Premul(width, height); + //_surface = sharedSurface; + + _surface = SkSurface::MakeRasterN32Premul(width, height); + + _canvas = _surface->getCanvas(); + _canvas->resetMatrix(); + _canvas->clear(SkColorSetARGB(0, 0, 0, 0)); +} + +CGContextSkiaImpl::~CGContextSkiaImpl() { +} + +int CGContextSkiaImpl::width() const { + return _width; +} + +int CGContextSkiaImpl::height() const { + return _height; +} + +std::shared_ptr CGContextSkiaImpl::makeLayer(int width, int height) { + return std::make_shared(width, height); +} + +void CGContextSkiaImpl::saveState() { + _canvas->save(); +} + +void CGContextSkiaImpl::restoreState() { + _canvas->restore(); +} + +void CGContextSkiaImpl::fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) { + SkPaint paint; + paint.setColor(skColor(color)); + paint.setAlphaf(_alpha); + paint.setAntiAlias(true); + paint.setBlendMode(_blendMode); + + SkPath nativePath; + skPath(path, nativePath); + nativePath.setFillType(fillRule == FillRule::EvenOdd ? SkPathFillType::kEvenOdd : SkPathFillType::kWinding); + + _canvas->drawPath(nativePath, paint); +} + +void CGContextSkiaImpl::linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setBlendMode(_blendMode); + paint.setDither(false); + paint.setStyle(SkPaint::Style::kFill_Style); + + SkPoint linearPoints[2] = { + SkPoint::Make(start.x, start.y), + SkPoint::Make(end.x, end.y) + }; + + std::vector colors; + for (const auto &color : gradient.colors()) { + colors.push_back(skColor(Color(color.r, color.g, color.b, color.a * _alpha))); + } + + std::vector locations; + for (auto location : gradient.locations()) { + locations.push_back(location); + } + + paint.setShader(SkGradientShader::MakeLinear(linearPoints, colors.data(), locations.data(), (int)colors.size(), SkTileMode::kMirror)); + + SkPath nativePath; + skPath(path, nativePath); + nativePath.setFillType(fillRule == FillRule::EvenOdd ? SkPathFillType::kEvenOdd : SkPathFillType::kWinding); + + _canvas->drawPath(nativePath, paint); +} + +void CGContextSkiaImpl::radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setBlendMode(_blendMode); + paint.setDither(false); + paint.setStyle(SkPaint::Style::kFill_Style); + + std::vector colors; + for (const auto &color : gradient.colors()) { + colors.push_back(skColor(Color(color.r, color.g, color.b, color.a * _alpha))); + } + + std::vector locations; + for (auto location : gradient.locations()) { + locations.push_back(location); + } + + paint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(startCenter.x, startCenter.y), endRadius, colors.data(), locations.data(), (int)colors.size(), SkTileMode::kMirror)); + + SkPath nativePath; + skPath(path, nativePath); + nativePath.setFillType(fillRule == FillRule::EvenOdd ? SkPathFillType::kEvenOdd : SkPathFillType::kWinding); + + _canvas->drawPath(nativePath, paint); +} + +void CGContextSkiaImpl::strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setBlendMode(_blendMode); + paint.setColor(skColor(color)); + paint.setAlphaf(_alpha); + paint.setStyle(SkPaint::Style::kStroke_Style); + + paint.setStrokeWidth(lineWidth); + switch (lineJoin) { + case LineJoin::Miter: { + paint.setStrokeJoin(SkPaint::Join::kMiter_Join); + break; + } + case LineJoin::Round: { + paint.setStrokeJoin(SkPaint::Join::kRound_Join); + break; + } + case LineJoin::Bevel: { + paint.setStrokeJoin(SkPaint::Join::kBevel_Join); + break; + } + default: { + paint.setStrokeJoin(SkPaint::Join::kBevel_Join); + break; + } + } + + switch (lineCap) { + case LineCap::Butt: { + paint.setStrokeCap(SkPaint::Cap::kButt_Cap); + break; + } + case LineCap::Round: { + paint.setStrokeCap(SkPaint::Cap::kRound_Cap); + break; + } + case LineCap::Square: { + paint.setStrokeCap(SkPaint::Cap::kSquare_Cap); + break; + } + default: { + paint.setStrokeCap(SkPaint::Cap::kSquare_Cap); + break; + } + } + + if (!dashPattern.empty()) { + std::vector intervals; + intervals.reserve(dashPattern.size()); + for (auto value : dashPattern) { + intervals.push_back(value); + } + paint.setPathEffect(SkDashPathEffect::Make(intervals.data(), (int)intervals.size(), dashPhase)); + } + + SkPath nativePath; + skPath(path, nativePath); + + _canvas->drawPath(nativePath, paint); +} + +void CGContextSkiaImpl::fill(CGRect const &rect, Color const &fillColor) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(skColor(fillColor)); + paint.setAlphaf(_alpha); + paint.setBlendMode(_blendMode); + + _canvas->drawRect(SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), paint); +} + +void CGContextSkiaImpl::setBlendMode(CGBlendMode blendMode) { + switch (blendMode) { + case CGBlendMode::Normal: { + _blendMode = SkBlendMode::kSrcOver; + break; + } + case CGBlendMode::DestinationIn: { + _blendMode = SkBlendMode::kDstIn; + break; + } + case CGBlendMode::DestinationOut: { + _blendMode = SkBlendMode::kDstOut; + break; + } + default: { + _blendMode = SkBlendMode::kSrcOver; + break; + } + } +} + +void CGContextSkiaImpl::setAlpha(double alpha) { + _alpha = alpha; +} + +void CGContextSkiaImpl::concatenate(CATransform3D const &transform) { + _canvas->concat(SkM44( + transform.m11, transform.m21, transform.m31, transform.m41, + transform.m12, transform.m22, transform.m32, transform.m42, + transform.m13, transform.m23, transform.m33, transform.m43, + transform.m14, transform.m24, transform.m34, transform.m44 + )); +} + +void CGContextSkiaImpl::draw(std::shared_ptr const &other, CGRect const &rect) { + CGContextSkiaImpl *impl = (CGContextSkiaImpl *)other.get(); + auto image = impl->surface()->makeImageSnapshot(); + SkPaint paint; + paint.setBlendMode(_blendMode); + paint.setAlphaf(_alpha); + _canvas->drawImageRect(image.get(), SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), SkSamplingOptions(SkFilterMode::kLinear), &paint); +} + +sk_sp CGContextSkiaImpl::surface() const { + return _surface; +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextTVGImpl.h b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextTVGImpl.h new file mode 100644 index 00000000000..c6a50cb381d --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextTVGImpl.h @@ -0,0 +1,65 @@ +#ifndef CGContextTVGImpl_h +#define CGContextTVGImpl_h + +#include "Lottie/Public/Primitives/CALayer.hpp" + +#include "thorvg.h" + +namespace lottie { + +class CGContextTVGImpl: public CGContext { +public: + CGContextTVGImpl(int width, int height); + virtual ~CGContextTVGImpl(); + + virtual int width() const override; + virtual int height() const override; + + virtual std::shared_ptr makeLayer(int width, int height) override; + + virtual void saveState() override; + virtual void restoreState() override; + + virtual void fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) override; + virtual void linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) override; + virtual void radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) override; + virtual void strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) override; + virtual void linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) override; + virtual void radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) override; + virtual void fill(CGRect const &rect, Color const &fillColor) override; + + virtual void setBlendMode(CGBlendMode blendMode) override; + + virtual void setAlpha(double alpha) override; + + virtual void concatenate(CATransform3D const &transform) override; + + virtual void draw(std::shared_ptr const &other, CGRect const &rect) override; + + uint32_t *backingData() { + return _backingData; + } + + int bytesPerRow() const { + return _bytesPerRow; + } + + void flush(); + +private: + int _width = 0; + int _height = 0; + std::unique_ptr _canvas; + + //SkBlendMode _blendMode = SkBlendMode::kSrcOver; + double _alpha = 1.0; + CATransform3D _transform; + std::vector _stateStack; + int _bytesPerRow = 0; + uint32_t *_backingData = nullptr; + int _statsNumStrokes = 0; +}; + +} + +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextTVGImpl.mm b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextTVGImpl.mm new file mode 100644 index 00000000000..5db9f3578ed --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGContextTVGImpl.mm @@ -0,0 +1,293 @@ +#include "Lottie/Public/Primitives/CGContextTVGImpl.h" + +#include "Lottie/Public/Primitives/CALayerCocoa.h" +#include "Lottie/Public/Primitives/VectorsCocoa.h" + +namespace lottie { + +namespace { + +void tvgPath(std::shared_ptr const &path, tvg::Shape *shape) { + path->enumerate([shape](CGPathItem const &item) { + switch (item.type) { + case CGPathItem::Type::MoveTo: { + shape->moveTo(item.points[0].x, item.points[0].y); + break; + } + case CGPathItem::Type::LineTo: { + shape->lineTo(item.points[0].x, item.points[0].y); + break; + } + case CGPathItem::Type::CurveTo: { + shape->cubicTo(item.points[0].x, item.points[0].y, item.points[1].x, item.points[1].y, item.points[2].x, item.points[2].y); + break; + } + case CGPathItem::Type::Close: { + shape->close(); + break; + } + } + }); +} + +tvg::Matrix tvgTransform(CATransform3D const &transform) { + CGAffineTransform affineTransform = CATransform3DGetAffineTransform(nativeTransform(transform)); + tvg::Matrix result; + result.e11 = affineTransform.a; + result.e21 = affineTransform.b; + result.e31 = 0.0f; + result.e12 = affineTransform.c; + result.e22 = affineTransform.d; + result.e32 = 0.0f; + result.e13 = affineTransform.tx; + result.e23 = affineTransform.ty; + result.e33 = 1.0f; + return result; +} + +} + +CGContextTVGImpl::CGContextTVGImpl(int width, int height) : +_width(width), _height(height), _transform(CATransform3D::identity()) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + tvg::Initializer::init(tvg::CanvasEngine::Sw, 0); + }); + + _canvas = tvg::SwCanvas::gen(); + + _bytesPerRow = width * 4; + + static uint32_t *sharedBackingData = (uint32_t *)malloc(_bytesPerRow * height); + _backingData = sharedBackingData; + + _canvas->target(_backingData, _bytesPerRow / 4, width, height, tvg::SwCanvas::ARGB8888); +} + +CGContextTVGImpl::~CGContextTVGImpl() { +} + +int CGContextTVGImpl::width() const { + return _width; +} + +int CGContextTVGImpl::height() const { + return _height; +} + +std::shared_ptr CGContextTVGImpl::makeLayer(int width, int height) { + return std::make_shared(width, height); +} + +void CGContextTVGImpl::saveState() { + _stateStack.push_back(_transform); +} + +void CGContextTVGImpl::restoreState() { + if (_stateStack.empty()) { + assert(false); + return; + } + _transform = _stateStack[_stateStack.size() - 1]; + _stateStack.pop_back(); +} + +void CGContextTVGImpl::fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) { + auto shape = tvg::Shape::gen(); + tvgPath(path, shape.get()); + + shape->transform(tvgTransform(_transform)); + + shape->fill((int)(color.r * 255.0), (int)(color.g * 255.0), (int)(color.b * 255.0), (int)(color.a * _alpha * 255.0)); + shape->fill(fillRule == FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); + + _canvas->push(std::move(shape)); +} + +void CGContextTVGImpl::linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) { + auto shape = tvg::Shape::gen(); + tvgPath(path, shape.get()); + + shape->transform(tvgTransform(_transform)); + + auto fill = tvg::LinearGradient::gen(); + fill->linear(start.x, start.y, end.x, end.y); + + std::vector colors; + for (size_t i = 0; i < gradient.colors().size(); i++) { + const auto &color = gradient.colors()[i]; + tvg::Fill::ColorStop colorStop; + colorStop.offset = gradient.locations()[i]; + colorStop.r = (int)(color.r * 255.0); + colorStop.g = (int)(color.g * 255.0); + colorStop.b = (int)(color.b * 255.0); + colorStop.a = (int)(color.a * _alpha * 255.0); + colors.push_back(colorStop); + } + fill->colorStops(colors.data(), (uint32_t)colors.size()); + shape->fill(std::move(fill)); + + shape->fill(fillRule == FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); + + _canvas->push(std::move(shape)); +} + +void CGContextTVGImpl::radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) { + auto shape = tvg::Shape::gen(); + tvgPath(path, shape.get()); + + shape->transform(tvgTransform(_transform)); + + auto fill = tvg::RadialGradient::gen(); + fill->radial(startCenter.x, startCenter.y, endRadius); + + std::vector colors; + for (size_t i = 0; i < gradient.colors().size(); i++) { + const auto &color = gradient.colors()[i]; + tvg::Fill::ColorStop colorStop; + colorStop.offset = gradient.locations()[i]; + colorStop.r = (int)(color.r * 255.0); + colorStop.g = (int)(color.g * 255.0); + colorStop.b = (int)(color.b * 255.0); + colorStop.a = (int)(color.a * _alpha * 255.0); + colors.push_back(colorStop); + } + fill->colorStops(colors.data(), (uint32_t)colors.size()); + shape->fill(std::move(fill)); + + shape->fill(fillRule == FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); + + _canvas->push(std::move(shape)); +} + +void CGContextTVGImpl::strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) { + auto shape = tvg::Shape::gen(); + tvgPath(path, shape.get()); + + shape->transform(tvgTransform(_transform)); + + shape->stroke((int)(color.r * 255.0), (int)(color.g * 255.0), (int)(color.b * 255.0), (int)(color.a * _alpha * 255.0)); + shape->stroke(lineWidth); + + switch (lineJoin) { + case LineJoin::Miter: { + shape->stroke(tvg::StrokeJoin::Miter); + break; + } + case LineJoin::Round: { + shape->stroke(tvg::StrokeJoin::Round); + break; + } + case LineJoin::Bevel: { + shape->stroke(tvg::StrokeJoin::Bevel); + break; + } + default: { + shape->stroke(tvg::StrokeJoin::Bevel); + break; + } + } + + switch (lineCap) { + case LineCap::Butt: { + shape->stroke(tvg::StrokeCap::Butt); + break; + } + case LineCap::Round: { + shape->stroke(tvg::StrokeCap::Round); + break; + } + case LineCap::Square: { + shape->stroke(tvg::StrokeCap::Square); + break; + } + default: { + shape->stroke(tvg::StrokeCap::Square); + break; + } + } + + if (!dashPattern.empty()) { + std::vector intervals; + intervals.reserve(dashPattern.size()); + for (auto value : dashPattern) { + intervals.push_back(value); + } + shape->stroke(intervals.data(), (uint32_t)intervals.size()); + //TODO:phase + } + + _canvas->push(std::move(shape)); +} + +void CGContextTVGImpl::linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) { + assert(false); +} + +void CGContextTVGImpl::radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) { + assert(false); +} + +void CGContextTVGImpl::fill(CGRect const &rect, Color const &fillColor) { + auto shape = tvg::Shape::gen(); + shape->appendRect(rect.x, rect.y, rect.width, rect.height, 0.0f, 0.0f); + + shape->transform(tvgTransform(_transform)); + + shape->fill((int)(fillColor.r * 255.0), (int)(fillColor.g * 255.0), (int)(fillColor.b * 255.0), (int)(fillColor.a * _alpha * 255.0)); + + _canvas->push(std::move(shape)); +} + +void CGContextTVGImpl::setBlendMode(CGBlendMode blendMode) { + /*switch (blendMode) { + case CGBlendMode::Normal: { + _blendMode = SkBlendMode::kSrcOver; + break; + } + case CGBlendMode::DestinationIn: { + _blendMode = SkBlendMode::kDstIn; + break; + } + case CGBlendMode::DestinationOut: { + _blendMode = SkBlendMode::kDstOut; + break; + } + default: { + _blendMode = SkBlendMode::kSrcOver; + break; + } + }*/ +} + +void CGContextTVGImpl::setAlpha(double alpha) { + _alpha = alpha; +} + +void CGContextTVGImpl::concatenate(CATransform3D const &transform) { + _transform = transform * _transform; + /*_canvas->concat(SkM44( + transform.m11, transform.m21, transform.m31, transform.m41, + transform.m12, transform.m22, transform.m32, transform.m42, + transform.m13, transform.m23, transform.m33, transform.m43, + transform.m14, transform.m24, transform.m34, transform.m44 + ));*/ +} + +void CGContextTVGImpl::draw(std::shared_ptr const &other, CGRect const &rect) { + /*CGContextTVGImpl *impl = (CGContextTVGImpl *)other.get(); + auto image = impl->surface()->makeImageSnapshot(); + SkPaint paint; + paint.setBlendMode(_blendMode); + paint.setAlphaf(_alpha); + _canvas->drawImageRect(image.get(), SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), SkSamplingOptions(SkFilterMode::kLinear), &paint);*/ +} + +void CGContextTVGImpl::flush() { + _canvas->draw(); + _canvas->sync(); + + _statsNumStrokes = 0; +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPath.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPath.cpp new file mode 100644 index 00000000000..7b352cf13b3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPath.cpp @@ -0,0 +1,194 @@ +#include "CGPath.hpp" + +#include + +namespace lottie { + +namespace { + +void addPointToBoundingRect(bool *isFirst, CGRect *rect, Vector2D const *point) { + if (*isFirst) { + *isFirst = false; + + rect->x = point->x; + rect->y = point->y; + rect->width = 0.0; + rect->height = 0.0; + + return; + } + if (point->x > rect->x + rect->width) { + rect->width = point->x - rect->x; + } + if (point->y > rect->y + rect->height) { + rect->height = point->y - rect->y; + } + if (point->x < rect->x) { + rect->width += rect->x - point->x; + rect->x = point->x; + } + if (point->y < rect->y) { + rect->height += rect->y - point->y; + rect->y = point->y; + } +} + +} + +Vector2D transformVector(Vector2D const &v, CATransform3D const &m) { + return Vector2D( + m.m11 * v.x + m.m21 * v.y + m.m41 * 1.0, + m.m12 * v.x + m.m22 * v.y + m.m42 * 1.0 + ); +} + +class CGPathImpl: public CGPath { +public: + CGPathImpl(); + virtual ~CGPathImpl(); + + virtual CGRect boundingBox() const override; + + virtual bool empty() const override; + + virtual std::shared_ptr copyUsingTransform(CATransform3D const &transform) const override; + + virtual void addLineTo(Vector2D const &point) override; + virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) override; + virtual void moveTo(Vector2D const &point) override; + virtual void closeSubpath() override; + virtual void addRect(CGRect const &rect) override; + virtual void addPath(std::shared_ptr const &path) override; + virtual bool isEqual(CGPath *other) const override; + virtual void enumerate(std::function) override; + +private: + std::vector _items; +}; + +CGPathImpl::CGPathImpl() { +} + +CGPathImpl::~CGPathImpl() { +} + +CGRect CGPathImpl::boundingBox() const { + bool isFirst = true; + CGRect result(0.0, 0.0, 0.0, 0.0); + + for (const auto &item : _items) { + switch (item.type) { + case CGPathItem::Type::MoveTo: { + addPointToBoundingRect(&isFirst, &result, &item.points[0]); + break; + } + case CGPathItem::Type::LineTo: { + addPointToBoundingRect(&isFirst, &result, &item.points[0]); + break; + } + case CGPathItem::Type::CurveTo: { + addPointToBoundingRect(&isFirst, &result, &item.points[0]); + addPointToBoundingRect(&isFirst, &result, &item.points[1]); + addPointToBoundingRect(&isFirst, &result, &item.points[2]); + break; + } + case CGPathItem::Type::Close: { + break; + } + default: { + break; + } + } + } + + return result; +} + +bool CGPathImpl::empty() const { + return _items.empty(); +} + +std::shared_ptr CGPathImpl::copyUsingTransform(CATransform3D const &transform) const { + auto result = std::make_shared(); + + if (transform == CATransform3D::identity()) { + result->_items = _items; + return result; + } + + result->_items.reserve(_items.capacity()); + for (auto &sourceItem : _items) { + CGPathItem &item = result->_items.emplace_back(sourceItem.type); + item.points[0] = transformVector(sourceItem.points[0], transform); + item.points[1] = transformVector(sourceItem.points[1], transform); + item.points[2] = transformVector(sourceItem.points[2], transform); + } + + return result; +} + +void CGPathImpl::addLineTo(Vector2D const &point) { + CGPathItem &item = _items.emplace_back(CGPathItem::Type::LineTo); + item.points[0] = point; +} + +void CGPathImpl::addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) { + CGPathItem &item = _items.emplace_back(CGPathItem::Type::CurveTo); + item.points[0] = control1; + item.points[1] = control2; + item.points[2] = point; +} + +void CGPathImpl::moveTo(Vector2D const &point) { + CGPathItem &item = _items.emplace_back(CGPathItem::Type::MoveTo); + item.points[0] = point; +} + +void CGPathImpl::closeSubpath() { + _items.emplace_back(CGPathItem::Type::Close); +} + +void CGPathImpl::addRect(CGRect const &rect) { + assert(false); + //CGPathAddRect(_path, nil, ::CGRectMake(rect.x, rect.y, rect.width, rect.height)); +} + +void CGPathImpl::addPath(std::shared_ptr const &path) { + if (_items.size() == 0) { + _items = std::static_pointer_cast(path)->_items; + } else { + size_t totalItemCount = _items.size() + std::static_pointer_cast(path)->_items.size(); + if (_items.capacity() < totalItemCount) { + _items.reserve(totalItemCount); + } + for (const auto &item : std::static_pointer_cast(path)->_items) { + _items.push_back(item); + } + } +} + +bool CGPathImpl::isEqual(CGPath *other) const { + if (_items.size() != ((CGPathImpl *)other)->_items.size()) { + return false; + } + + for (size_t i = 0; i < _items.size(); i++) { + if (_items[i] != ((CGPathImpl *)other)->_items[i]) { + return false; + } + } + + return true; +} + +void CGPathImpl::enumerate(std::function f) { + for (const auto &item : _items) { + f(item); + } +} + +std::shared_ptr CGPath::makePath() { + return std::static_pointer_cast(std::make_shared()); +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPath.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPath.hpp new file mode 100644 index 00000000000..76462832a1b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPath.hpp @@ -0,0 +1,75 @@ +#ifndef CGPath_hpp +#define CGPath_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" + +#include + +namespace lottie { + +struct CGPathItem { + enum class Type { + MoveTo, + LineTo, + CurveTo, + Close + }; + + Type type; + Vector2D points[3] = { Vector2D(0.0, 0.0), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0) }; + + explicit CGPathItem(Type type_) : + type(type_) { + } + + bool operator==(const CGPathItem &rhs) const { + if (type != rhs.type) { + return false; + } + if (points[0] != rhs.points[0]) { + return false; + } + if (points[1] != rhs.points[1]) { + return false; + } + if (points[2] != rhs.points[2]) { + return false; + } + + return true; + } + + bool operator!=(const CGPathItem &rhs) const { + return !(*this == rhs); + } +}; + +class CGPath { +public: + static std::shared_ptr makePath(); + + virtual ~CGPath() = default; + + virtual CGRect boundingBox() const = 0; + + virtual bool empty() const = 0; + + virtual std::shared_ptr copyUsingTransform(CATransform3D const &transform) const = 0; + + virtual void addLineTo(Vector2D const &point) = 0; + virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) = 0; + virtual void moveTo(Vector2D const &point) = 0; + virtual void closeSubpath() = 0; + virtual void addRect(CGRect const &rect) = 0; + virtual void addPath(std::shared_ptr const &path) = 0; + + virtual void enumerate(std::function) = 0; + + virtual bool isEqual(CGPath *other) const = 0; +}; + +Vector2D transformVector(Vector2D const &v, CATransform3D const &m); + +} + +#endif /* CGPath_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPath.mm b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPath.mm new file mode 100644 index 00000000000..a7798daa9a2 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPath.mm @@ -0,0 +1,240 @@ +#include "CGPath.hpp" +#include "Lottie/Public/Primitives/CGPathCocoa.h" + +#import + +namespace { + +void addPointToBoundingRect(bool *isFirst, CGRect *rect, CGPoint *point) { + if (*isFirst) { + *isFirst = false; + + rect->origin.x = point->x; + rect->origin.y = point->y; + rect->size.width = 0.0; + rect->size.height = 0.0; + + return; + } + if (point->x > rect->origin.x + rect->size.width) { + rect->size.width = point->x - rect->origin.x; + } + if (point->y > rect->origin.y + rect->size.height) { + rect->size.height = point->y - rect->origin.y; + } + if (point->x < rect->origin.x) { + rect->size.width += rect->origin.x - point->x; + rect->origin.x = point->x; + } + if (point->y < rect->origin.y) { + rect->size.height += rect->origin.y - point->y; + rect->origin.y = point->y; + } +} + +} + +CGRect calculatePathBoundingBox(CGPathRef path) { + __block CGRect result = CGRectMake(0.0, 0.0, 0.0, 0.0); + __block bool isFirst = true; + + CGPathApplyWithBlock(path, ^(const CGPathElement * _Nonnull element) { + switch (element->type) { + case kCGPathElementMoveToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + break; + } + case kCGPathElementAddLineToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + break; + } + case kCGPathElementAddCurveToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + addPointToBoundingRect(&isFirst, &result, &element->points[1]); + addPointToBoundingRect(&isFirst, &result, &element->points[2]); + break; + } + case kCGPathElementAddQuadCurveToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + addPointToBoundingRect(&isFirst, &result, &element->points[1]); + break; + } + case kCGPathElementCloseSubpath: { + break; + } + } + }); + + return result; +} + +namespace lottie { + +CGPathCocoaImpl::CGPathCocoaImpl() { + _path = CGPathCreateMutable(); +} + +CGPathCocoaImpl::CGPathCocoaImpl(CGMutablePathRef path) { + CFRetain(path); + _path = path; +} + +CGPathCocoaImpl::~CGPathCocoaImpl() { + CGPathRelease(_path); +} + +CGRect CGPathCocoaImpl::boundingBox() const { + auto rect = calculatePathBoundingBox(_path); + return CGRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); +} + +bool CGPathCocoaImpl::empty() const { + return CGPathIsEmpty(_path); +} + +std::shared_ptr CGPathCocoaImpl::copyUsingTransform(CATransform3D const &transform) const { + ::CATransform3D nativeTransform; + nativeTransform.m11 = transform.m11; + nativeTransform.m12 = transform.m12; + nativeTransform.m13 = transform.m13; + nativeTransform.m14 = transform.m14; + + nativeTransform.m21 = transform.m21; + nativeTransform.m22 = transform.m22; + nativeTransform.m23 = transform.m23; + nativeTransform.m24 = transform.m24; + + nativeTransform.m31 = transform.m31; + nativeTransform.m32 = transform.m32; + nativeTransform.m33 = transform.m33; + nativeTransform.m34 = transform.m34; + + nativeTransform.m41 = transform.m41; + nativeTransform.m42 = transform.m42; + nativeTransform.m43 = transform.m43; + nativeTransform.m44 = transform.m44; + + auto affineTransform = CATransform3DGetAffineTransform(nativeTransform); + + CGPathRef resultPath = CGPathCreateCopyByTransformingPath(_path, &affineTransform); + if (resultPath == nil) { + return nullptr; + } + + CGMutablePathRef resultMutablePath = CGPathCreateMutableCopy(resultPath); + CGPathRelease(resultPath); + auto result = std::make_shared(resultMutablePath); + CGPathRelease(resultMutablePath); + + return result; +} + +void CGPathCocoaImpl::addLineTo(Vector2D const &point) { + CGPathAddLineToPoint(_path, nil, point.x, point.y); +} + +void CGPathCocoaImpl::addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) { + CGPathAddCurveToPoint(_path, nil, control1.x, control1.y, control2.x, control2.y, point.x, point.y); +} + +void CGPathCocoaImpl::moveTo(Vector2D const &point) { + CGPathMoveToPoint(_path, nil, point.x, point.y); +} + +void CGPathCocoaImpl::closeSubpath() { + CGPathCloseSubpath(_path); +} + +void CGPathCocoaImpl::addRect(CGRect const &rect) { + CGPathAddRect(_path, nil, ::CGRectMake(rect.x, rect.y, rect.width, rect.height)); +} + +void CGPathCocoaImpl::addPath(std::shared_ptr const &path) { + if (CGPathIsEmpty(_path)) { + _path = CGPathCreateMutableCopy(std::static_pointer_cast(path)->_path); + } else { + CGPathAddPath(_path, nil, std::static_pointer_cast(path)->_path); + } +} + +CGPathRef CGPathCocoaImpl::nativePath() const { + return _path; +} + +bool CGPathCocoaImpl::isEqual(CGPath *other) const { + CGPathCocoaImpl *otherImpl = (CGPathCocoaImpl *)other; + return CGPathEqualToPath(_path, otherImpl->_path); +} + +void CGPathCocoaImpl::enumerate(std::function f) { + CGPathApplyWithBlock(_path, ^(const CGPathElement * _Nonnull element) { + CGPathItem item(CGPathItem::Type::MoveTo); + + switch (element->type) { + case kCGPathElementMoveToPoint: { + item.type = CGPathItem::Type::MoveTo; + item.points[0] = Vector2D(element->points[0].x, element->points[0].y); + f(item); + break; + } + case kCGPathElementAddLineToPoint: { + item.type = CGPathItem::Type::LineTo; + item.points[0] = Vector2D(element->points[0].x, element->points[0].y); + f(item); + break; + } + case kCGPathElementAddCurveToPoint: { + item.type = CGPathItem::Type::CurveTo; + item.points[0] = Vector2D(element->points[0].x, element->points[0].y); + item.points[1] = Vector2D(element->points[1].x, element->points[1].y); + item.points[2] = Vector2D(element->points[2].x, element->points[2].y); + f(item); + break; + } + case kCGPathElementAddQuadCurveToPoint: { + break; + } + case kCGPathElementCloseSubpath: { + item.type = CGPathItem::Type::Close; + f(item); + break; + } + } + }); +} + +void CGPathCocoaImpl::withNativePath(std::shared_ptr const &path, std::function f) { + CGMutablePathRef result = CGPathCreateMutable(); + + path->enumerate([result](CGPathItem const &element) { + switch (element.type) { + case CGPathItem::Type::MoveTo: { + CGPathMoveToPoint(result, nullptr, element.points[0].x, element.points[0].y); + break; + } + case CGPathItem::Type::LineTo: { + CGPathAddLineToPoint(result, nullptr, element.points[0].x, element.points[0].y); + break; + } + case CGPathItem::Type::CurveTo: { + CGPathAddCurveToPoint(result, nullptr, element.points[0].x, element.points[0].y, element.points[1].x, element.points[1].y, element.points[2].x, element.points[2].y); + break; + } + case CGPathItem::Type::Close: { + CGPathCloseSubpath(result); + break; + } + default: + break; + } + }); + + f(result); + CFRelease(result); +} + +/*std::shared_ptr CGPath::makePath() { + return std::static_pointer_cast(std::make_shared()); +}*/ + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPathCocoa.h b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPathCocoa.h new file mode 100644 index 00000000000..744ef5f2937 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CGPathCocoa.h @@ -0,0 +1,42 @@ +#ifndef CGPathCocoa_h +#define CGPathCocoa_h + +#include "Lottie/Public/Primitives/CGPath.hpp" + +#include + +CGRect calculatePathBoundingBox(CGPathRef path); + +namespace lottie { + +class CGPathCocoaImpl: public CGPath { +public: + CGPathCocoaImpl(); + explicit CGPathCocoaImpl(CGMutablePathRef path); + virtual ~CGPathCocoaImpl(); + + virtual CGRect boundingBox() const override; + + virtual bool empty() const override; + + virtual std::shared_ptr copyUsingTransform(CATransform3D const &transform) const override; + + virtual void addLineTo(Vector2D const &point) override; + virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) override; + virtual void moveTo(Vector2D const &point) override; + virtual void closeSubpath() override; + virtual void addRect(CGRect const &rect) override; + virtual void addPath(std::shared_ptr const &path) override; + virtual CGPathRef nativePath() const; + virtual bool isEqual(CGPath *other) const override; + virtual void enumerate(std::function) override; + + static void withNativePath(std::shared_ptr const &path, std::function f); + +private: + ::CGMutablePathRef _path = nil; +}; + +} + +#endif /* CGPathCocoa_h */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CTFont.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CTFont.cpp new file mode 100644 index 00000000000..24022559bfe --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CTFont.cpp @@ -0,0 +1,5 @@ +#include "CTFont.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CTFont.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CTFont.hpp new file mode 100644 index 00000000000..351d4bfc224 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/CTFont.hpp @@ -0,0 +1,12 @@ +#ifndef CTFont_hpp +#define CTFont_hpp + +namespace lottie { + +class CTFont { + +}; + +} + +#endif /* CTFont_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Color.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Color.cpp new file mode 100644 index 00000000000..c1ecb86f457 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Color.cpp @@ -0,0 +1,29 @@ +#include "Color.hpp" + +#include + +namespace lottie { + +Color Color::fromString(std::string const &string) { + if (string.empty()) { + return Color(0.0, 0.0, 0.0, 0.0); + } + + std::string workString = string; + if (workString[0] == '#') { + workString.erase(workString.begin()); + } + + std::istringstream converter(workString); + uint32_t rgbValue; + converter >> std::hex >> rgbValue; + + return Color( + ((double)((rgbValue & 0xFF0000) >> 16)) / 255.0, + ((double)((rgbValue & 0x00FF00) >> 8)) / 255.0, + ((double)(rgbValue & 0x0000FF)) / 255.0, + 1.0 + ); +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Color.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Color.hpp new file mode 100644 index 00000000000..8fdbdea8aed --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Color.hpp @@ -0,0 +1,144 @@ +#ifndef Color_hpp +#define Color_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +enum class ColorFormatDenominator { + One, + OneHundred, + TwoFiftyFive +}; + +struct Color { + double r; + double g; + double b; + double a; + + bool operator==(Color const &rhs) const { + if (r != rhs.r) { + return false; + } + if (g != rhs.g) { + return false; + } + if (b != rhs.b) { + return false; + } + if (a != rhs.a) { + return false; + } + return true; + } + + bool operator!=(Color const &rhs) const { + return !(*this == rhs); + } + + explicit Color(double r_, double g_, double b_, double a_, ColorFormatDenominator denominator = ColorFormatDenominator::One) { + double denominatorValue = 1.0; + switch (denominator) { + case ColorFormatDenominator::One: { + denominatorValue = 1.0; + break; + } + case ColorFormatDenominator::OneHundred: { + denominatorValue = 100.0; + break; + } + case ColorFormatDenominator::TwoFiftyFive: { + denominatorValue = 255.0; + break; + } + } + + r = r_ / denominatorValue; + g = g_ / denominatorValue; + b = b_ / denominatorValue; + a = a_ / denominatorValue; + } + + explicit Color(json11::Json const &jsonAny) noexcept(false) : + r(0.0), g(0.0), b(0.0), a(0.0) { + if (!jsonAny.is_array()) { + throw LottieParsingException(); + } + + for (const auto &item : jsonAny.array_items()) { + if (!item.is_number()) { + throw LottieParsingException(); + } + } + + size_t index = 0; + + double r1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + r1 = jsonAny.array_items()[index].number_value(); + index++; + } + + double g1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + g1 = jsonAny.array_items()[index].number_value(); + index++; + } + + double b1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + b1 = jsonAny.array_items()[index].number_value(); + index++; + } + + double a1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + a1 = jsonAny.array_items()[index].number_value(); + index++; + } + + if (r1 > 1.0 && r1 > 1.0 && b1 > 1.0 && a1 > 1.0) { + r1 = r1 / 255.0; + g1 = g1 / 255.0; + b1 = b1 / 255.0; + a1 = a1 / 255.0; + } + + r = r1; + g = g1; + b = b1; + a = a1; + } + + json11::Json toJson() const { + json11::Json::array result; + + result.push_back(json11::Json(r)); + result.push_back(json11::Json(g)); + result.push_back(json11::Json(b)); + result.push_back(json11::Json(a)); + + return result; + } + + static Color fromString(std::string const &string); +}; + +} + +#endif /* Color_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Color.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Color.swift new file mode 100644 index 00000000000..948b77e911e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Color.swift @@ -0,0 +1,45 @@ +// +// Color.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation + +// MARK: - ColorFormatDenominator + +public enum ColorFormatDenominator: Hashable { + case One + case OneHundred + case TwoFiftyFive + + var value: Double { + switch self { + case .One: + return 1.0 + case .OneHundred: + return 100.0 + case .TwoFiftyFive: + return 255.0 + } + } +} + +// MARK: - Color + +public struct Color: Hashable { + + public var r: Double + public var g: Double + public var b: Double + public var a: Double + + public init(r: Double, g: Double, b: Double, a: Double, denominator: ColorFormatDenominator = .One) { + self.r = r / denominator.value + self.g = g / denominator.value + self.b = b / denominator.value + self.a = a / denominator.value + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/DashPattern.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/DashPattern.cpp new file mode 100644 index 00000000000..33426de3384 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/DashPattern.cpp @@ -0,0 +1,5 @@ +#include "DashPattern.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/DashPattern.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/DashPattern.hpp new file mode 100644 index 00000000000..d22ee1e4d85 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/DashPattern.hpp @@ -0,0 +1,18 @@ +#ifndef DashPattern_hpp +#define DashPattern_hpp + +#include + +namespace lottie { + +struct DashPattern { + DashPattern(std::vector &&values_) : + values(std::move(values_)) { + } + + std::vector values; +}; + +} + +#endif /* DashPattern_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/DrawingAttributes.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/DrawingAttributes.hpp new file mode 100644 index 00000000000..7b43581e65f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/DrawingAttributes.hpp @@ -0,0 +1,22 @@ +#ifndef DrawingAttributes_hpp +#define DrawingAttributes_hpp + +namespace lottie { + +enum class LineCap: int { + None = 0, + Butt = 1, + Round = 2, + Square = 3 +}; + +enum class LineJoin: int { + None = 0, + Miter = 1, + Round = 2, + Bevel = 3 +}; + +} + +#endif /* DrawingAttributes_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/GradientColorSet.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/GradientColorSet.cpp new file mode 100644 index 00000000000..3a92ce0d41b --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/GradientColorSet.cpp @@ -0,0 +1,5 @@ +#include "GradientColorSet.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/GradientColorSet.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/GradientColorSet.hpp new file mode 100644 index 00000000000..e1db01a15f9 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/GradientColorSet.hpp @@ -0,0 +1,42 @@ +#ifndef GradientColorSet_hpp +#define GradientColorSet_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +struct GradientColorSet { + GradientColorSet() { + } + + explicit GradientColorSet(json11::Json const &jsonAny) noexcept(false) { + if (!jsonAny.is_array()) { + throw LottieParsingException(); + } + + for (const auto &item : jsonAny.array_items()) { + if (!item.is_number()) { + throw LottieParsingException(); + } + colors.push_back(item.number_value()); + } + } + + json11::Json toJson() const { + json11::Json::array result; + + for (auto value : colors) { + result.push_back(value); + } + + return result; + } + + std::vector colors; +}; + +} + +#endif /* GradientColorSet_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/RenderTree.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/RenderTree.cpp new file mode 100644 index 00000000000..eec19c15969 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/RenderTree.cpp @@ -0,0 +1,140 @@ +#include "RenderTree.hpp" + +namespace lottie { + +BoundingBoxNode::BoundingBoxNode( + LayerParams const &layer_, + CGRect const &globalRect_, + CGRect const &localRect_, + CATransform3D const &globalTransform_, + bool drawsContent_, + std::shared_ptr renderableItem_, + bool isInvertedMatte_, + std::vector> const &subnodes_, + std::shared_ptr const &mask_ +) : +layer(layer_), +globalRect(globalRect_), +localRect(localRect_), +globalTransform(globalTransform_), +drawsContent(drawsContent_), +renderableItem(renderableItem_), +isInvertedMatte(isInvertedMatte_), +subnodes(subnodes_), +mask(mask_) { +} + +std::shared_ptr boundingBoxTree(std::shared_ptr const &layer, Vector2D const &globalSize, CATransform3D const &parentTransform) { + if (layer->isHidden() || layer->opacity() == 0.0f) { + return nullptr; + } + + if (layer->masksToBounds()) { + if (layer->bounds().empty()) { + return nullptr; + } + } + + auto currentTransform = parentTransform; + + currentTransform = currentTransform.translated(Vector2D(layer->position().x, layer->position().y)); + currentTransform = currentTransform.translated(Vector2D(-layer->bounds().x, -layer->bounds().y)); + currentTransform = layer->transform() * currentTransform; + + if (!currentTransform.isInvertible()) { + return nullptr; + } + + std::optional effectiveLocalBounds; + + auto renderableItem = layer->renderableItem(); + if (renderableItem) { + effectiveLocalBounds = renderableItem->boundingRect(); + } else if (layer->implementsDraw()) { + effectiveLocalBounds = layer->bounds(); + } + + bool isInvertedMatte = layer->isInvertedMatte(); + if (isInvertedMatte) { + effectiveLocalBounds = layer->bounds(); + } + + if (effectiveLocalBounds && effectiveLocalBounds->empty()) { + effectiveLocalBounds = std::nullopt; + } + + std::vector> subnodes; + std::optional subnodesGlobalRect; + + for (const auto &sublayer : layer->sublayers()) { + if (const auto subnode = boundingBoxTree(sublayer, globalSize, currentTransform)) { + subnodes.push_back(subnode); + + if (subnodesGlobalRect) { + subnodesGlobalRect = subnodesGlobalRect->unionWith(subnode->globalRect); + } else { + subnodesGlobalRect = subnode->globalRect; + } + } + } + + std::optional fuzzyGlobalRect; + + if (effectiveLocalBounds) { + CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform); + if (fuzzyGlobalRect) { + fuzzyGlobalRect = fuzzyGlobalRect->unionWith(effectiveGlobalBounds); + } else { + fuzzyGlobalRect = effectiveGlobalBounds; + } + } + + if (subnodesGlobalRect) { + if (fuzzyGlobalRect) { + fuzzyGlobalRect = fuzzyGlobalRect->unionWith(subnodesGlobalRect.value()); + } else { + fuzzyGlobalRect = subnodesGlobalRect; + } + } + + if (!fuzzyGlobalRect) { + return nullptr; + } + + CGRect globalRect( + std::floor(fuzzyGlobalRect->x), + std::floor(fuzzyGlobalRect->y), + std::ceil(fuzzyGlobalRect->width + fuzzyGlobalRect->x - floor(fuzzyGlobalRect->x)), + std::ceil(fuzzyGlobalRect->height + fuzzyGlobalRect->y - floor(fuzzyGlobalRect->y)) + ); + + if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(globalRect)) { + return nullptr; + } + + std::shared_ptr maskNode; + if (layer->mask()) { + if (const auto maskNodeValue = boundingBoxTree(layer->mask(), globalSize, currentTransform)) { + if (!maskNodeValue->globalRect.intersects(globalRect)) { + return nullptr; + } + maskNode = maskNodeValue; + } else { + return nullptr; + } + } + + return std::make_shared( + layer, + globalRect, + CGRect(0.0, 0.0, 0.0, 0.0), + currentTransform, + effectiveLocalBounds.has_value(), + renderableItem, + isInvertedMatte, + subnodes, + maskNode + ); +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/RenderTree.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/RenderTree.hpp new file mode 100644 index 00000000000..1ab374e2261 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/RenderTree.hpp @@ -0,0 +1,172 @@ +#ifndef RenderTree_hpp +#define RenderTree_hpp + +#include + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/CALayer.hpp" + +namespace lottie { + +struct BoundingBoxNode { + struct LayerParams { + CGRect _bounds; + Vector2D _position; + CATransform3D _transform; + double _opacity; + bool _masksToBounds; + bool _isHidden; + + LayerParams( + CGRect bounds_, + Vector2D position_, + CATransform3D transform_, + double opacity_, + bool masksToBounds_, + bool isHidden_ + ) : + _bounds(bounds_), + _position(position_), + _transform(transform_), + _opacity(opacity_), + _masksToBounds(masksToBounds_), + _isHidden(isHidden_) { + } + + LayerParams(std::shared_ptr const &layer) : + _bounds(layer->bounds()), + _position(layer->position()), + _transform(layer->transform()), + _opacity(layer->opacity()), + _masksToBounds(layer->masksToBounds()), + _isHidden(layer->isHidden()) { + } + + bool operator==(LayerParams const &rhs) const { + if (_bounds != rhs._bounds) { + return false; + } + if (_position != rhs._position) { + return false; + } + if (_transform != rhs._transform) { + return false; + } + if (_opacity != rhs._opacity) { + return false; + } + if (_masksToBounds != rhs._masksToBounds) { + return false; + } + if (_isHidden != rhs._isHidden) { + return false; + } + return true; + } + + bool operator!=(LayerParams const &rhs) const { + return !(*this == rhs); + } + + CGRect bounds() const { + return _bounds; + } + + Vector2D position() const { + return _position; + } + + CATransform3D transform() const { + return _transform; + } + + double opacity() const { + return _opacity; + } + + bool masksToBounds() const { + return _masksToBounds; + } + + bool isHidden() const { + return _isHidden; + } + }; + + LayerParams layer; + CGRect globalRect; + CGRect localRect; + CATransform3D globalTransform; + bool drawsContent; + std::shared_ptr renderableItem; + bool isInvertedMatte; + std::vector> subnodes; + std::shared_ptr mask; + + explicit BoundingBoxNode( + LayerParams const &layer_, + CGRect const &globalRect_, + CGRect const &localRect_, + CATransform3D const &globalTransform_, + bool drawsContent_, + std::shared_ptr renderableItem_, + bool isInvertedMatte_, + std::vector> const &subnodes_, + std::shared_ptr const &mask_ + ); + + bool operator==(BoundingBoxNode const &rhs) const { + if (layer != rhs.layer) { + return false; + } + if (globalRect != rhs.globalRect) { + return false; + } + if (localRect != rhs.localRect) { + return false; + } + if (globalTransform != rhs.globalTransform) { + return false; + } + if (drawsContent != rhs.drawsContent) { + return false; + } + if ((renderableItem == nullptr) != (rhs.renderableItem == nullptr)) { + return false; + } else if (renderableItem) { + if (!renderableItem->isEqual(rhs.renderableItem)) { + return false; + } + } + if (isInvertedMatte != rhs.isInvertedMatte) { + return false; + } + if (subnodes.size() != rhs.subnodes.size()) { + return false; + } else { + for (size_t i = 0; i < subnodes.size(); i++) { + if ((*subnodes[i].get()) != (*rhs.subnodes[i].get())) { + return false; + } + } + } + if ((mask == nullptr) != (rhs.mask == nullptr)) { + return false; + } else if (mask) { + if ((*mask.get()) != *(rhs.mask.get())) { + return false; + } + } + return true; + } + + bool operator!=(BoundingBoxNode const &rhs) const { + return !(*this == rhs); + } +}; + +std::shared_ptr boundingBoxTree(std::shared_ptr const &layer, Vector2D const &globalSize, CATransform3D const &parentTransform); + +} + +#endif /* RenderTree_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Vectors.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Vectors.hpp new file mode 100644 index 00000000000..9cdff038d12 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Vectors.hpp @@ -0,0 +1,494 @@ +#ifndef Vectors_hpp +#define Vectors_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +struct Vector1D { + enum class InternalRepresentationType { + SingleNumber, + Array + }; + + explicit Vector1D(double value_) : + value(value_) { + } + + explicit Vector1D(json11::Json const &json) noexcept(false) { + if (json.is_number()) { + value = json.number_value(); + } else if (json.is_array()) { + if (json.array_items().empty()) { + throw LottieParsingException(); + } + if (!json.array_items()[0].is_number()) { + throw LottieParsingException(); + } + value = json.array_items()[0].number_value(); + } else { + throw LottieParsingException(); + } + } + + json11::Json toJson() const { + return json11::Json(value); + } + + double value; + + double distanceTo(Vector1D const &to) const { + return abs(to.value - value); + } +}; + +double interpolate(double value, double to, double amount); + +Vector1D interpolate( + Vector1D const &from, + Vector1D const &to, + double amount +); + +struct Vector2D { + static Vector2D Zero() { + return Vector2D(0.0, 0.0); + } + + Vector2D() : + x(0.0), + y(0.0) { + } + + explicit Vector2D(double x_, double y_) : + x(x_), + y(y_) { + } + + explicit Vector2D(json11::Json const &json) noexcept(false) { + x = 0.0; + y = 0.0; + + if (json.is_array()) { + int index = 0; + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + x = json.array_items()[index].number_value(); + index++; + } + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + y = json.array_items()[index].number_value(); + index++; + } + } else if (json.is_object()) { + auto xAny = getAny(json.object_items(), "x"); + if (xAny.is_number()) { + x = xAny.number_value(); + } else if (xAny.is_array()) { + if (xAny.array_items().empty()) { + throw LottieParsingException(); + } + if (!xAny.array_items()[0].is_number()) { + throw LottieParsingException(); + } + x = xAny.array_items()[0].number_value(); + } + + auto yAny = getAny(json.object_items(), "y"); + if (yAny.is_number()) { + y = yAny.number_value(); + } else if (yAny.is_array()) { + if (yAny.array_items().empty()) { + throw LottieParsingException(); + } + if (!yAny.array_items()[0].is_number()) { + throw LottieParsingException(); + } + y = yAny.array_items()[0].number_value(); + } + } else { + throw LottieParsingException(); + } + } + + json11::Json toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("x", x)); + result.insert(std::make_pair("y", y)); + + return json11::Json(result); + } + + double x; + double y; + + Vector2D operator+(Vector2D const &rhs) const { + return Vector2D(x + rhs.x, y + rhs.y); + } + + Vector2D operator-(Vector2D const &rhs) const { + return Vector2D(x - rhs.x, y - rhs.y); + } + + Vector2D operator*(double scalar) const { + return Vector2D(x * scalar, y * scalar); + } + + bool operator==(Vector2D const &rhs) const { + return x == rhs.x && y == rhs.y; + } + + bool operator!=(Vector2D const &rhs) const { + return !(*this == rhs); + } + + bool isZero() const { + return x == 0.0 && y == 0.0; + } + + double distanceTo(Vector2D const &to) const { + auto deltaX = to.x - x; + auto deltaY = to.y - y; + return sqrt(deltaX * deltaX + deltaY * deltaY); + } + + bool colinear(Vector2D const &a, Vector2D const &b) const { + double area = x * (a.y - b.y) + a.x * (b.y - y) + b.x * (y - a.y); + double accuracy = 0.05; + if (area < accuracy && area > -accuracy) { + return true; + } + return false; + } + + Vector2D pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, double amount) const; + + Vector2D interpolate(Vector2D const &to, double amount) const; + + Vector2D interpolate( + Vector2D const &to, + Vector2D const &outTangent, + Vector2D const &inTangent, + double amount, + int maxIterations = 3, + int samples = 20, + double accuracy = 1.0 + ) const; +}; + +Vector2D interpolate( + Vector2D const &from, + Vector2D const &to, + double amount +); + +struct Vector3D { + explicit Vector3D(double x_, double y_, double z_) : + x(x_), + y(y_), + z(z_) { + } + + explicit Vector3D(json11::Json const &json) noexcept(false) { + if (!json.is_array()) { + throw LottieParsingException(); + } + + int index = 0; + + x = 0.0; + y = 0.0; + z = 0.0; + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + x = json.array_items()[index].number_value(); + index++; + } + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + y = json.array_items()[index].number_value(); + index++; + } + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + z = json.array_items()[index].number_value(); + index++; + } + } + + json11::Json toJson() const { + json11::Json::array result; + + result.push_back(json11::Json(x)); + result.push_back(json11::Json(y)); + result.push_back(json11::Json(z)); + + return json11::Json(result); + } + + double x = 0.0; + double y = 0.0; + double z = 0.0; +}; + +Vector3D interpolate( + Vector3D const &from, + Vector3D const &to, + double amount +); + +inline double degreesToRadians(double value) { + return value * M_PI / 180.0; +} + +inline double radiansToDegrees(double value) { + return value * 180.0 / M_PI; +} + +struct CATransform3D { + double m11, m12, m13, m14; + double m21, m22, m23, m24; + double m31, m32, m33, m34; + double m41, m42, m43, m44; + + CATransform3D( + double m11_, double m12_, double m13_, double m14_, + double m21_, double m22_, double m23_, double m24_, + double m31_, double m32_, double m33_, double m34_, + double m41_, double m42_, double m43_, double m44_ + ) : + m11(m11_), m12(m12_), m13(m13_), m14(m14_), + m21(m21_), m22(m22_), m23(m23_), m24(m24_), + m31(m31_), m32(m32_), m33(m33_), m34(m34_), + m41(m41_), m42(m42_), m43(m43_), m44(m44_) { + } + + bool operator==(CATransform3D const &rhs) const { + return m11 == rhs.m11 && m12 == rhs.m12 && m13 == rhs.m13 && m14 == rhs.m14 && + m21 == rhs.m21 && m22 == rhs.m22 && m23 == rhs.m23 && m24 == rhs.m24 && + m31 == rhs.m31 && m32 == rhs.m32 && m33 == rhs.m33 && m34 == rhs.m34 && + m41 == rhs.m41 && m42 == rhs.m42 && m43 == rhs.m43 && m44 == rhs.m44; + } + + bool operator!=(CATransform3D const &rhs) const { + return !(*this == rhs); + } + + inline bool isIdentity() const { + return m11 == 1.0 && m12 == 0.0 && m13 == 0.0 && m14 == 0.0 && + m21 == 0.0 && m22 == 1.0 && m23 == 0.0 && m24 == 0.0 && + m31 == 0.0 && m32 == 0.0 && m33 == 1.0 && m34 == 0.0 && + m41 == 0.0 && m42 == 0.0 && m43 == 0.0 && m44 == 1.0; + } + + static CATransform3D makeTranslation(double tx, double ty, double tz) { + return CATransform3D( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + tx, ty, tz, 1 + ); + } + + static CATransform3D makeScale(double sx, double sy, double sz) { + return CATransform3D( + sx, 0, 0, 0, + 0, sy, 0, 0, + 0, 0, sz, 0, + 0, 0, 0, 1 + ); + } + + static CATransform3D makeRotation(double radians, double x, double y, double z); + + static CATransform3D makeSkew(double skew, double skewAxis) { + double mCos = cos(degreesToRadians(skewAxis)); + double mSin = sin(degreesToRadians(skewAxis)); + double aTan = tan(degreesToRadians(skew)); + + CATransform3D transform1( + mCos, + mSin, + 0.0, + 0.0, + -mSin, + mCos, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + CATransform3D transform2( + 1.0, + 0.0, + 0.0, + 0.0, + aTan, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + CATransform3D transform3( + mCos, + -mSin, + 0.0, + 0.0, + mSin, + mCos, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + return transform3 * transform2 * transform1; + } + + static CATransform3D makeTransform( + Vector2D const &anchor, + Vector2D const &position, + Vector2D const &scale, + double rotation, + std::optional skew, + std::optional skewAxis + ) { + CATransform3D result = CATransform3D::identity(); + if (skew.has_value() && skewAxis.has_value()) { + result = CATransform3D::identity().translated(position).rotated(rotation).skewed(-skew.value(), skewAxis.value()).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } else { + result = CATransform3D::identity().translated(position).rotated(rotation).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } + + return result; + } + + CATransform3D rotated(double degrees) const; + + CATransform3D translated(Vector2D const &translation) const; + + CATransform3D scaled(Vector2D const &scale) const; + + CATransform3D skewed(double skew, double skewAxis) const { + return CATransform3D::makeSkew(skew, skewAxis) * (*this); + } + + static CATransform3D const &identity() { + return _identity; + } + + CATransform3D operator*(CATransform3D const &b) const; + + bool isInvertible() const; + + CATransform3D inverted() const; + +private: + static CATransform3D _identity; +}; + +struct CGRect { + explicit CGRect(double x_, double y_, double width_, double height_) : + x(x_), y(y_), width(width_), height(height_) { + } + + double x = 0.0; + double y = 0.0; + double width = 0.0; + double height = 0.0; + + static CGRect veryLarge() { + return CGRect( + -100000000.0, + -100000000.0, + 200000000.0, + 200000000.0 + ); + } + + bool operator==(CGRect const &rhs) const { + return x == rhs.x && y == rhs.y && width == rhs.width && height == rhs.height; + } + + bool operator!=(CGRect const &rhs) const { + return !(*this == rhs); + } + + bool empty() const { + return width <= 0.0 || height <= 0.0; + } + + CGRect insetBy(double dx, double dy) const { + CGRect result = *this; + + result.x += dx; + result.y += dy; + result.width -= dx * 2.0; + result.height -= dy * 2.0; + + return result; + } + + bool intersects(CGRect const &other) const; + bool contains(CGRect const &other) const; + + CGRect intersection(CGRect const &other) const; + CGRect unionWith(CGRect const &other) const; + + CGRect applyingTransform(CATransform3D const &transform) const; +}; + +inline bool isInRangeOrEqual(double value, double from, double to) { + return from <= value && value <= to; +} + +inline bool isInRange(double value, double from, double to) { + return from < value && value < to; +} + +double cubicBezierInterpolate(double value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3); + +} + +#endif /* Vectors_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Vectors.mm b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Vectors.mm new file mode 100644 index 00000000000..e37b668c029 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Vectors.mm @@ -0,0 +1,518 @@ +#include "Vectors.hpp" + +#include "VectorsCocoa.h" + +#include "Lottie/Public/Keyframes/Interpolatable.hpp" + +#include + +#import + +#import + +namespace lottie { + +CATransform3D CATransform3D::_identity = CATransform3D( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 +); + +double interpolate(double value, double to, double amount) { + return value + ((to - value) * amount); +} + +Vector1D interpolate( + Vector1D const &from, + Vector1D const &to, + double amount +) { + return Vector1D(interpolate(from.value, to.value, amount)); +} + +Vector2D interpolate( + Vector2D const &from, + Vector2D const &to, + double amount +) { + return Vector2D(interpolate(from.x, to.x, amount), interpolate(from.y, to.y, amount)); +} + + +Vector3D interpolate( + Vector3D const &from, + Vector3D const &to, + double amount +) { + return Vector3D(interpolate(from.x, to.x, amount), interpolate(from.y, to.y, amount), interpolate(from.z, to.z, amount)); +} + +static double cubicRoot(double value) { + return pow(value, 1.0 / 3.0); +} + +static double SolveQuadratic(double a, double b, double c) { + double result = (-b + sqrt((b * b) - 4 * a * c)) / (2 * a); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + + result = (-b - sqrt((b * b) - 4 * a * c)) / (2 * a); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + + return -1.0; +} + +static double SolveCubic(double a, double b, double c, double d) { + if (a == 0.0) { + return SolveQuadratic(b, c, d); + } + if (d == 0.0) { + return 0.0; + } + b /= a; + c /= a; + d /= a; + double q = (3.0 * c - (b * b)) / 9.0; + double r = (-27.0 * d + b * (9.0 * c - 2.0 * (b * b))) / 54.0; + double disc = (q * q * q) + (r * r); + double term1 = b / 3.0; + + if (disc > 0.0) { + double s = r + sqrt(disc); + s = (s < 0) ? -cubicRoot(-s) : cubicRoot(s); + double t = r - sqrt(disc); + t = (t < 0) ? -cubicRoot(-t) : cubicRoot(t); + + double result = -term1 + s + t; + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + } else if (disc == 0) { + double r13 = (r < 0) ? -cubicRoot(-r) : cubicRoot(r); + + double result = -term1 + 2.0 * r13; + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + + result = -(r13 + term1); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + } else { + q = -q; + double dum1 = q * q * q; + dum1 = acos(r / sqrt(dum1)); + double r13 = 2.0 * sqrt(q); + + double result = -term1 + r13 * cos(dum1 / 3.0); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + result = -term1 + r13 * cos((dum1 + 2.0 * M_PI) / 3.0); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + result = -term1 + r13 * cos((dum1 + 4.0 * M_PI) / 3.0); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + } + + return -1; +} + +double cubicBezierInterpolate(double value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3) { + double t = 0.0; + if (value == P0.x) { + // Handle corner cases explicitly to prevent rounding errors + t = 0.0; + } else if (value == P3.x) { + t = 1.0; + } else { + // Calculate t + double a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x; + double b = 3 * P0.x - 6 * P1.x + 3 * P2.x; + double c = -3 * P0.x + 3 * P1.x; + double d = P0.x - value; + double tTemp = SolveCubic(a, b, c, d); + if (tTemp == -1.0) { + return -1.0; + } + t = tTemp; + } + + // Calculate y from t + double oneMinusT = 1.0 - t; + return (oneMinusT * oneMinusT * oneMinusT) * P0.y + 3 * t * (oneMinusT * oneMinusT) * P1.y + 3 * (t * t) * (1 - t) * P2.y + (t * t * t) * P3.y; +} + +struct InterpolationPoint2D { + InterpolationPoint2D(Vector2D const point_, double distance_) : + point(point_), distance(distance_) { + } + + Vector2D point; + double distance; +}; + +namespace { + double interpolateDouble(double value, double to, double amount) { + return value + ((to - value) * amount); + } +} + +Vector2D Vector2D::pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, double amount) const { + auto a = interpolate(outTangent, amount); + auto b = outTangent.interpolate(inTangent, amount); + auto c = inTangent.interpolate(to, amount); + auto d = a.interpolate(b, amount); + auto e = b.interpolate(c, amount); + auto f = d.interpolate(e, amount); + return f; +} + +Vector2D Vector2D::interpolate(Vector2D const &to, double amount) const { + return Vector2D( + interpolateDouble(x, to.x, amount), + interpolateDouble(y, to.y, amount) + ); +} + +Vector2D Vector2D::interpolate( + Vector2D const &to, + Vector2D const &outTangent, + Vector2D const &inTangent, + double amount, + int maxIterations, + int samples, + double accuracy +) const { + if (amount == 0.0) { + return *this; + } + if (amount == 1.0) { + return to; + } + + if (colinear(outTangent, inTangent) && outTangent.colinear(inTangent, to)) { + return interpolate(to, amount); + } + + double step = 1.0 / (double)samples; + + std::vector points; + points.push_back(InterpolationPoint2D(*this, 0.0)); + double totalLength = 0.0; + + Vector2D previousPoint = *this; + double previousAmount = 0.0; + + int closestPoint = 0; + + while (previousAmount < 1.0) { + previousAmount = previousAmount + step; + + if (previousAmount < amount) { + closestPoint = closestPoint + 1; + } + + auto newPoint = pointOnPath(to, outTangent, inTangent, previousAmount); + auto distance = previousPoint.distanceTo(newPoint); + totalLength = totalLength + distance; + points.push_back(InterpolationPoint2D(newPoint, totalLength)); + previousPoint = newPoint; + } + + double accurateDistance = amount * totalLength; + auto point = points[closestPoint]; + + bool foundPoint = false; + + double pointAmount = ((double)closestPoint) * step; + double nextPointAmount = pointAmount + step; + + int refineIterations = 0; + while (!foundPoint) { + refineIterations = refineIterations + 1; + /// First see if the next point is still less than the projected length. + auto nextPoint = points[closestPoint + 1]; + if (nextPoint.distance < accurateDistance) { + point = nextPoint; + closestPoint = closestPoint + 1; + pointAmount = ((double)closestPoint) * step; + nextPointAmount = pointAmount + step; + if (closestPoint == (int)points.size()) { + foundPoint = true; + } + continue; + } + if (accurateDistance < point.distance) { + closestPoint = closestPoint - 1; + if (closestPoint < 0) { + foundPoint = true; + continue; + } + point = points[closestPoint]; + pointAmount = ((double)closestPoint) * step; + nextPointAmount = pointAmount + step; + continue; + } + + /// Now we are certain the point is the closest point under the distance + auto pointDiff = nextPoint.distance - point.distance; + auto proposedPointAmount = remapDouble((accurateDistance - point.distance) / pointDiff, 0.0, 1.0, pointAmount, nextPointAmount); + + auto newPoint = pointOnPath(to, outTangent, inTangent, proposedPointAmount); + auto newDistance = point.distance + point.point.distanceTo(newPoint); + pointAmount = proposedPointAmount; + point = InterpolationPoint2D(newPoint, newDistance); + if (accurateDistance - newDistance <= accuracy || + newDistance - accurateDistance <= accuracy) { + foundPoint = true; + } + + if (refineIterations == maxIterations) { + foundPoint = true; + } + } + return point.point; +} + +::CATransform3D nativeTransform(CATransform3D const &value) { + ::CATransform3D result; + + result.m11 = value.m11; + result.m12 = value.m12; + result.m13 = value.m13; + result.m14 = value.m14; + + result.m21 = value.m21; + result.m22 = value.m22; + result.m23 = value.m23; + result.m24 = value.m24; + + result.m31 = value.m31; + result.m32 = value.m32; + result.m33 = value.m33; + result.m34 = value.m34; + + result.m41 = value.m41; + result.m42 = value.m42; + result.m43 = value.m43; + result.m44 = value.m44; + + return result; +} + +CATransform3D fromNativeTransform(::CATransform3D const &value) { + CATransform3D result = CATransform3D::identity(); + + result.m11 = value.m11; + result.m12 = value.m12; + result.m13 = value.m13; + result.m14 = value.m14; + + result.m21 = value.m21; + result.m22 = value.m22; + result.m23 = value.m23; + result.m24 = value.m24; + + result.m31 = value.m31; + result.m32 = value.m32; + result.m33 = value.m33; + result.m34 = value.m34; + + result.m41 = value.m41; + result.m42 = value.m42; + result.m43 = value.m43; + result.m44 = value.m44; + + return result; +} + +CATransform3D CATransform3D::makeRotation(double radians, double x, double y, double z) { + return fromNativeTransform(CATransform3DMakeRotation(radians, x, y, z)); + + /*if (x == 0.0 && y == 0.0 && z == 0.0) { + return CATransform3D::identity(); + } + + float s = sin(radians); + float c = cos(radians); + + float len = sqrt(x*x + y*y + z*z); + x /= len; y /= len; z /= len; + + CATransform3D returnValue = CATransform3D::identity(); + + returnValue.m11 = c + (1-c) * x*x; + returnValue.m12 = (1-c) * x*y + s*z; + returnValue.m13 = (1-c) * x*z - s*y; + returnValue.m14 = 0; + + returnValue.m21 = (1-c) * y*x - s*z; + returnValue.m22 = c + (1-c) * y*y; + returnValue.m23 = (1-c) * y*z + s*x; + returnValue.m24 = 0; + + returnValue.m31 = (1-c) * z*x + s*y; + returnValue.m32 = (1-c) * y*z - s*x; + returnValue.m33 = c + (1-c) * z*z; + returnValue.m34 = 0; + + returnValue.m41 = 0; + returnValue.m42 = 0; + returnValue.m43 = 0; + returnValue.m44 = 1; + + return returnValue;*/ +} + +CATransform3D CATransform3D::rotated(double degrees) const { + return fromNativeTransform(CATransform3DRotate(nativeTransform(*this), degreesToRadians(degrees), 0.0, 0.0, 1.0)); + //return CATransform3D::makeRotation(degreesToRadians(degrees), 0.0, 0.0, 1.0) * (*this); +} + +CATransform3D CATransform3D::translated(Vector2D const &translation) const { + return fromNativeTransform(CATransform3DTranslate(nativeTransform(*this), translation.x, translation.y, 0.0)); +} + +CATransform3D CATransform3D::scaled(Vector2D const &scale) const { + return fromNativeTransform(CATransform3DScale(nativeTransform(*this), scale.x, scale.y, 1.0)); + //return CATransform3D::makeScale(scale.x, scale.y, 1.0) * (*this); +} + +CATransform3D CATransform3D::operator*(CATransform3D const &b) const { + if (isIdentity()) { + return b; + } + if (b.isIdentity()) { + return *this; + } + + const CATransform3D lhs = b; + const CATransform3D &rhs = *this; + CATransform3D result = CATransform3D::identity(); + + result.m11 = (lhs.m11*rhs.m11)+(lhs.m21*rhs.m12)+(lhs.m31*rhs.m13)+(lhs.m41*rhs.m14); + result.m12 = (lhs.m12*rhs.m11)+(lhs.m22*rhs.m12)+(lhs.m32*rhs.m13)+(lhs.m42*rhs.m14); + result.m13 = (lhs.m13*rhs.m11)+(lhs.m23*rhs.m12)+(lhs.m33*rhs.m13)+(lhs.m43*rhs.m14); + result.m14 = (lhs.m14*rhs.m11)+(lhs.m24*rhs.m12)+(lhs.m34*rhs.m13)+(lhs.m44*rhs.m14); + + result.m21 = (lhs.m11*rhs.m21)+(lhs.m21*rhs.m22)+(lhs.m31*rhs.m23)+(lhs.m41*rhs.m24); + result.m22 = (lhs.m12*rhs.m21)+(lhs.m22*rhs.m22)+(lhs.m32*rhs.m23)+(lhs.m42*rhs.m24); + result.m23 = (lhs.m13*rhs.m21)+(lhs.m23*rhs.m22)+(lhs.m33*rhs.m23)+(lhs.m43*rhs.m24); + result.m24 = (lhs.m14*rhs.m21)+(lhs.m24*rhs.m22)+(lhs.m34*rhs.m23)+(lhs.m44*rhs.m24); + + result.m31 = (lhs.m11*rhs.m31)+(lhs.m21*rhs.m32)+(lhs.m31*rhs.m33)+(lhs.m41*rhs.m34); + result.m32 = (lhs.m12*rhs.m31)+(lhs.m22*rhs.m32)+(lhs.m32*rhs.m33)+(lhs.m42*rhs.m34); + result.m33 = (lhs.m13*rhs.m31)+(lhs.m23*rhs.m32)+(lhs.m33*rhs.m33)+(lhs.m43*rhs.m34); + result.m34 = (lhs.m14*rhs.m31)+(lhs.m24*rhs.m32)+(lhs.m34*rhs.m33)+(lhs.m44*rhs.m34); + + result.m41 = (lhs.m11*rhs.m41)+(lhs.m21*rhs.m42)+(lhs.m31*rhs.m43)+(lhs.m41*rhs.m44); + result.m42 = (lhs.m12*rhs.m41)+(lhs.m22*rhs.m42)+(lhs.m32*rhs.m43)+(lhs.m42*rhs.m44); + result.m43 = (lhs.m13*rhs.m41)+(lhs.m23*rhs.m42)+(lhs.m33*rhs.m43)+(lhs.m43*rhs.m44); + result.m44 = (lhs.m14*rhs.m41)+(lhs.m24*rhs.m42)+(lhs.m34*rhs.m43)+(lhs.m44*rhs.m44); + + return result; +} + +bool CATransform3D::isInvertible() const { + return std::abs(m11 * m22 - m12 * m21) >= 0.00000001; +} + +CATransform3D CATransform3D::inverted() const { + return fromNativeTransform(CATransform3DMakeAffineTransform(CGAffineTransformInvert(CATransform3DGetAffineTransform(nativeTransform(*this))))); +} + +bool CGRect::intersects(CGRect const &other) const { + return CGRectIntersectsRect(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); +} + +bool CGRect::contains(CGRect const &other) const { + return CGRectContainsRect(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); +} + +CGRect CGRect::intersection(CGRect const &other) const { + auto result = CGRectIntersection(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); + return CGRect(result.origin.x, result.origin.y, result.size.width, result.size.height); +} + +CGRect CGRect::unionWith(CGRect const &other) const { + auto result = CGRectUnion(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); + return CGRect(result.origin.x, result.origin.y, result.size.width, result.size.height); +} + +static inline Vector2D applyingTransformToPoint(CATransform3D const &transform, Vector2D const &point) { + double newX = point.x * transform.m11 + point.y * transform.m21 + transform.m41; + double newY = point.x * transform.m12 + point.y * transform.m22 + transform.m42; + double newW = point.x * transform.m14 + point.y * transform.m24 + transform.m44; + + return Vector2D(newX / newW, newY / newW); +} + +CGRect CGRect::applyingTransform(CATransform3D const &transform) const { + if (transform.isIdentity()) { + return *this; + } + + Vector2D topLeft = applyingTransformToPoint(transform, Vector2D(x, y)); + Vector2D topRight = applyingTransformToPoint(transform, Vector2D(x + width, y)); + Vector2D bottomLeft = applyingTransformToPoint(transform, Vector2D(x, y + height)); + Vector2D bottomRight = applyingTransformToPoint(transform, Vector2D(x + width, y + height)); + + double minX = topLeft.x; + if (topRight.x < minX) { + minX = topRight.x; + } + if (bottomLeft.x < minX) { + minX = bottomLeft.x; + } + if (bottomRight.x < minX) { + minX = bottomRight.x; + } + + double minY = topLeft.y; + if (topRight.y < minY) { + minY = topRight.y; + } + if (bottomLeft.y < minY) { + minY = bottomLeft.y; + } + if (bottomRight.y < minY) { + minY = bottomRight.y; + } + + double maxX = topLeft.x; + if (topRight.x > maxX) { + maxX = topRight.x; + } + if (bottomLeft.x > maxX) { + maxX = bottomLeft.x; + } + if (bottomRight.x > maxX) { + maxX = bottomRight.x; + } + + double maxY = topLeft.y; + if (topRight.y > maxY) { + maxY = topRight.y; + } + if (bottomLeft.y > maxY) { + maxY = bottomLeft.y; + } + if (bottomRight.y > maxY) { + maxY = bottomRight.y; + } + + CGRect result(minX, minY, maxX - minX, maxY - minY); + + return result; +} + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Vectors.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Vectors.swift new file mode 100644 index 00000000000..9d20ee8eae0 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/Vectors.swift @@ -0,0 +1,38 @@ +// +// Vectors.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation + +// MARK: - Vector1D + +public struct Vector1D: Hashable { + + public init(_ value: Double) { + self.value = value + } + + public let value: Double + +} + +// MARK: - Vector3D + +/// A three dimensional vector. +/// These vectors are encoded and decoded from [Double] +public struct Vector3D: Hashable { + + public let x: Double + public let y: Double + public let z: Double + + public init(x: Double, y: Double, z: Double) { + self.x = x + self.y = y + self.z = z + } + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/VectorsCocoa.h b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/VectorsCocoa.h new file mode 100644 index 00000000000..9804d2f2f11 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/Primitives/VectorsCocoa.h @@ -0,0 +1,13 @@ +#ifndef VectorsCocoa_h +#define VectorsCocoa_h + +#import + +namespace lottie { + +::CATransform3D nativeTransform(CATransform3D const &value); +CATransform3D fromNativeTransform(::CATransform3D const &value); + +} + +#endif /* VectorsCocoa_h */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/TextProvider/AnimationTextProvider.cpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/TextProvider/AnimationTextProvider.cpp new file mode 100644 index 00000000000..6ba1e020c87 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/TextProvider/AnimationTextProvider.cpp @@ -0,0 +1,5 @@ +#include "AnimationTextProvider.hpp" + +namespace lottie { + +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/TextProvider/AnimationTextProvider.hpp b/Tests/LottieMetalTest/LottieSwift/Sources/Public/TextProvider/AnimationTextProvider.hpp new file mode 100644 index 00000000000..d93089c6230 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/TextProvider/AnimationTextProvider.hpp @@ -0,0 +1,48 @@ +#ifndef AnimationTextProvider_hpp +#define AnimationTextProvider_hpp + +#include +#include + +namespace lottie { + +/// Text provider is a protocol that is used to supply text to `AnimationView`. +class AnimationTextProvider { +public: + virtual std::string textFor(std::string const &keypathName, std::string const &sourceText) = 0; +}; + +/// Text provider that simply map values from dictionary +class DictionaryTextProvider: public AnimationTextProvider { +public: + DictionaryTextProvider(std::map const &values) : + _values(values) { + } + + virtual std::string textFor(std::string const &keypathName, std::string const &sourceText) override { + const auto it = _values.find(keypathName); + if (it != _values.end()) { + return it->second; + } else { + return sourceText; + } + } + +private: + std::map _values; +}; + +/// Default text provider. Uses text in the animation file +class DefaultTextProvider: public AnimationTextProvider { +public: + DefaultTextProvider() { + } + + virtual std::string textFor(std::string const &keypathName, std::string const &sourceText) override { + return sourceText; + } +}; + +} + +#endif /* AnimationTextProvider_hpp */ diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/TextProvider/AnimationTextProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/TextProvider/AnimationTextProvider.swift new file mode 100644 index 00000000000..b448dbb1df7 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/TextProvider/AnimationTextProvider.swift @@ -0,0 +1,53 @@ +// +// AnimationImageProvider.swift +// Lottie_iOS +// +// Created by Alexandr Goncharov on 07/06/2019. +// + +import Foundation + +// MARK: - AnimationTextProvider + +/// Text provider is a protocol that is used to supply text to `AnimationView`. +public protocol AnimationTextProvider: AnyObject { + func textFor(keypathName: String, sourceText: String) -> String +} + +// MARK: - DictionaryTextProvider + +/// Text provider that simply map values from dictionary +public final class DictionaryTextProvider: AnimationTextProvider { + + // MARK: Lifecycle + + public init(_ values: [String: String]) { + self.values = values + } + + // MARK: Public + + public func textFor(keypathName: String, sourceText: String) -> String { + values[keypathName] ?? sourceText + } + + // MARK: Internal + + let values: [String: String] +} + +// MARK: - DefaultTextProvider + +/// Default text provider. Uses text in the animation file +public final class DefaultTextProvider: AnimationTextProvider { + + // MARK: Lifecycle + + public init() {} + + // MARK: Public + + public func textFor(keypathName _: String, sourceText: String) -> String { + sourceText + } +} diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimatedButton.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimatedButton.swift new file mode 100644 index 00000000000..7aa0346bed3 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimatedButton.swift @@ -0,0 +1,78 @@ +// +// AnimatedButton.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) +import UIKit +/// An interactive button that plays an animation when pressed. +open class AnimatedButton: AnimatedControl { + + // MARK: Lifecycle + + public override init( + animation: Animation, + configuration: LottieConfiguration = .shared) + { + super.init(animation: animation, configuration: configuration) + accessibilityTraits = UIAccessibilityTraits.button + } + + public override init() { + super.init() + accessibilityTraits = UIAccessibilityTraits.button + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + // MARK: Public + + /// Sets the play range for the given UIControlEvent. + public func setPlayRange(fromProgress: AnimationProgressTime, toProgress: AnimationProgressTime, event: UIControl.Event) { + rangesForEvents[event.rawValue] = (from: fromProgress, to: toProgress) + } + + /// Sets the play range for the given UIControlEvent. + public func setPlayRange(fromMarker fromName: String, toMarker toName: String, event: UIControl.Event) { + if + let start = animationView.progressTime(forMarker: fromName), + let end = animationView.progressTime(forMarker: toName) + { + rangesForEvents[event.rawValue] = (from: start, to: end) + } + } + + public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + let _ = super.beginTracking(touch, with: event) + let touchEvent = UIControl.Event.touchDown + if let playrange = rangesForEvents[touchEvent.rawValue] { + animationView.play(fromProgress: playrange.from, toProgress: playrange.to, loopMode: LottieLoopMode.playOnce) + } + return true + } + + public override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + super.endTracking(touch, with: event) + let touchEvent: UIControl.Event + if let touch = touch, bounds.contains(touch.location(in: self)) { + touchEvent = UIControl.Event.touchUpInside + } else { + touchEvent = UIControl.Event.touchUpOutside + } + + if let playrange = rangesForEvents[touchEvent.rawValue] { + animationView.play(fromProgress: playrange.from, toProgress: playrange.to, loopMode: LottieLoopMode.playOnce) + } + } + + // MARK: Fileprivate + + fileprivate var rangesForEvents: [UInt : (from: CGFloat, to: CGFloat)] = + [UIControl.Event.touchUpInside.rawValue : (from: 0, to: 1)] +} +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimatedControl.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimatedControl.swift new file mode 100644 index 00000000000..a0d568afc50 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimatedControl.swift @@ -0,0 +1,177 @@ +// +// AnimatedControl.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) +import UIKit + +/// Lottie comes prepacked with a two Animated Controls, `AnimatedSwitch` and +/// `AnimatedButton`. Both of these controls are built on top of `AnimatedControl` +/// +/// `AnimatedControl` is a subclass of `UIControl` that provides an interactive +/// mechanism for controlling the visual state of an animation in response to +/// user actions. +/// +/// The `AnimatedControl` will show and hide layers depending on the current +/// `UIControl.State` of the control. +/// +/// Users of `AnimationControl` can set a Layer Name for each `UIControl.State`. +/// When the state is change the `AnimationControl` will change the visibility +/// of its layers. +/// +/// NOTE: Do not initialize directly. This is intended to be subclassed. +open class AnimatedControl: UIControl { + + // MARK: Lifecycle + + // MARK: Initializers + + public init( + animation: Animation, + configuration: LottieConfiguration = .shared) + { + animationView = AnimationView( + animation: animation, + configuration: configuration) + + super.init(frame: animation.bounds) + commonInit() + } + + public init() { + animationView = AnimationView() + super.init(frame: .zero) + commonInit() + } + + required public init?(coder aDecoder: NSCoder) { + animationView = AnimationView() + super.init(coder: aDecoder) + commonInit() + } + + // MARK: Open + + // MARK: UIControl Overrides + + open override var isEnabled: Bool { + didSet { + updateForState() + } + } + + open override var isSelected: Bool { + didSet { + updateForState() + } + } + + open override var isHighlighted: Bool { + didSet { + updateForState() + } + } + + open override var intrinsicContentSize: CGSize { + animationView.intrinsicContentSize + } + + open override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + updateForState() + return super.beginTracking(touch, with: event) + } + + open override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + updateForState() + return super.continueTracking(touch, with: event) + } + + open override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + updateForState() + return super.endTracking(touch, with: event) + } + + open override func cancelTracking(with event: UIEvent?) { + updateForState() + super.cancelTracking(with: event) + } + + open func animationDidSet() { + + } + + // MARK: Public + + /// The animation view in which the animation is rendered. + public let animationView: AnimationView + + /// The animation backing the animated control. + public var animation: Animation? { + didSet { + animationView.animation = animation + animationView.bounds = animation?.bounds ?? .zero + setNeedsLayout() + updateForState() + animationDidSet() + } + } + + /// The speed of the animation playback. Defaults to 1 + public var animationSpeed: CGFloat { + set { animationView.animationSpeed = newValue } + get { animationView.animationSpeed } + } + + /// Sets which Animation Layer should be visible for the given state. + public func setLayer(named: String, forState: UIControl.State) { + stateMap[forState.rawValue] = named + updateForState() + } + + /// Sets a ValueProvider for the specified keypath + public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + animationView.setValueProvider(valueProvider, keypath: keypath) + } + + // MARK: Internal + + var stateMap: [UInt: String] = [:] + + func updateForState() { + guard let animationLayer = animationView.animationLayer else { return } + if + let layerName = stateMap[state.rawValue], + let stateLayer = animationLayer.layer(for: AnimationKeypath(keypath: layerName)) + { + for layer in animationLayer._animationLayers { + layer.isHidden = true + } + stateLayer.isHidden = false + } else { + for layer in animationLayer._animationLayers { + layer.isHidden = false + } + } + } + + // MARK: Fileprivate + + fileprivate func commonInit() { + animationView.clipsToBounds = false + clipsToBounds = true + animationView.translatesAutoresizingMaskIntoConstraints = false + animationView.backgroundBehavior = .forceFinish + addSubview(animationView) + animationView.contentMode = .scaleAspectFit + animationView.isUserInteractionEnabled = false + animationView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + animationView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + animationView.topAnchor.constraint(equalTo: topAnchor).isActive = true + animationView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } +} +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimatedSwitch.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimatedSwitch.swift new file mode 100644 index 00000000000..19e1edd6b7f --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimatedSwitch.swift @@ -0,0 +1,225 @@ +// +// AnimatedSwitch.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) +import UIKit + +/// An interactive switch with an 'On' and 'Off' state. When the user taps on the +/// switch the state is toggled and the appropriate animation is played. +/// +/// Both the 'On' and 'Off' have an animation play range associated with their state. +open class AnimatedSwitch: AnimatedControl { + + // MARK: Lifecycle + + public override init( + animation: Animation, + configuration: LottieConfiguration = .shared) + { + /// Generate a haptic generator if available. + #if os(iOS) + if #available(iOS 10.0, *) { + self.hapticGenerator = HapticGenerator() + } else { + hapticGenerator = NullHapticGenerator() + } + #else + hapticGenerator = NullHapticGenerator() + #endif + super.init(animation: animation, configuration: configuration) + updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false) + accessibilityTraits = UIAccessibilityTraits.button + } + + public override init() { + /// Generate a haptic generator if available. + #if os(iOS) + if #available(iOS 10.0, *) { + self.hapticGenerator = HapticGenerator() + } else { + hapticGenerator = NullHapticGenerator() + } + #else + hapticGenerator = NullHapticGenerator() + #endif + super.init() + updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false) + accessibilityTraits = UIAccessibilityTraits.button + } + + required public init?(coder aDecoder: NSCoder) { + /// Generate a haptic generator if available. + #if os(iOS) + if #available(iOS 10.0, *) { + self.hapticGenerator = HapticGenerator() + } else { + hapticGenerator = NullHapticGenerator() + } + #else + hapticGenerator = NullHapticGenerator() + #endif + super.init(coder: aDecoder) + accessibilityTraits = UIAccessibilityTraits.button + } + + // MARK: Public + + /// Defines what happens when the user taps the switch while an + /// animation is still in flight + public enum CancelBehavior { + case reverse // default - plays the current animation in reverse + case none // does not update the animation when canceled + } + + /// The cancel behavior for the switch. See CancelBehavior for options + public var cancelBehavior: CancelBehavior = .reverse + + /// The current state of the switch. + public var isOn: Bool { + set { + /// This is forwarded to a private variable because the animation needs to be updated without animation when set externally and with animation when set internally. + guard _isOn != newValue else { return } + updateOnState(isOn: newValue, animated: false, shouldFireHaptics: false) + } + get { + _isOn + } + } + + /// Set the state of the switch and specify animation and haptics + public func setIsOn(_ isOn: Bool, animated: Bool, shouldFireHaptics: Bool = true) { + guard isOn != _isOn else { return } + updateOnState(isOn: isOn, animated: animated, shouldFireHaptics: shouldFireHaptics) + } + + /// Sets the play range for the given state. When the switch is toggled, the animation range is played. + public func setProgressForState( + fromProgress: AnimationProgressTime, + toProgress: AnimationProgressTime, + forOnState: Bool) + { + if forOnState { + onStartProgress = fromProgress + onEndProgress = toProgress + } else { + offStartProgress = fromProgress + offEndProgress = toProgress + } + + updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false) + } + + public override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + super.endTracking(touch, with: event) + updateOnState(isOn: !_isOn, animated: true, shouldFireHaptics: true) + sendActions(for: .valueChanged) + } + + public override func animationDidSet() { + updateOnState(isOn: _isOn, animated: true, shouldFireHaptics: false) + } + + // MARK: Internal + + // MARK: Animation State + + func updateOnState(isOn: Bool, animated: Bool, shouldFireHaptics: Bool) { + _isOn = isOn + var startProgress = isOn ? onStartProgress : offStartProgress + var endProgress = isOn ? onEndProgress : offEndProgress + let finalProgress = endProgress + + if cancelBehavior == .reverse { + let realtimeProgress = animationView.realtimeAnimationProgress + + let previousStateStart = isOn ? offStartProgress : onStartProgress + let previousStateEnd = isOn ? offEndProgress : onEndProgress + if + realtimeProgress.isInRange( + min(previousStateStart, previousStateEnd), + max(previousStateStart, previousStateEnd)) + { + /// Animation is currently in the previous time range. Reverse the previous play. + startProgress = previousStateEnd + endProgress = previousStateStart + } + } + + updateAccessibilityLabel() + + guard animated == true else { + animationView.currentProgress = finalProgress + return + } + + if shouldFireHaptics { + hapticGenerator.generateImpact() + } + + animationView.play( + fromProgress: startProgress, + toProgress: endProgress, + loopMode: LottieLoopMode.playOnce, + completion: { [weak self] finished in + guard let self = self else { return } + + // For the Main Thread rendering engine, we freeze the animation at the expected final progress + // once the animation is complete. This isn't necessary on the Core Animation engine. + if finished, !(self.animationView.animationLayer is CoreAnimationLayer) { + self.animationView.currentProgress = finalProgress + } + }) + } + + // MARK: Fileprivate + + fileprivate var onStartProgress: CGFloat = 0 + fileprivate var onEndProgress: CGFloat = 1 + fileprivate var offStartProgress: CGFloat = 1 + fileprivate var offEndProgress: CGFloat = 0 + fileprivate var _isOn = false + fileprivate var hapticGenerator: ImpactGenerator + + // MARK: Private + + private func updateAccessibilityLabel() { + accessibilityValue = _isOn ? NSLocalizedString("On", comment: "On") : NSLocalizedString("Off", comment: "Off") + } + +} +#endif + +// MARK: - ImpactGenerator + +protocol ImpactGenerator { + func generateImpact() +} + +// MARK: - NullHapticGenerator + +class NullHapticGenerator: ImpactGenerator { + func generateImpact() { + + } +} + +#if os(iOS) +@available(iOS 10.0, *) +class HapticGenerator: ImpactGenerator { + + // MARK: Internal + + func generateImpact() { + impact.impactOccurred() + } + + // MARK: Fileprivate + + fileprivate let impact = UIImpactFeedbackGenerator(style: .light) +} +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimationSubview.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimationSubview.swift new file mode 100644 index 00000000000..bc4df54db48 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimationSubview.swift @@ -0,0 +1,20 @@ +// +// AnimationSubview.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) +import UIKit + +/// A view that can be added to a keypath of an AnimationView +public final class AnimationSubview: UIView { + + var viewLayer: CALayer? { + layer + } + +} +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimationViewBase.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimationViewBase.swift new file mode 100644 index 00000000000..adf48dc3938 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/AnimationViewBase.swift @@ -0,0 +1,78 @@ +// +// AnimationViewBase.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/6/19. +// + +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) +import UIKit + +/// The base view for `AnimationView` on iOS, tvOS, watchOS, and macCatalyst. +/// +/// Enables the `AnimationView` implementation to be shared across platforms. +public class AnimationViewBase: UIView { + + // MARK: Public + + public override var contentMode: UIView.ContentMode { + didSet { + setNeedsLayout() + } + } + + public override func didMoveToWindow() { + super.didMoveToWindow() + animationMovedToWindow() + } + + public override func layoutSubviews() { + super.layoutSubviews() + layoutAnimation() + } + + // MARK: Internal + + var viewLayer: CALayer? { + layer + } + + var screenScale: CGFloat { + UIScreen.main.scale + } + + func layoutAnimation() { + // Implemented by subclasses. + } + + func animationMovedToWindow() { + // Implemented by subclasses. + } + + func commonInit() { + contentMode = .scaleAspectFit + clipsToBounds = true + NotificationCenter.default.addObserver( + self, + selector: #selector(animationWillEnterForeground), + name: UIApplication.willEnterForegroundNotification, + object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(animationWillMoveToBackground), + name: UIApplication.didEnterBackgroundNotification, + object: nil) + } + + @objc + func animationWillMoveToBackground() { + // Implemented by subclasses. + } + + @objc + func animationWillEnterForeground() { + // Implemented by subclasses. + } + +} +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/BundleImageProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/BundleImageProvider.swift new file mode 100644 index 00000000000..512d07af7c9 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/BundleImageProvider.swift @@ -0,0 +1,90 @@ +// +// LottieBundleImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import CoreGraphics +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) +import UIKit + +/// An `AnimationImageProvider` that provides images by name from a specific bundle. +/// The BundleImageProvider is initialized with a bundle and an optional searchPath. +public class BundleImageProvider: AnimationImageProvider { + + // MARK: Lifecycle + + /// Initializes an image provider with a bundle and an optional subpath. + /// + /// Provides images for an animation from a bundle. Additionally the provider can + /// search a specific subpath for the images. + /// + /// - Parameter bundle: The bundle containing images for the provider. + /// - Parameter searchPath: The subpath is a path within the bundle to search for image assets. + /// + public init(bundle: Bundle, searchPath: String?) { + self.bundle = bundle + self.searchPath = searchPath + } + + // MARK: Public + + public func imageForAsset(asset: ImageAsset) -> CGImage? { + + if + let data = Data(imageAsset: asset), + let image = UIImage(data: data) + { + return image.cgImage + } + + let imagePath: String? + /// Try to find the image in the bundle. + if let searchPath = searchPath { + /// Search in the provided search path for the image + var directoryPath = URL(fileURLWithPath: searchPath) + directoryPath.appendPathComponent(asset.directory) + + if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: directoryPath.path) { + /// First search for the image in the asset provided sub directory. + imagePath = path + } else if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: searchPath) { + /// Try finding the image in the search path. + imagePath = path + } else { + imagePath = bundle.path(forResource: asset.name, ofType: nil) + } + } else { + if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: asset.directory) { + /// First search for the image in the asset provided sub directory. + imagePath = path + } else { + /// First search for the image in bundle. + imagePath = bundle.path(forResource: asset.name, ofType: nil) + } + } + + if imagePath == nil { + guard let image = UIImage(named: asset.name, in: bundle, compatibleWith: nil) else { + LottieLogger.shared.assertionFailure("Could not find image \"\(asset.name)\" in bundle") + return nil + } + return image.cgImage + } + + guard let foundPath = imagePath, let image = UIImage(contentsOfFile: foundPath) else { + /// No image found. + LottieLogger.shared.assertionFailure("Could not find image \"\(asset.name)\" in bundle") + return nil + } + return image.cgImage + } + + // MARK: Internal + + let bundle: Bundle + let searchPath: String? +} +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift new file mode 100644 index 00000000000..73963557f98 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift @@ -0,0 +1,33 @@ +// +// CompatibleAnimationKeypath.swift +// Lottie_iOS +// +// Created by Tyler Hedrick on 3/6/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) + +/// An Objective-C compatible wrapper around Lottie's AnimationKeypath +@objc +public final class CompatibleAnimationKeypath: NSObject { + + // MARK: Lifecycle + + /// Creates a keypath from a dot separated string. The string is separated by "." + @objc + public init(keypath: String) { + animationKeypath = AnimationKeypath(keypath: keypath) + } + + /// Creates a keypath from a list of strings. + @objc + public init(keys: [String]) { + animationKeypath = AnimationKeypath(keys: keys) + } + + // MARK: Public + + public let animationKeypath: AnimationKeypath +} +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift new file mode 100644 index 00000000000..b1e358ce8a6 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift @@ -0,0 +1,323 @@ +// +// CompatibleAnimationView.swift +// Lottie_iOS +// +// Created by Tyler Hedrick on 3/6/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) +import UIKit + +/// An Objective-C compatible wrapper around Lottie's Animation class. +/// Use in tandem with CompatibleAnimationView when using Lottie in Objective-C +@objc +public final class CompatibleAnimation: NSObject { + + // MARK: Lifecycle + + @objc + public init(name: String, bundle: Bundle = Bundle.main) { + self.name = name + self.bundle = bundle + super.init() + } + + // MARK: Internal + + internal var animation: Animation? { + Animation.named(name, bundle: bundle) + } + + @objc + static func named(_ name: String) -> CompatibleAnimation { + CompatibleAnimation(name: name) + } + + // MARK: Private + + private let name: String + private let bundle: Bundle +} + +/// An Objective-C compatible wrapper around Lottie's AnimationView. +@objc +public final class CompatibleAnimationView: UIView { + + // MARK: Lifecycle + + @objc + public init(compatibleAnimation: CompatibleAnimation) { + animationView = AnimationView(animation: compatibleAnimation.animation) + self.compatibleAnimation = compatibleAnimation + super.init(frame: .zero) + commonInit() + } + + @objc + public override init(frame: CGRect) { + animationView = AnimationView() + super.init(frame: frame) + commonInit() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Public + + @objc + public var compatibleAnimation: CompatibleAnimation? { + didSet { + animationView.animation = compatibleAnimation?.animation + } + } + + @objc + public var loopAnimationCount: CGFloat = 0 { + didSet { + animationView.loopMode = loopAnimationCount == -1 ? .loop : .repeat(Float(loopAnimationCount)) + } + } + + @objc + public override var contentMode: UIView.ContentMode { + set { animationView.contentMode = newValue } + get { animationView.contentMode } + } + + @objc + public var shouldRasterizeWhenIdle: Bool { + set { animationView.shouldRasterizeWhenIdle = newValue } + get { animationView.shouldRasterizeWhenIdle } + } + + @objc + public var currentProgress: CGFloat { + set { animationView.currentProgress = newValue } + get { animationView.currentProgress } + } + + @objc + public var currentTime: TimeInterval { + set { animationView.currentTime = newValue } + get { animationView.currentTime } + } + + @objc + public var currentFrame: CGFloat { + set { animationView.currentFrame = newValue } + get { animationView.currentFrame } + } + + @objc + public var realtimeAnimationFrame: CGFloat { + animationView.realtimeAnimationFrame + } + + @objc + public var realtimeAnimationProgress: CGFloat { + animationView.realtimeAnimationProgress + } + + @objc + public var animationSpeed: CGFloat { + set { animationView.animationSpeed = newValue } + get { animationView.animationSpeed } + } + + @objc + public var respectAnimationFrameRate: Bool { + set { animationView.respectAnimationFrameRate = newValue } + get { animationView.respectAnimationFrameRate } + } + + @objc + public var isAnimationPlaying: Bool { + animationView.isAnimationPlaying + } + + @objc + public func play() { + play(completion: nil) + } + + @objc + public func play(completion: ((Bool) -> Void)?) { + animationView.play(completion: completion) + } + + @objc + public func play( + fromProgress: CGFloat, + toProgress: CGFloat, + completion: ((Bool) -> Void)? = nil) + { + animationView.play( + fromProgress: fromProgress, + toProgress: toProgress, + loopMode: nil, + completion: completion) + } + + @objc + public func play( + fromFrame: CGFloat, + toFrame: CGFloat, + completion: ((Bool) -> Void)? = nil) + { + animationView.play( + fromFrame: fromFrame, + toFrame: toFrame, + loopMode: nil, + completion: completion) + } + + @objc + public func play( + fromMarker: String, + toMarker: String, + completion: ((Bool) -> Void)? = nil) + { + animationView.play( + fromMarker: fromMarker, + toMarker: toMarker, + completion: completion) + } + + @objc + public func stop() { + animationView.stop() + } + + @objc + public func pause() { + animationView.pause() + } + + @objc + public func reloadImages() { + animationView.reloadImages() + } + + @objc + public func forceDisplayUpdate() { + animationView.forceDisplayUpdate() + } + + @objc + public func getValue( + for keypath: CompatibleAnimationKeypath, + atFrame: CGFloat) + -> Any? + { + animationView.getValue( + for: keypath.animationKeypath, + atFrame: atFrame) + } + + @objc + public func logHierarchyKeypaths() { + animationView.logHierarchyKeypaths() + } + + @objc + public func setColorValue(_ color: UIColor, forKeypath keypath: CompatibleAnimationKeypath) { + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + // TODO: Fix color spaces + let colorspace = CGColorSpaceCreateDeviceRGB() + + let convertedColor = color.cgColor.converted(to: colorspace, intent: .defaultIntent, options: nil) + + if let components = convertedColor?.components, components.count == 4 { + red = components[0] + green = components[1] + blue = components[2] + alpha = components[3] + } else { + color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + } + + let valueProvider = ColorValueProvider(Color(r: Double(red), g: Double(green), b: Double(blue), a: Double(alpha))) + animationView.setValueProvider(valueProvider, keypath: keypath.animationKeypath) + } + + @objc + public func getColorValue(for keypath: CompatibleAnimationKeypath, atFrame: CGFloat) -> UIColor? { + let value = animationView.getValue(for: keypath.animationKeypath, atFrame: atFrame) + guard let colorValue = value as? Color else { + return nil; + } + + return UIColor( + red: CGFloat(colorValue.r), + green: CGFloat(colorValue.g), + blue: CGFloat(colorValue.b), + alpha: CGFloat(colorValue.a)) + } + + @objc + public func addSubview( + _ subview: AnimationSubview, + forLayerAt keypath: CompatibleAnimationKeypath) + { + animationView.addSubview( + subview, + forLayerAt: keypath.animationKeypath) + } + + @objc + public func convert( + rect: CGRect, + toLayerAt keypath: CompatibleAnimationKeypath?) + -> CGRect + { + animationView.convert( + rect, + toLayerAt: keypath?.animationKeypath) ?? .zero + } + + @objc + public func convert( + point: CGPoint, + toLayerAt keypath: CompatibleAnimationKeypath?) + -> CGPoint + { + animationView.convert( + point, + toLayerAt: keypath?.animationKeypath) ?? .zero + } + + @objc + public func progressTime(forMarker named: String) -> CGFloat { + animationView.progressTime(forMarker: named) ?? 0 + } + + @objc + public func frameTime(forMarker named: String) -> CGFloat { + animationView.frameTime(forMarker: named) ?? 0 + } + + // MARK: Private + + private let animationView: AnimationView + + private func commonInit() { + translatesAutoresizingMaskIntoConstraints = false + setUpViews() + } + + private func setUpViews() { + animationView.translatesAutoresizingMaskIntoConstraints = false + addSubview(animationView) + animationView.topAnchor.constraint(equalTo: topAnchor).isActive = true + animationView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + animationView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + animationView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } +} +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/FilepathImageProvider.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/FilepathImageProvider.swift new file mode 100644 index 00000000000..4fe3db0d758 --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/FilepathImageProvider.swift @@ -0,0 +1,60 @@ +// +// FilepathImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/1/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) +import UIKit + +/// Provides an image for a lottie animation from a provided Bundle. +public class FilepathImageProvider: AnimationImageProvider { + + // MARK: Lifecycle + + /// Initializes an image provider with a specific filepath. + /// + /// - Parameter filepath: The absolute filepath containing the images. + /// + public init(filepath: String) { + self.filepath = URL(fileURLWithPath: filepath) + } + + public init(filepath: URL) { + self.filepath = filepath + } + + // MARK: Public + + public func imageForAsset(asset: ImageAsset) -> CGImage? { + + if + asset.name.hasPrefix("data:"), + let url = URL(string: asset.name), + let data = try? Data(contentsOf: url), + let image = UIImage(data: data) + { + return image.cgImage + } + + let directPath = filepath.appendingPathComponent(asset.name).path + if FileManager.default.fileExists(atPath: directPath) { + return UIImage(contentsOfFile: directPath)?.cgImage + } + + let pathWithDirectory = filepath.appendingPathComponent(asset.directory).appendingPathComponent(asset.name).path + if FileManager.default.fileExists(atPath: pathWithDirectory) { + return UIImage(contentsOfFile: pathWithDirectory)?.cgImage + } + + LottieLogger.shared.assertionFailure("Could not find image \"\(asset.name)\" in bundle") + return nil + } + + // MARK: Internal + + let filepath: URL +} +#endif diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/UIColorExtension.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/UIColorExtension.swift new file mode 100644 index 00000000000..4cf335e059e --- /dev/null +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Public/iOS/UIColorExtension.swift @@ -0,0 +1,21 @@ +// +// UIColorExtension.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) +import UIKit + +extension UIColor { + + public var lottieColorValue: Color { + var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 + getRed(&r, green: &g, blue: &b, alpha: &a) + return Color(r: Double(r), g: Double(g), b: Double(b), a: Double(a)) + } + +} +#endif diff --git a/Tests/LottieMetalTest/MacSources/ViewController.swift b/Tests/LottieMetalTest/MacSources/ViewController.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Tests/LottieMetalTest/QOILoader/BUILD b/Tests/LottieMetalTest/QOILoader/BUILD new file mode 100644 index 00000000000..0c3f6ac18ea --- /dev/null +++ b/Tests/LottieMetalTest/QOILoader/BUILD @@ -0,0 +1,33 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +objc_library( + name = "QOILoader", + enable_modules = True, + module_name = "QOILoader", + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.mm", + "Sources/**/*.h", + "Sources/**/*.c", + "Sources/**/*.cpp", + "Sources/**/*.hpp", + ]), + copts = [ + "-Werror", + "-I{}/Sources".format(package_name()), + ], + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + deps = [ + ], + sdk_frameworks = [ + "Foundation", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/Tests/LottieMetalTest/QOILoader/PublicHeaders/QOILoader/QOILoader.h b/Tests/LottieMetalTest/QOILoader/PublicHeaders/QOILoader/QOILoader.h new file mode 100644 index 00000000000..c2c92fb90e3 --- /dev/null +++ b/Tests/LottieMetalTest/QOILoader/PublicHeaders/QOILoader/QOILoader.h @@ -0,0 +1,10 @@ +#ifndef QOILoader_h +#define QOILoader_h + +#import +#import + +NSData * _Nullable encodeImageQOI(UIImage * _Nonnull image); +UIImage * _Nullable decodeImageQOI(NSData * _Nonnull data); + +#endif /* QOILoader_h */ diff --git a/Tests/LottieMetalTest/QOILoader/Sources/QOILoader.m b/Tests/LottieMetalTest/QOILoader/Sources/QOILoader.m new file mode 100644 index 00000000000..6bc35b87a65 --- /dev/null +++ b/Tests/LottieMetalTest/QOILoader/Sources/QOILoader.m @@ -0,0 +1,64 @@ +#import + +#define QOI_IMPLEMENTATION +#import "qoi.h" + +NSData * _Nullable encodeImageQOI(UIImage * _Nonnull image) { + if (image == nil) { + return nil; + } + CGImageRef cgImage = image.CGImage; + if (cgImage == nil) { + return nil; + } + CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage); + if (dataProvider == nil) { + return nil; + } + NSData *data = (__bridge_transfer NSData *)CGDataProviderCopyData(dataProvider); + if (data == nil) { + return nil; + } + if (CGImageGetBitsPerPixel(cgImage) / CGImageGetBitsPerComponent(cgImage) != 4) { + return nil; + } + + int outLength = 0; + void *outBytes = qoi_encode(data.bytes, &(qoi_desc){ + .width = (unsigned int)CGImageGetWidth(cgImage), + .height = (unsigned int)CGImageGetHeight(cgImage), + .channels = 4, + .colorspace = QOI_SRGB + }, &outLength); + if (outBytes == nil) { + return nil; + } + return [[NSData alloc] initWithBytesNoCopy:outBytes length:outLength freeWhenDone:true]; +} + +static void releaseMallocedMemory(void *info, const void *data, size_t size) { + free((void *)data); +} + +UIImage * _Nullable decodeImageQOI(NSData * _Nonnull data) { + qoi_desc desc; + void *outPixels = qoi_decode(data.bytes, (int)data.length, &desc, 4); + if (outPixels == nil) { + return nil; + } + + CGDataProviderRef dataProvider = CGDataProviderCreateWithData(nil, outPixels, desc.width * 4 * desc.height, releaseMallocedMemory); + if (dataProvider == nil) { + free(outPixels); + return nil; + } + + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; + CGImageRef cgImage = CGImageCreate(desc.width, desc.height, 8, 8 * 4, desc.width * 4, CGColorSpaceCreateDeviceRGB(), bitmapInfo, dataProvider, nil, false, kCGRenderingIntentDefault); + CGDataProviderRelease(dataProvider); + + UIImage *image = [[UIImage alloc] initWithCGImage:cgImage]; + CGImageRelease(cgImage); + + return image; +} diff --git a/Tests/LottieMetalTest/QOILoader/Sources/qoi.h b/Tests/LottieMetalTest/QOILoader/Sources/qoi.h new file mode 100644 index 00000000000..3c38fd4de25 --- /dev/null +++ b/Tests/LottieMetalTest/QOILoader/Sources/qoi.h @@ -0,0 +1,649 @@ +/* + +Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT + + +QOI - The "Quite OK Image" format for fast, lossless image compression + +-- About + +QOI encodes and decodes images in a lossless format. Compared to stb_image and +stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and +20% better compression. + + +-- Synopsis + +// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this +// library to create the implementation. + +#define QOI_IMPLEMENTATION +#include "qoi.h" + +// Encode and store an RGBA buffer to the file system. The qoi_desc describes +// the input pixel data. +qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){ + .width = 1920, + .height = 1080, + .channels = 4, + .colorspace = QOI_SRGB +}); + +// Load and decode a QOI image from the file system into a 32bbp RGBA buffer. +// The qoi_desc struct will be filled with the width, height, number of channels +// and colorspace read from the file header. +qoi_desc desc; +void *rgba_pixels = qoi_read("image.qoi", &desc, 4); + + + +-- Documentation + +This library provides the following functions; +- qoi_read -- read and decode a QOI file +- qoi_decode -- decode the raw bytes of a QOI image from memory +- qoi_write -- encode and write a QOI file +- qoi_encode -- encode an rgba buffer into a QOI image in memory + +See the function declaration below for the signature and more information. + +If you don't want/need the qoi_read and qoi_write functions, you can define +QOI_NO_STDIO before including this library. + +This library uses malloc() and free(). To supply your own malloc implementation +you can define QOI_MALLOC and QOI_FREE before including this library. + +This library uses memset() to zero-initialize the index. To supply your own +implementation you can define QOI_ZEROARR before including this library. + + +-- Data Format + +A QOI file has a 14 byte header, followed by any number of data "chunks" and an +8-byte end marker. + +struct qoi_header_t { + char magic[4]; // magic bytes "qoif" + uint32_t width; // image width in pixels (BE) + uint32_t height; // image height in pixels (BE) + uint8_t channels; // 3 = RGB, 4 = RGBA + uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear +}; + +Images are encoded row by row, left to right, top to bottom. The decoder and +encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An +image is complete when all pixels specified by width * height have been covered. + +Pixels are encoded as + - a run of the previous pixel + - an index into an array of previously seen pixels + - a difference to the previous pixel value in r,g,b + - full r,g,b or r,g,b,a values + +The color channels are assumed to not be premultiplied with the alpha channel +("un-premultiplied alpha"). + +A running array[64] (zero-initialized) of previously seen pixel values is +maintained by the encoder and decoder. Each pixel that is seen by the encoder +and decoder is put into this array at the position formed by a hash function of +the color value. In the encoder, if the pixel value at the index matches the +current pixel, this index position is written to the stream as QOI_OP_INDEX. +The hash function for the index is: + + index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64 + +Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The +bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All +values encoded in these data bits have the most significant bit on the left. + +The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the +presence of an 8-bit tag first. + +The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. + + +The possible chunks are: + + +.- QOI_OP_INDEX ----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 0 0 | index | +`-------------------------` +2-bit tag b00 +6-bit index into the color index array: 0..63 + +A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the +same index. QOI_OP_RUN should be used instead. + + +.- QOI_OP_DIFF -----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----+-----+-----| +| 0 1 | dr | dg | db | +`-------------------------` +2-bit tag b01 +2-bit red channel difference from the previous pixel between -2..1 +2-bit green channel difference from the previous pixel between -2..1 +2-bit blue channel difference from the previous pixel between -2..1 + +The difference to the current channel values are using a wraparound operation, +so "1 - 2" will result in 255, while "255 + 1" will result in 0. + +Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as +0 (b00). 1 is stored as 3 (b11). + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_LUMA -------------------------------------. +| Byte[0] | Byte[1] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | +|-------+-----------------+-------------+-----------| +| 1 0 | green diff | dr - dg | db - dg | +`---------------------------------------------------` +2-bit tag b10 +6-bit green channel difference from the previous pixel -32..31 +4-bit red channel difference minus green channel difference -8..7 +4-bit blue channel difference minus green channel difference -8..7 + +The green channel is used to indicate the general direction of change and is +encoded in 6 bits. The red and blue channels (dr and db) base their diffs off +of the green channel difference and are encoded in 4 bits. I.e.: + dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) + db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) + +The difference to the current channel values are using a wraparound operation, +so "10 - 13" will result in 253, while "250 + 7" will result in 1. + +Values are stored as unsigned integers with a bias of 32 for the green channel +and a bias of 8 for the red and blue channel. + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RUN ------------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 1 1 | run | +`-------------------------` +2-bit tag b11 +6-bit run-length repeating the previous pixel: 1..62 + +The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64 +(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and +QOI_OP_RGBA tags. + + +.- QOI_OP_RGB ------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------| +| 1 1 1 1 1 1 1 0 | red | green | blue | +`-------------------------------------------------------` +8-bit tag b11111110 +8-bit red channel value +8-bit green channel value +8-bit blue channel value + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RGBA ---------------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------+---------| +| 1 1 1 1 1 1 1 1 | red | green | blue | alpha | +`-----------------------------------------------------------------` +8-bit tag b11111111 +8-bit red channel value +8-bit green channel value +8-bit blue channel value +8-bit alpha channel value + +*/ + + +/* ----------------------------------------------------------------------------- +Header - Public functions */ + +#ifndef QOI_H +#define QOI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions. +It describes either the input format (for qoi_write and qoi_encode), or is +filled with the description read from the file header (for qoi_read and +qoi_decode). + +The colorspace in this qoi_desc is an enum where + 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel + 1 = all channels are linear +You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely +informative. It will be saved to the file header, but does not affect +how chunks are en-/decoded. */ + +#define QOI_SRGB 0 +#define QOI_LINEAR 1 + +typedef struct { + unsigned int width; + unsigned int height; + unsigned char channels; + unsigned char colorspace; +} qoi_desc; + +#ifndef QOI_NO_STDIO + +/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file +system. The qoi_desc struct must be filled with the image width, height, +number of channels (3 = RGB, 4 = RGBA) and the colorspace. + +The function returns 0 on failure (invalid parameters, or fopen or malloc +failed) or the number of bytes written on success. */ + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc); + + +/* Read and decode a QOI image from the file system. If channels is 0, the +number of channels from the file header is used. If channels is 3 or 4 the +output format will be forced into this number of channels. + +The function either returns NULL on failure (invalid data, or malloc or fopen +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +will be filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_read(const char *filename, qoi_desc *desc, int channels); + +#endif /* QOI_NO_STDIO */ + + +/* Encode raw RGB or RGBA pixels into a QOI image in memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the encoded data on success. On success the out_len +is set to the size in bytes of the encoded data. + +The returned qoi data should be free()d after use. */ + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len); + + +/* Decode a QOI image from memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +is filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels); + + +#ifdef __cplusplus +} +#endif +#endif /* QOI_H */ + + +/* ----------------------------------------------------------------------------- +Implementation */ + +#ifdef QOI_IMPLEMENTATION +#include +#include + +#ifndef QOI_MALLOC + #define QOI_MALLOC(sz) malloc(sz) + #define QOI_FREE(p) free(p) +#endif +#ifndef QOI_ZEROARR + #define QOI_ZEROARR(a) memset((a),0,sizeof(a)) +#endif + +#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ +#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ +#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ +#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ +#define QOI_OP_RGB 0xfe /* 11111110 */ +#define QOI_OP_RGBA 0xff /* 11111111 */ + +#define QOI_MASK_2 0xc0 /* 11000000 */ + +#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) +#define QOI_MAGIC \ + (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ + ((unsigned int)'i') << 8 | ((unsigned int)'f')) +#define QOI_HEADER_SIZE 14 + +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) + +typedef union { + struct { unsigned char r, g, b, a; } rgba; + unsigned int v; +} qoi_rgba_t; + +static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; + +static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { + bytes[(*p)++] = (0xff000000 & v) >> 24; + bytes[(*p)++] = (0x00ff0000 & v) >> 16; + bytes[(*p)++] = (0x0000ff00 & v) >> 8; + bytes[(*p)++] = (0x000000ff & v); +} + +static unsigned int qoi_read_32(const unsigned char *bytes, int *p) { + unsigned int a = bytes[(*p)++]; + unsigned int b = bytes[(*p)++]; + unsigned int c = bytes[(*p)++]; + unsigned int d = bytes[(*p)++]; + return a << 24 | b << 16 | c << 8 | d; +} + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { + int i, max_size, p, run; + int px_len, px_end, px_pos, channels; + unsigned char *bytes; + const unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px, px_prev; + + if ( + data == NULL || out_len == NULL || desc == NULL || + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || + desc->height >= QOI_PIXELS_MAX / desc->width + ) { + return NULL; + } + + max_size = + desc->width * desc->height * (desc->channels + 1) + + QOI_HEADER_SIZE + sizeof(qoi_padding); + + p = 0; + bytes = (unsigned char *) QOI_MALLOC(max_size); + if (!bytes) { + return NULL; + } + + qoi_write_32(bytes, &p, QOI_MAGIC); + qoi_write_32(bytes, &p, desc->width); + qoi_write_32(bytes, &p, desc->height); + bytes[p++] = desc->channels; + bytes[p++] = desc->colorspace; + + + pixels = (const unsigned char *)data; + + QOI_ZEROARR(index); + + run = 0; + px_prev.rgba.r = 0; + px_prev.rgba.g = 0; + px_prev.rgba.b = 0; + px_prev.rgba.a = 255; + px = px_prev; + + px_len = desc->width * desc->height * desc->channels; + px_end = px_len - desc->channels; + channels = desc->channels; + + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + px.rgba.r = pixels[px_pos + 0]; + px.rgba.g = pixels[px_pos + 1]; + px.rgba.b = pixels[px_pos + 2]; + + if (channels == 4) { + px.rgba.a = pixels[px_pos + 3]; + } + + if (px.v == px_prev.v) { + run++; + if (run == 62 || px_pos == px_end) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + } + else { + int index_pos; + + if (run > 0) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + + index_pos = QOI_COLOR_HASH(px) % 64; + + if (index[index_pos].v == px.v) { + bytes[p++] = QOI_OP_INDEX | index_pos; + } + else { + index[index_pos] = px; + + if (px.rgba.a == px_prev.rgba.a) { + signed char vr = px.rgba.r - px_prev.rgba.r; + signed char vg = px.rgba.g - px_prev.rgba.g; + signed char vb = px.rgba.b - px_prev.rgba.b; + + signed char vg_r = vr - vg; + signed char vg_b = vb - vg; + + if ( + vr > -3 && vr < 2 && + vg > -3 && vg < 2 && + vb > -3 && vb < 2 + ) { + bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2); + } + else if ( + vg_r > -9 && vg_r < 8 && + vg > -33 && vg < 32 && + vg_b > -9 && vg_b < 8 + ) { + bytes[p++] = QOI_OP_LUMA | (vg + 32); + bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8); + } + else { + bytes[p++] = QOI_OP_RGB; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + } + } + else { + bytes[p++] = QOI_OP_RGBA; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + bytes[p++] = px.rgba.a; + } + } + } + px_prev = px; + } + + for (i = 0; i < (int)sizeof(qoi_padding); i++) { + bytes[p++] = qoi_padding[i]; + } + + *out_len = p; + return bytes; +} + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { + const unsigned char *bytes; + unsigned int header_magic; + unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px; + int px_len, chunks_len, px_pos; + int p = 0, run = 0; + + if ( + data == NULL || desc == NULL || + (channels != 0 && channels != 3 && channels != 4) || + size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) + ) { + return NULL; + } + + bytes = (const unsigned char *)data; + + header_magic = qoi_read_32(bytes, &p); + desc->width = qoi_read_32(bytes, &p); + desc->height = qoi_read_32(bytes, &p); + desc->channels = bytes[p++]; + desc->colorspace = bytes[p++]; + + if ( + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || + header_magic != QOI_MAGIC || + desc->height >= QOI_PIXELS_MAX / desc->width + ) { + return NULL; + } + + if (channels == 0) { + channels = desc->channels; + } + + px_len = desc->width * desc->height * channels; + pixels = (unsigned char *) QOI_MALLOC(px_len); + if (!pixels) { + return NULL; + } + + QOI_ZEROARR(index); + px.rgba.r = 0; + px.rgba.g = 0; + px.rgba.b = 0; + px.rgba.a = 255; + + chunks_len = size - (int)sizeof(qoi_padding); + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (run > 0) { + run--; + } + else if (p < chunks_len) { + int b1 = bytes[p++]; + + if (b1 == QOI_OP_RGB) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + } + else if (b1 == QOI_OP_RGBA) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + px.rgba.a = bytes[p++]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { + px.rgba.r += ((b1 >> 4) & 0x03) - 2; + px.rgba.g += ((b1 >> 2) & 0x03) - 2; + px.rgba.b += ( b1 & 0x03) - 2; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { + int b2 = bytes[p++]; + int vg = (b1 & 0x3f) - 32; + px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); + px.rgba.g += vg; + px.rgba.b += vg - 8 + (b2 & 0x0f); + } + else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); + } + + index[QOI_COLOR_HASH(px) % 64] = px; + } + + pixels[px_pos + 0] = px.rgba.r; + pixels[px_pos + 1] = px.rgba.g; + pixels[px_pos + 2] = px.rgba.b; + + if (channels == 4) { + pixels[px_pos + 3] = px.rgba.a; + } + } + + return pixels; +} + +#ifndef QOI_NO_STDIO +#include + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc) { + FILE *f = fopen(filename, "wb"); + int size, err; + void *encoded; + + if (!f) { + return 0; + } + + encoded = qoi_encode(data, desc, &size); + if (!encoded) { + fclose(f); + return 0; + } + + fwrite(encoded, 1, size, f); + fflush(f); + err = ferror(f); + fclose(f); + + QOI_FREE(encoded); + return err ? 0 : size; +} + +void *qoi_read(const char *filename, qoi_desc *desc, int channels) { + FILE *f = fopen(filename, "rb"); + int size, bytes_read; + void *pixels, *data; + + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + size = (int)ftell(f); + if (size <= 0 || fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + return NULL; + } + + data = QOI_MALLOC(size); + if (!data) { + fclose(f); + return NULL; + } + + bytes_read = (int)fread(data, 1, size, f); + fclose(f); + pixels = (bytes_read != size) ? NULL : qoi_decode(data, bytes_read, desc, channels); + QOI_FREE(data); + return pixels; +} + +#endif /* QOI_NO_STDIO */ +#endif /* QOI_IMPLEMENTATION */ diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/BUILD b/Tests/LottieMetalTest/SoftwareLottieRenderer/BUILD new file mode 100644 index 00000000000..0aedc6e7b23 --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/BUILD @@ -0,0 +1,35 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +objc_library( + name = "SoftwareLottieRenderer", + enable_modules = True, + module_name = "SoftwareLottieRenderer", + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.mm", + "Sources/**/*.h", + "Sources/**/*.c", + "Sources/**/*.cpp", + "Sources/**/*.hpp", + ]), + copts = [ + "-Werror", + "-I{}/Sources".format(package_name()), + ], + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + deps = [ + "//submodules/TelegramUI/Components/LottieCpp", + "//Tests/LottieMetalTest/thorvg", + ], + sdk_frameworks = [ + "Foundation", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h new file mode 100644 index 00000000000..42939022507 --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h @@ -0,0 +1,27 @@ +#ifndef SoftwareLottieRenderer_h +#define SoftwareLottieRenderer_h + +#import +#import + +#import + +#ifdef __cplusplus +extern "C" { +#endif + +CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path); + +@interface SoftwareLottieRenderer : NSObject + +- (instancetype _Nonnull)initWithAnimationContainer:(LottieAnimationContainer * _Nonnull)animationContainer; + +- (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering; + +@end + +#ifdef __cplusplus +} +#endif + +#endif /* QOILoader_h */ diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h new file mode 100644 index 00000000000..32a02bfc8aa --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h @@ -0,0 +1,92 @@ +#ifndef Canvas_h +#define Canvas_h + +#include + +#include +#include +#include +#include + +namespace lottieRendering { + +class Image { +public: + virtual ~Image() = default; +}; + +class Gradient { +public: + Gradient(std::vector const &colors, std::vector const &locations) : + _colors(colors), + _locations(locations) { + assert(_colors.size() == _locations.size()); + } + + std::vector const &colors() const { + return _colors; + } + + std::vector const &locations() const { + return _locations; + } + +private: + std::vector _colors; + std::vector _locations; +}; + +enum class BlendMode { + Normal, + DestinationIn, + DestinationOut +}; + +enum class PathCommandType { + MoveTo, + LineTo, + CurveTo, + Close +}; + +typedef struct { + PathCommandType type; + CGPoint points[4]; +} PathCommand; + +typedef std::function)> CanvasPathEnumerator; + +class Canvas { +public: + virtual ~Canvas() = default; + + virtual int width() const = 0; + virtual int height() const = 0; + + virtual std::shared_ptr makeLayer(int width, int height) = 0; + + virtual void saveState() = 0; + virtual void restoreState() = 0; + + virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) = 0; + virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) = 0; + virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) = 0; + + virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) = 0; + virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) = 0; + virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) = 0; + + virtual void fill(lottie::CGRect const &rect, lottie::Color const &fillColor) = 0; + virtual void setBlendMode(BlendMode blendMode) = 0; + + virtual void setAlpha(float alpha) = 0; + + virtual void concatenate(lottie::Transform2D const &transform) = 0; + + virtual void draw(std::shared_ptr const &other, lottie::CGRect const &rect) = 0; +}; + +} + +#endif + diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h new file mode 100644 index 00000000000..db8064458a5 --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h @@ -0,0 +1,72 @@ +#ifndef CoreGraphicsCanvasImpl_h +#define CoreGraphicsCanvasImpl_h + +#include "Canvas.h" + +namespace lottieRendering { + +class ImageImpl: public Image { +public: + ImageImpl(::CGImageRef image); + virtual ~ImageImpl(); + ::CGImageRef nativeImage() const; + +private: + CGImageRef _image = nil; +}; + +class CanvasImpl: public Canvas { +public: + CanvasImpl(int width, int height); + CanvasImpl(CGContextRef context, int width, int height); + virtual ~CanvasImpl(); + + virtual int width() const override; + virtual int height() const override; + + std::shared_ptr makeLayer(int width, int height) override; + + virtual void saveState() override; + virtual void restoreState() override; + + virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; + virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + + virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) override; + virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + + virtual void fill(lottie::CGRect const &rect, lottie::Color const &fillColor) override; + virtual void setBlendMode(BlendMode blendMode) override; + virtual void setAlpha(float alpha) override; + virtual void concatenate(lottie::Transform2D const &transform) override; + + virtual std::shared_ptr makeImage() const; + virtual void draw(std::shared_ptr const &other, lottie::CGRect const &rect) override; + + CGContextRef nativeContext() const { + return _context; + } + + std::vector &backingData() { + return _backingData; + } + + int bytesPerRow() { + return _bytesPerRow; + } + +private: + int _width = 0; + int _height = 0; + int _bytesPerRow = 0; + std::vector _backingData; + CGContextRef _context = nil; + CGContextRef _topContext = nil; + CGLayerRef _layer = nil; +}; + +} + +#endif diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm new file mode 100644 index 00000000000..38f95beb724 --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm @@ -0,0 +1,538 @@ +#include "CoreGraphicsCanvasImpl.h" + +namespace lottieRendering { + +namespace { + +int alignUp(int size, int align) { + assert(((align - 1) & align) == 0); + + int alignmentMask = align - 1; + return (size + alignmentMask) & ~alignmentMask; +} + +bool addEnumeratedPath(CGContextRef context, CanvasPathEnumerator const &enumeratePath) { + bool isEmpty = true; + + enumeratePath([&](PathCommand const &command) { + switch (command.type) { + case PathCommandType::MoveTo: { + if (isEmpty) { + isEmpty = false; + CGContextBeginPath(context); + } + CGContextMoveToPoint(context, command.points[0].x, command.points[0].y); + break; + } + case PathCommandType::LineTo: { + if (isEmpty) { + isEmpty = false; + CGContextBeginPath(context); + } + CGContextAddLineToPoint(context, command.points[0].x, command.points[0].y); + break; + } + case PathCommandType::CurveTo: { + if (isEmpty) { + isEmpty = false; + CGContextBeginPath(context); + } + CGContextAddCurveToPoint(context, command.points[0].x, command.points[0].y, command.points[1].x, command.points[1].y, command.points[2].x, command.points[2].y); + break; + } + case PathCommandType::Close: { + if (isEmpty) { + isEmpty = false; + CGContextBeginPath(context); + } + CGContextClosePath(context); + break; + } + default: { + break; + } + } + }); + + return !isEmpty; +} + +} + +ImageImpl::ImageImpl(::CGImageRef image) { + _image = CGImageRetain(image); +} + +ImageImpl::~ImageImpl() { + CFRelease(_image); +} + +::CGImageRef ImageImpl::nativeImage() const { + return _image; +} + + +CanvasImpl::CanvasImpl(int width, int height) { + _width = width; + _height = height; + _bytesPerRow = alignUp(width * 4, 16); + _backingData.resize(_bytesPerRow * _height); + memset(_backingData.data(), 0, _backingData.size()); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; + _context = CGBitmapContextCreate(_backingData.data(), _width, _height, 8, _bytesPerRow, colorSpace, bitmapInfo); + + CGContextClearRect(_context, CGRectMake(0.0, 0.0, _width, _height)); + + //CGContextSetInterpolationQuality(_context, kCGInterpolationLow); + //CGContextSetAllowsAntialiasing(_context, true); + //CGContextSetShouldAntialias(_context, true); + + CFRelease(colorSpace); + + _topContext = CGContextRetain(_context); +} + +CanvasImpl::CanvasImpl(CGContextRef context, int width, int height) { + _topContext = CGContextRetain(context); + _layer = CGLayerCreateWithContext(context, CGSizeMake(width, height), nil); + _context = CGContextRetain(CGLayerGetContext(_layer)); + _width = width; + _height = height; +} + +CanvasImpl::~CanvasImpl() { + CFRelease(_context); + if (_topContext) { + CFRelease(_topContext); + } + if (_layer) { + CFRelease(_layer); + } +} + +int CanvasImpl::width() const { + return _width; +} + +int CanvasImpl::height() const { + return _height; +} + +std::shared_ptr CanvasImpl::makeLayer(int width, int height) { + return std::make_shared(_topContext, width, height); +} + +void CanvasImpl::saveState() { + CGContextSaveGState(_context); +} + +void CanvasImpl::restoreState() { + CGContextRestoreGState(_context); +} + +void CanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) { + if (!addEnumeratedPath(_context, enumeratePath)) { + return; + } + + CGFloat components[4] = { color.r, color.g, color.b, color.a }; + CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); + CGContextSetFillColorWithColor(_context, nativeColor); + CFRelease(nativeColor); + + switch (fillRule) { + case lottie::FillRule::EvenOdd: { + CGContextEOFillPath(_context); + break; + } + default: { + CGContextFillPath(_context); + break; + } + } +} + +void CanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { + CGContextSaveGState(_context); + + if (!addEnumeratedPath(_context, enumeratePath)) { + CGContextRestoreGState(_context); + return; + } + + switch (fillRule) { + case lottie::FillRule::EvenOdd: { + CGContextEOClip(_context); + break; + } + default: { + CGContextClip(_context); + break; + } + } + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + std::vector locations; + for (const auto location : gradient.locations()) { + locations.push_back(location); + } + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); + if (nativeGradient) { + CGContextDrawLinearGradient(_context, nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { + CGContextSaveGState(_context); + + if (!addEnumeratedPath(_context, enumeratePath)) { + CGContextRestoreGState(_context); + return; + } + + switch (fillRule) { + case lottie::FillRule::EvenOdd: { + CGContextEOClip(_context); + break; + } + default: { + CGContextClip(_context); + break; + } + } + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + std::vector locations; + for (const auto location : gradient.locations()) { + locations.push_back(location); + } + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); + if (nativeGradient) { + CGContextDrawRadialGradient(_context, nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) { + if (!addEnumeratedPath(_context, enumeratePath)) { + return; + } + + CGFloat components[4] = { color.r, color.g, color.b, color.a }; + CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); + CGContextSetStrokeColorWithColor(_context, nativeColor); + CFRelease(nativeColor); + + CGContextSetLineWidth(_context, lineWidth); + + switch (lineJoin) { + case lottie::LineJoin::Miter: { + CGContextSetLineJoin(_context, kCGLineJoinMiter); + break; + } + case lottie::LineJoin::Round: { + CGContextSetLineJoin(_context, kCGLineJoinRound); + break; + } + case lottie::LineJoin::Bevel: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + default: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + } + + switch (lineCap) { + case lottie::LineCap::Butt: { + CGContextSetLineCap(_context, kCGLineCapButt); + break; + } + case lottie::LineCap::Round: { + CGContextSetLineCap(_context, kCGLineCapRound); + break; + } + case lottie::LineCap::Square: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + default: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + } + + if (!dashPattern.empty()) { + std::vector mappedDashPattern; + for (const auto value : dashPattern) { + mappedDashPattern.push_back(value); + } + CGContextSetLineDash(_context, dashPhase, mappedDashPattern.data(), mappedDashPattern.size()); + } + CGContextStrokePath(_context); +} + +void CanvasImpl::linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { + CGContextSaveGState(_context); + if (!addEnumeratedPath(_context, enumeratePath)) { + CGContextRestoreGState(_context); + return; + } + + CGContextSetLineWidth(_context, lineWidth); + + switch (lineJoin) { + case lottie::LineJoin::Miter: { + CGContextSetLineJoin(_context, kCGLineJoinMiter); + break; + } + case lottie::LineJoin::Round: { + CGContextSetLineJoin(_context, kCGLineJoinRound); + break; + } + case lottie::LineJoin::Bevel: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + default: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + } + + switch (lineCap) { + case lottie::LineCap::Butt: { + CGContextSetLineCap(_context, kCGLineCapButt); + break; + } + case lottie::LineCap::Round: { + CGContextSetLineCap(_context, kCGLineCapRound); + break; + } + case lottie::LineCap::Square: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + default: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + } + + if (!dashPattern.empty()) { + std::vector mappedDashPattern; + for (const auto value : dashPattern) { + mappedDashPattern.push_back(value); + } + CGContextSetLineDash(_context, dashPhase, mappedDashPattern.data(), mappedDashPattern.size()); + } + + CGContextReplacePathWithStrokedPath(_context); + CGContextClip(_context); + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + std::vector locations; + for (const auto location : gradient.locations()) { + locations.push_back(location); + } + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); + if (nativeGradient) { + CGContextDrawLinearGradient(_context, nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { + CGContextSaveGState(_context); + if (!addEnumeratedPath(_context, enumeratePath)) { + CGContextRestoreGState(_context); + return; + } + + CGContextSetLineWidth(_context, lineWidth); + + switch (lineJoin) { + case lottie::LineJoin::Miter: { + CGContextSetLineJoin(_context, kCGLineJoinMiter); + break; + } + case lottie::LineJoin::Round: { + CGContextSetLineJoin(_context, kCGLineJoinRound); + break; + } + case lottie::LineJoin::Bevel: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + default: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + } + + switch (lineCap) { + case lottie::LineCap::Butt: { + CGContextSetLineCap(_context, kCGLineCapButt); + break; + } + case lottie::LineCap::Round: { + CGContextSetLineCap(_context, kCGLineCapRound); + break; + } + case lottie::LineCap::Square: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + default: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + } + + if (!dashPattern.empty()) { + std::vector mappedDashPattern; + for (const auto value : dashPattern) { + mappedDashPattern.push_back(value); + } + CGContextSetLineDash(_context, dashPhase, mappedDashPattern.data(), mappedDashPattern.size()); + } + + CGContextReplacePathWithStrokedPath(_context); + CGContextClip(_context); + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + std::vector locations; + for (const auto location : gradient.locations()) { + locations.push_back(location); + } + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); + if (nativeGradient) { + CGContextDrawRadialGradient(_context, nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) { + CGFloat components[4] = { fillColor.r, fillColor.g, fillColor.b, fillColor.a }; + CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); + CGContextSetFillColorWithColor(_context, nativeColor); + CFRelease(nativeColor); + + CGContextFillRect(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height)); +} + +void CanvasImpl::setBlendMode(BlendMode blendMode) { + ::CGBlendMode nativeMode = kCGBlendModeNormal; + switch (blendMode) { + case BlendMode::Normal: { + nativeMode = kCGBlendModeNormal; + break; + } + case BlendMode::DestinationIn: { + nativeMode = kCGBlendModeDestinationIn; + break; + } + case BlendMode::DestinationOut: { + nativeMode = kCGBlendModeDestinationOut; + break; + } + } + CGContextSetBlendMode(_context, nativeMode); +} + +void CanvasImpl::setAlpha(float alpha) { + CGContextSetAlpha(_context, alpha); +} + +void CanvasImpl::concatenate(lottie::Transform2D const &transform) { + CGContextConcatCTM(_context, CATransform3DGetAffineTransform(nativeTransform(transform))); +} + +std::shared_ptr CanvasImpl::makeImage() const { + ::CGImageRef nativeImage = CGBitmapContextCreateImage(_context); + if (nativeImage) { + auto image = std::make_shared(nativeImage); + CFRelease(nativeImage); + return image; + } else { + return nil; + } +} + +void CanvasImpl::draw(std::shared_ptr const &other, lottie::CGRect const &rect) { + CanvasImpl *impl = (CanvasImpl *)other.get(); + if (impl->_layer) { + CGContextDrawLayerInRect(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height), impl->_layer); + } else { + auto image = impl->makeImage(); + CGContextDrawImage(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height), ((ImageImpl *)image.get())->nativeImage()); + } +} + +} + diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.h new file mode 100644 index 00000000000..56918703ae0 --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.h @@ -0,0 +1,47 @@ +#ifndef NullCanvasImpl_h +#define NullCanvasImpl_h + +#include "Canvas.h" + +namespace lottieRendering { + +class NullCanvasImpl: public Canvas { +public: + NullCanvasImpl(int width, int height); + virtual ~NullCanvasImpl(); + + virtual int width() const override; + virtual int height() const override; + + virtual std::shared_ptr makeLayer(int width, int height) override; + + virtual void saveState() override; + virtual void restoreState() override; + + virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; + virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) override; + virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + virtual void fill(lottie::CGRect const &rect, lottie::Color const &fillColor) override; + + virtual void setBlendMode(BlendMode blendMode) override; + + virtual void setAlpha(float alpha) override; + + virtual void concatenate(lottie::Transform2D const &transform) override; + + virtual void draw(std::shared_ptr const &other, lottie::CGRect const &rect) override; + + void flush(); + +private: + float _width = 0.0f; + float _height = 0.0f; + lottie::Transform2D _transform; +}; + +} + +#endif diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.mm new file mode 100644 index 00000000000..60302011802 --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.mm @@ -0,0 +1,81 @@ +#include "NullCanvasImpl.h" + +namespace lottieRendering { + +namespace { + +void addEnumeratedPath(CanvasPathEnumerator const &enumeratePath) { + enumeratePath([&](PathCommand const &command) { + }); +} + +} + +NullCanvasImpl::NullCanvasImpl(int width, int height) : +_width(width), _height(height), _transform(lottie::Transform2D::identity()) { +} + +NullCanvasImpl::~NullCanvasImpl() { +} + +int NullCanvasImpl::width() const { + return _width; +} + +int NullCanvasImpl::height() const { + return _height; +} + +std::shared_ptr NullCanvasImpl::makeLayer(int width, int height) { + return std::make_shared(width, height); +} + +void NullCanvasImpl::saveState() { +} + +void NullCanvasImpl::restoreState() { +} + +void NullCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) { +} + +void NullCanvasImpl::setBlendMode(BlendMode blendMode) { +} + +void NullCanvasImpl::setAlpha(float alpha) { +} + +void NullCanvasImpl::concatenate(lottie::Transform2D const &transform) { +} + +void NullCanvasImpl::draw(std::shared_ptr const &other, lottie::CGRect const &rect) { +} + +void NullCanvasImpl::flush() { +} + +} diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm new file mode 100644 index 00000000000..c71328ef452 --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm @@ -0,0 +1,631 @@ +#import + +#import "Canvas.h" +#import "CoreGraphicsCanvasImpl.h" +#import "ThorVGCanvasImpl.h" +#import "NullCanvasImpl.h" + +#include + +namespace { + +static constexpr float minVisibleAlpha = 0.5f / 255.0f; + +static constexpr float minGlobalRectCalculationSize = 200.0f; + +struct TransformedPath { + lottie::BezierPath path; + lottie::Transform2D transform; + + TransformedPath(lottie::BezierPath const &path_, lottie::Transform2D const &transform_) : + path(path_), + transform(transform_) { + } +}; + +static lottie::CGRect collectPathBoundingBoxes(std::shared_ptr item, size_t subItemLimit, lottie::Transform2D const &parentTransform, bool skipApplyTransform, lottie::BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + //TODO:remove skipApplyTransform + lottie::Transform2D effectiveTransform = parentTransform; + if (!skipApplyTransform && item->isGroup) { + effectiveTransform = item->transform * effectiveTransform; + } + + size_t maxSubitem = std::min(item->subItems.size(), subItemLimit); + + lottie::CGRect boundingBox(0.0, 0.0, 0.0, 0.0); + if (item->path) { + if (item->path->needsBoundsRecalculation) { + item->path->bounds = lottie::bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, item->path->path); + item->path->needsBoundsRecalculation = false; + } + boundingBox = item->path->bounds.applyingTransform(effectiveTransform); + } + + for (size_t i = 0; i < maxSubitem; i++) { + auto &subItem = item->subItems[i]; + + lottie::CGRect subItemBoundingBox = collectPathBoundingBoxes(subItem, INT32_MAX, effectiveTransform, false, bezierPathsBoundingBoxContext); + + if (boundingBox.empty()) { + boundingBox = subItemBoundingBox; + } else { + boundingBox = boundingBox.unionWith(subItemBoundingBox); + } + } + + return boundingBox; +} + +static void enumeratePaths(std::shared_ptr item, size_t subItemLimit, lottie::Transform2D const &parentTransform, bool skipApplyTransform, std::function const &onPath) { + //TODO:remove skipApplyTransform + lottie::Transform2D effectiveTransform = parentTransform; + if (!skipApplyTransform && item->isGroup) { + effectiveTransform = item->transform * effectiveTransform; + } + + size_t maxSubitem = std::min(item->subItems.size(), subItemLimit); + + if (item->path) { + onPath(item->path->path, effectiveTransform); + } + + for (size_t i = 0; i < maxSubitem; i++) { + auto &subItem = item->subItems[i]; + + enumeratePaths(subItem, INT32_MAX, effectiveTransform, false, onPath); + } +} + +} + +namespace lottie { + +static std::optional getRenderContentItemGlobalRect(std::shared_ptr const &contentItem, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + auto currentTransform = parentTransform; + Transform2D localTransform = contentItem->transform; + currentTransform = localTransform * currentTransform; + + std::optional globalRect; + for (const auto &shadingVariant : contentItem->shadings) { + lottie::CGRect shapeBounds = collectPathBoundingBoxes(contentItem, shadingVariant->subItemLimit, lottie::Transform2D::identity(), true, bezierPathsBoundingBoxContext); + + if (shadingVariant->stroke) { + shapeBounds = shapeBounds.insetBy(-shadingVariant->stroke->lineWidth / 2.0, -shadingVariant->stroke->lineWidth / 2.0); + } else if (shadingVariant->fill) { + } else { + continue; + } + + CGRect shapeGlobalBounds = shapeBounds.applyingTransform(currentTransform); + if (globalRect) { + globalRect = globalRect->unionWith(shapeGlobalBounds); + } else { + globalRect = shapeGlobalBounds; + } + } + + for (const auto &subItem : contentItem->subItems) { + auto subGlobalRect = getRenderContentItemGlobalRect(subItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); + if (subGlobalRect) { + if (globalRect) { + globalRect = globalRect->unionWith(subGlobalRect.value()); + } else { + globalRect = subGlobalRect.value(); + } + } + } + + if (globalRect) { + CGRect integralGlobalRect( + std::floor(globalRect->x), + std::floor(globalRect->y), + std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)), + std::ceil(globalRect->height + globalRect->y - floor(globalRect->y)) + ); + return integralGlobalRect.intersection(CGRect(0.0, 0.0, globalSize.x, globalSize.y)); + } else { + return std::nullopt; + } +} + +static std::optional getRenderNodeGlobalRect(std::shared_ptr const &node, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, bool isInvertedMatte, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + if (node->isHidden() || node->alpha() < minVisibleAlpha) { + return std::nullopt; + } + + auto currentTransform = parentTransform; + Transform2D localTransform = node->transform(); + currentTransform = localTransform * currentTransform; + + std::optional globalRect; + if (node->_contentItem) { + globalRect = getRenderContentItemGlobalRect(node->_contentItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); + } + + if (isInvertedMatte) { + CGRect globalBounds = CGRect(0.0f, 0.0f, node->size().x, node->size().y).applyingTransform(currentTransform); + if (globalRect) { + globalRect = globalRect->unionWith(globalBounds); + } else { + globalRect = globalBounds; + } + } + + for (const auto &subNode : node->subnodes()) { + auto subGlobalRect = getRenderNodeGlobalRect(subNode, globalSize, currentTransform, false, bezierPathsBoundingBoxContext); + if (subGlobalRect) { + if (globalRect) { + globalRect = globalRect->unionWith(subGlobalRect.value()); + } else { + globalRect = subGlobalRect.value(); + } + } + } + + if (globalRect) { + CGRect integralGlobalRect( + std::floor(globalRect->x), + std::floor(globalRect->y), + std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)), + std::ceil(globalRect->height + globalRect->y - floor(globalRect->y)) + ); + return integralGlobalRect.intersection(CGRect(0.0, 0.0, globalSize.x, globalSize.y)); + } else { + return std::nullopt; + } +} + +} + +namespace { + +static void drawLottieContentItem(std::shared_ptr const &parentContext, std::shared_ptr item, float parentAlpha, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, lottie::BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + auto currentTransform = parentTransform; + lottie::Transform2D localTransform = item->transform; + currentTransform = localTransform * currentTransform; + + float normalizedOpacity = item->alpha; + float layerAlpha = ((float)normalizedOpacity) * parentAlpha; + + if (normalizedOpacity == 0.0f) { + return; + } + + parentContext->saveState(); + + std::shared_ptr const *currentContext; + std::shared_ptr tempContext; + + bool needsTempContext = false; + needsTempContext = layerAlpha != 1.0 && item->drawContentCount > 1; + + std::optional globalRect; + if (needsTempContext) { + if (globalSize.x <= minGlobalRectCalculationSize && globalSize.y <= minGlobalRectCalculationSize) { + globalRect = lottie::CGRect(0.0, 0.0, globalSize.x, globalSize.y); + } else { + globalRect = lottie::getRenderContentItemGlobalRect(item, globalSize, parentTransform, bezierPathsBoundingBoxContext); + } + if (!globalRect || globalRect->width <= 0.0f || globalRect->height <= 0.0f) { + parentContext->restoreState(); + return; + } + + auto tempContextValue = parentContext->makeLayer((int)(globalRect->width), (int)(globalRect->height)); + tempContext = tempContextValue; + + currentContext = &tempContext; + (*currentContext)->concatenate(lottie::Transform2D::identity().translated(lottie::Vector2D(-globalRect->x, -globalRect->y))); + + (*currentContext)->saveState(); + (*currentContext)->concatenate(currentTransform); + } else { + currentContext = &parentContext; + } + + parentContext->concatenate(item->transform); + + float renderAlpha = 1.0; + if (tempContext) { + renderAlpha = 1.0; + } else { + renderAlpha = layerAlpha; + } + + for (const auto &shading : item->shadings) { + lottieRendering::CanvasPathEnumerator iteratePaths; + if (shading->explicitPath) { + auto itemPaths = shading->explicitPath.value(); + iteratePaths = [itemPaths = itemPaths](std::function iterate) -> void { + lottieRendering::PathCommand pathCommand; + for (const auto &path : itemPaths) { + std::optional previousElement; + for (const auto &element : path.elements()) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + pathCommand.type = lottieRendering::PathCommandType::LineTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } else { + pathCommand.type = lottieRendering::PathCommandType::CurveTo; + pathCommand.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + pathCommand.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); + pathCommand.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); + iterate(pathCommand); + } + } else { + pathCommand.type = lottieRendering::PathCommandType::MoveTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } + previousElement = element; + } + if (path.closed().value_or(true)) { + pathCommand.type = lottieRendering::PathCommandType::Close; + iterate(pathCommand); + } + } + }; + } else { + iteratePaths = [&](std::function iterate) { + enumeratePaths(item, shading->subItemLimit, lottie::Transform2D::identity(), true, [&](lottie::BezierPath const &sourcePath, lottie::Transform2D const &transform) { + auto path = sourcePath.copyUsingTransform(transform); + + lottieRendering::PathCommand pathCommand; + std::optional previousElement; + for (const auto &element : path.elements()) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + pathCommand.type = lottieRendering::PathCommandType::LineTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } else { + pathCommand.type = lottieRendering::PathCommandType::CurveTo; + pathCommand.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + pathCommand.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); + pathCommand.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); + iterate(pathCommand); + } + } else { + pathCommand.type = lottieRendering::PathCommandType::MoveTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } + previousElement = element; + } + if (path.closed().value_or(true)) { + pathCommand.type = lottieRendering::PathCommandType::Close; + iterate(pathCommand); + } + }); + }; + } + + /*auto iteratePaths = [&](std::function iterate) -> void { + lottieRendering::PathCommand pathCommand; + for (const auto &path : itemPaths) { + std::optional previousElement; + for (const auto &element : path.elements()) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + pathCommand.type = lottieRendering::PathCommandType::LineTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } else { + pathCommand.type = lottieRendering::PathCommandType::CurveTo; + pathCommand.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + pathCommand.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); + pathCommand.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); + iterate(pathCommand); + } + } else { + pathCommand.type = lottieRendering::PathCommandType::MoveTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } + previousElement = element; + } + if (path.closed().value_or(true)) { + pathCommand.type = lottieRendering::PathCommandType::Close; + iterate(pathCommand); + } + } + };*/ + + if (shading->stroke) { + if (shading->stroke->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Solid) { + lottie::RenderTreeNodeContentItem::SolidShading *solidShading = (lottie::RenderTreeNodeContentItem::SolidShading *)shading->stroke->shading.get(); + + if (solidShading->opacity != 0.0) { + lottie::LineJoin lineJoin = lottie::LineJoin::Bevel; + switch (shading->stroke->lineJoin) { + case lottie::LineJoin::Bevel: { + lineJoin = lottie::LineJoin::Bevel; + break; + } + case lottie::LineJoin::Round: { + lineJoin = lottie::LineJoin::Round; + break; + } + case lottie::LineJoin::Miter: { + lineJoin = lottie::LineJoin::Miter; + break; + } + default: { + break; + } + } + + lottie::LineCap lineCap = lottie::LineCap::Square; + switch (shading->stroke->lineCap) { + case lottie::LineCap::Butt: { + lineCap = lottie::LineCap::Butt; + break; + } + case lottie::LineCap::Round: { + lineCap = lottie::LineCap::Round; + break; + } + case lottie::LineCap::Square: { + lineCap = lottie::LineCap::Square; + break; + } + default: { + break; + } + } + + std::vector dashPattern; + if (!shading->stroke->dashPattern.empty()) { + dashPattern = shading->stroke->dashPattern; + } + + (*currentContext)->strokePath(iteratePaths, shading->stroke->lineWidth, lineJoin, lineCap, shading->stroke->dashPhase, dashPattern, lottie::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha)); + } else if (shading->stroke->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) { + //TODO:gradient stroke + } + } + } else if (shading->fill) { + lottie::FillRule rule = lottie::FillRule::NonZeroWinding; + switch (shading->fill->rule) { + case lottie::FillRule::EvenOdd: { + rule = lottie::FillRule::EvenOdd; + break; + } + case lottie::FillRule::NonZeroWinding: { + rule = lottie::FillRule::NonZeroWinding; + break; + } + default: { + break; + } + } + + if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Solid) { + lottie::RenderTreeNodeContentItem::SolidShading *solidShading = (lottie::RenderTreeNodeContentItem::SolidShading *)shading->fill->shading.get(); + if (solidShading->opacity != 0.0) { + (*currentContext)->fillPath(iteratePaths, rule, lottie::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha)); + } + } else if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) { + lottie::RenderTreeNodeContentItem::GradientShading *gradientShading = (lottie::RenderTreeNodeContentItem::GradientShading *)shading->fill->shading.get(); + + if (gradientShading->opacity != 0.0) { + std::vector colors; + std::vector locations; + for (const auto &color : gradientShading->colors) { + colors.push_back(lottie::Color(color.r, color.g, color.b, color.a * gradientShading->opacity * renderAlpha)); + } + locations = gradientShading->locations; + + lottieRendering::Gradient gradient(colors, locations); + lottie::Vector2D start(gradientShading->start.x, gradientShading->start.y); + lottie::Vector2D end(gradientShading->end.x, gradientShading->end.y); + + switch (gradientShading->gradientType) { + case lottie::GradientType::Linear: { + (*currentContext)->linearGradientFillPath(iteratePaths, rule, gradient, start, end); + break; + } + case lottie::GradientType::Radial: { + (*currentContext)->radialGradientFillPath(iteratePaths, rule, gradient, start, 0.0, start, start.distanceTo(end)); + break; + } + default: { + break; + } + } + } + } + } + } + + for (auto it = item->subItems.rbegin(); it != item->subItems.rend(); it++) { + const auto &subItem = *it; + drawLottieContentItem(*currentContext, subItem, renderAlpha, globalSize, currentTransform, bezierPathsBoundingBoxContext); + } + + if (tempContext) { + tempContext->restoreState(); + + parentContext->concatenate(currentTransform.inverted()); + parentContext->setAlpha(layerAlpha); + parentContext->draw(tempContext, globalRect.value()); + parentContext->setAlpha(1.0); + } + + parentContext->restoreState(); +} + +static void renderLottieRenderNode(std::shared_ptr node, std::shared_ptr const &parentContext, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, float parentAlpha, bool isInvertedMatte, lottie::BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + float normalizedOpacity = node->alpha(); + float layerAlpha = ((float)normalizedOpacity) * parentAlpha; + + if (node->isHidden() || normalizedOpacity < minVisibleAlpha) { + return; + } + + auto currentTransform = parentTransform; + lottie::Transform2D localTransform = node->transform(); + currentTransform = localTransform * currentTransform; + + std::shared_ptr maskContext; + std::shared_ptr currentContext; + std::shared_ptr tempContext; + + bool masksToBounds = node->masksToBounds(); + if (masksToBounds) { + lottie::CGRect effectiveGlobalBounds = lottie::CGRect(0.0f, 0.0f, node->size().x, node->size().y).applyingTransform(currentTransform); + if (effectiveGlobalBounds.width <= 0.0f || effectiveGlobalBounds.height <= 0.0f) { + return; + } + if (effectiveGlobalBounds.contains(lottie::CGRect(0.0, 0.0, globalSize.x, globalSize.y))) { + masksToBounds = false; + } + } + + parentContext->saveState(); + + bool needsTempContext = false; + if (node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) { + needsTempContext = true; + } else { + needsTempContext = layerAlpha != 1.0 || masksToBounds; + } + + std::optional globalRect; + if (needsTempContext) { + if (globalSize.x <= minGlobalRectCalculationSize && globalSize.y <= minGlobalRectCalculationSize) { + globalRect = lottie::CGRect(0.0, 0.0, globalSize.x, globalSize.y); + } else { + globalRect = lottie::getRenderNodeGlobalRect(node, globalSize, parentTransform, false, bezierPathsBoundingBoxContext); + } + if (!globalRect || globalRect->width <= 0.0f || globalRect->height <= 0.0f) { + parentContext->restoreState(); + return; + } + + if ((node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) || masksToBounds) { + auto maskBackingStorage = parentContext->makeLayer((int)(globalRect->width), (int)(globalRect->height)); + + maskBackingStorage->concatenate(lottie::Transform2D::identity().translated(lottie::Vector2D(-globalRect->x, -globalRect->y))); + maskBackingStorage->concatenate(currentTransform); + + if (masksToBounds) { + maskBackingStorage->fill(lottie::CGRect(0.0f, 0.0f, node->size().x, node->size().y), lottie::Color(1.0f, 1.0f, 1.0f, 1.0f)); + } + if (node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) { + renderLottieRenderNode(node->mask(), maskBackingStorage, globalSize, currentTransform, 1.0, node->invertMask(), bezierPathsBoundingBoxContext); + } + + maskContext = maskBackingStorage; + } + + auto tempContextValue = parentContext->makeLayer((int)(globalRect->width), (int)(globalRect->height)); + tempContext = tempContextValue; + + currentContext = tempContextValue; + currentContext->concatenate(lottie::Transform2D::identity().translated(lottie::Vector2D(-globalRect->x, -globalRect->y))); + + currentContext->saveState(); + currentContext->concatenate(currentTransform); + } else { + currentContext = parentContext; + } + + parentContext->concatenate(node->transform()); + + float renderAlpha = 1.0f; + if (tempContext) { + renderAlpha = 1.0f; + } else { + renderAlpha = layerAlpha; + } + + if (node->_contentItem) { + drawLottieContentItem(currentContext, node->_contentItem, renderAlpha, globalSize, currentTransform, bezierPathsBoundingBoxContext); + } + + if (isInvertedMatte) { + currentContext->fill(lottie::CGRect(0.0f, 0.0f, node->size().x, node->size().y), lottie::Color(0.0f, 0.0f, 0.0f, 1.0f)); + currentContext->setBlendMode(lottieRendering::BlendMode::DestinationOut); + } + + for (const auto &subnode : node->subnodes()) { + renderLottieRenderNode(subnode, currentContext, globalSize, currentTransform, renderAlpha, false, bezierPathsBoundingBoxContext); + } + + if (tempContext) { + tempContext->restoreState(); + + if (maskContext) { + tempContext->setBlendMode(lottieRendering::BlendMode::DestinationIn); + tempContext->draw(maskContext, lottie::CGRect(globalRect->x, globalRect->y, globalRect->width, globalRect->height)); + } + + parentContext->concatenate(currentTransform.inverted()); + parentContext->setAlpha(layerAlpha); + parentContext->draw(tempContext, globalRect.value()); + } + + parentContext->restoreState(); +} + +} + +CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) { + auto rect = calculatePathBoundingBox(path); + return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); +} + +@interface SoftwareLottieRenderer() { + LottieAnimationContainer *_animationContainer; + std::shared_ptr _bezierPathsBoundingBoxContext; +} + +@end + +@implementation SoftwareLottieRenderer + +- (instancetype _Nonnull)initWithAnimationContainer:(LottieAnimationContainer * _Nonnull)animationContainer { + self = [super init]; + if (self != nil) { + _animationContainer = animationContainer; + _bezierPathsBoundingBoxContext = std::make_shared(); + } + return self; +} + +- (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering { + LottieAnimation *animation = _animationContainer.animation; + std::shared_ptr renderNode = [_animationContainer internalGetRootRenderTreeNode]; + if (!renderNode) { + return nil; + } + + lottie::Transform2D rootTransform = lottie::Transform2D::identity().scaled(lottie::Vector2D(size.width / (float)animation.size.width, size.height / (float)animation.size.height)); + + if (useReferenceRendering) { + auto context = std::make_shared((int)size.width, (int)size.height); + + CGPoint scale = CGPointMake(size.width / (CGFloat)animation.size.width, size.height / (CGFloat)animation.size.height); + context->concatenate(lottie::Transform2D::makeScale(scale.x, scale.y)); + + renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), rootTransform, 1.0, false, *_bezierPathsBoundingBoxContext.get()); + + auto image = context->makeImage(); + + return [[UIImage alloc] initWithCGImage:std::static_pointer_cast(image)->nativeImage()]; + } else { + //auto context = std::make_shared((int)size.width, (int)size.height); + auto context = std::make_shared((int)size.width, (int)size.height); + + CGPoint scale = CGPointMake(size.width / (CGFloat)animation.size.width, size.height / (CGFloat)animation.size.height); + context->concatenate(lottie::Transform2D::makeScale(scale.x, scale.y)); + + //renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), rootTransform, 1.0, false, *_bezierPathsBoundingBoxContext.get()); + + return nil; + } +} + +@end diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h new file mode 100644 index 00000000000..dbd676b5e95 --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h @@ -0,0 +1,64 @@ +#ifndef ThorVGCanvasImpl_h +#define ThorVGCanvasImpl_h + +#include "Canvas.h" + +#include + +namespace lottieRendering { + +class ThorVGCanvasImpl: public Canvas { +public: + ThorVGCanvasImpl(int width, int height); + virtual ~ThorVGCanvasImpl(); + + virtual int width() const override; + virtual int height() const override; + + virtual std::shared_ptr makeLayer(int width, int height) override; + + virtual void saveState() override; + virtual void restoreState() override; + + virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; + virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) override; + virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + virtual void fill(lottie::CGRect const &rect, lottie::Color const &fillColor) override; + + virtual void setBlendMode(BlendMode blendMode) override; + + virtual void setAlpha(float alpha) override; + + virtual void concatenate(lottie::Transform2D const &transform) override; + + virtual void draw(std::shared_ptr const &other, lottie::CGRect const &rect) override; + + uint32_t *backingData() { + return _backingData; + } + + int bytesPerRow() const { + return _bytesPerRow; + } + + void flush(); + +private: + int _width = 0; + int _height = 0; + std::unique_ptr _canvas; + + float _alpha = 1.0; + lottie::Transform2D _transform; + std::vector _stateStack; + int _bytesPerRow = 0; + uint32_t *_backingData = nullptr; + int _statsNumStrokes = 0; +}; + +} + +#endif diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.mm new file mode 100644 index 00000000000..4be6064df21 --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.mm @@ -0,0 +1,288 @@ +#include "ThorVGCanvasImpl.h" + +namespace lottieRendering { + +namespace { + +void tvgPath(CanvasPathEnumerator const &enumeratePath, tvg::Shape *shape) { + enumeratePath([&](PathCommand const &command) { + switch (command.type) { + case PathCommandType::MoveTo: { + shape->moveTo(command.points[0].x, command.points[0].y); + break; + } + case PathCommandType::LineTo: { + shape->lineTo(command.points[0].x, command.points[0].y); + break; + } + case PathCommandType::CurveTo: { + shape->cubicTo(command.points[0].x, command.points[0].y, command.points[1].x, command.points[1].y, command.points[2].x, command.points[2].y); + break; + } + case PathCommandType::Close: { + shape->close(); + break; + } + } + }); +} + +tvg::Matrix tvgTransform(lottie::Transform2D const &transform) { + CGAffineTransform affineTransform = CATransform3DGetAffineTransform(lottie::nativeTransform(transform)); + tvg::Matrix result; + result.e11 = affineTransform.a; + result.e21 = affineTransform.b; + result.e31 = 0.0f; + result.e12 = affineTransform.c; + result.e22 = affineTransform.d; + result.e32 = 0.0f; + result.e13 = affineTransform.tx; + result.e23 = affineTransform.ty; + result.e33 = 1.0f; + return result; +} + +} + +ThorVGCanvasImpl::ThorVGCanvasImpl(int width, int height) : +_width(width), _height(height), _transform(lottie::Transform2D::identity()) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + tvg::Initializer::init(0); + }); + + _canvas = tvg::SwCanvas::gen(); + + _bytesPerRow = width * 4; + + static uint32_t *sharedBackingData = (uint32_t *)malloc(_bytesPerRow * height); + _backingData = sharedBackingData; + + _canvas->target(_backingData, _bytesPerRow / 4, width, height, tvg::SwCanvas::ARGB8888); +} + +ThorVGCanvasImpl::~ThorVGCanvasImpl() { +} + +int ThorVGCanvasImpl::width() const { + return _width; +} + +int ThorVGCanvasImpl::height() const { + return _height; +} + +std::shared_ptr ThorVGCanvasImpl::makeLayer(int width, int height) { + return std::make_shared(width, height); +} + +void ThorVGCanvasImpl::saveState() { + _stateStack.push_back(_transform); +} + +void ThorVGCanvasImpl::restoreState() { + if (_stateStack.empty()) { + assert(false); + return; + } + _transform = _stateStack[_stateStack.size() - 1]; + _stateStack.pop_back(); +} + +void ThorVGCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) { + auto shape = tvg::Shape::gen(); + tvgPath(enumeratePath, shape.get()); + + shape->transform(tvgTransform(_transform)); + + shape->fill((int)(color.r * 255.0), (int)(color.g * 255.0), (int)(color.b * 255.0), (int)(color.a * _alpha * 255.0)); + shape->fill(fillRule == lottie::FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); + + _canvas->push(std::move(shape)); +} + +void ThorVGCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { + auto shape = tvg::Shape::gen(); + tvgPath(enumeratePath, shape.get()); + + shape->transform(tvgTransform(_transform)); + + auto fill = tvg::LinearGradient::gen(); + fill->linear(start.x, start.y, end.x, end.y); + + std::vector colors; + for (size_t i = 0; i < gradient.colors().size(); i++) { + const auto &color = gradient.colors()[i]; + tvg::Fill::ColorStop colorStop; + colorStop.offset = gradient.locations()[i]; + colorStop.r = (int)(color.r * 255.0); + colorStop.g = (int)(color.g * 255.0); + colorStop.b = (int)(color.b * 255.0); + colorStop.a = (int)(color.a * _alpha * 255.0); + colors.push_back(colorStop); + } + fill->colorStops(colors.data(), (uint32_t)colors.size()); + shape->fill(std::move(fill)); + + shape->fill(fillRule == lottie::FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); + + _canvas->push(std::move(shape)); +} + +void ThorVGCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { + auto shape = tvg::Shape::gen(); + tvgPath(enumeratePath, shape.get()); + + shape->transform(tvgTransform(_transform)); + + auto fill = tvg::RadialGradient::gen(); + fill->radial(startCenter.x, startCenter.y, endRadius); + + std::vector colors; + for (size_t i = 0; i < gradient.colors().size(); i++) { + const auto &color = gradient.colors()[i]; + tvg::Fill::ColorStop colorStop; + colorStop.offset = gradient.locations()[i]; + colorStop.r = (int)(color.r * 255.0); + colorStop.g = (int)(color.g * 255.0); + colorStop.b = (int)(color.b * 255.0); + colorStop.a = (int)(color.a * _alpha * 255.0); + colors.push_back(colorStop); + } + fill->colorStops(colors.data(), (uint32_t)colors.size()); + shape->fill(std::move(fill)); + + shape->fill(fillRule == lottie::FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); + + _canvas->push(std::move(shape)); +} + +void ThorVGCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) { + auto shape = tvg::Shape::gen(); + tvgPath(enumeratePath, shape.get()); + + shape->transform(tvgTransform(_transform)); + + shape->strokeFill((int)(color.r * 255.0), (int)(color.g * 255.0), (int)(color.b * 255.0), (int)(color.a * _alpha * 255.0)); + shape->strokeWidth(lineWidth); + + switch (lineJoin) { + case lottie::LineJoin::Miter: { + shape->strokeJoin(tvg::StrokeJoin::Miter); + break; + } + case lottie::LineJoin::Round: { + shape->strokeJoin(tvg::StrokeJoin::Round); + break; + } + case lottie::LineJoin::Bevel: { + shape->strokeJoin(tvg::StrokeJoin::Bevel); + break; + } + default: { + shape->strokeJoin(tvg::StrokeJoin::Bevel); + break; + } + } + + switch (lineCap) { + case lottie::LineCap::Butt: { + shape->strokeCap(tvg::StrokeCap::Butt); + break; + } + case lottie::LineCap::Round: { + shape->strokeCap(tvg::StrokeCap::Round); + break; + } + case lottie::LineCap::Square: { + shape->strokeCap(tvg::StrokeCap::Square); + break; + } + default: { + shape->strokeCap(tvg::StrokeCap::Square); + break; + } + } + + if (!dashPattern.empty()) { + std::vector intervals; + intervals.reserve(dashPattern.size()); + for (auto value : dashPattern) { + intervals.push_back(value); + } + shape->strokeDash(intervals.data(), (uint32_t)intervals.size()); + //TODO:phase + } + + _canvas->push(std::move(shape)); +} + +void ThorVGCanvasImpl::linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { +} + +void ThorVGCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { +} + +void ThorVGCanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) { + auto shape = tvg::Shape::gen(); + shape->appendRect(rect.x, rect.y, rect.width, rect.height, 0.0f, 0.0f); + + shape->transform(tvgTransform(_transform)); + + shape->fill((int)(fillColor.r * 255.0), (int)(fillColor.g * 255.0), (int)(fillColor.b * 255.0), (int)(fillColor.a * _alpha * 255.0)); + + _canvas->push(std::move(shape)); +} + +void ThorVGCanvasImpl::setBlendMode(BlendMode blendMode) { + /*switch (blendMode) { + case CGBlendMode::Normal: { + _blendMode = SkBlendMode::kSrcOver; + break; + } + case CGBlendMode::DestinationIn: { + _blendMode = SkBlendMode::kDstIn; + break; + } + case CGBlendMode::DestinationOut: { + _blendMode = SkBlendMode::kDstOut; + break; + } + default: { + _blendMode = SkBlendMode::kSrcOver; + break; + } + }*/ +} + +void ThorVGCanvasImpl::setAlpha(float alpha) { + _alpha = alpha; +} + +void ThorVGCanvasImpl::concatenate(lottie::Transform2D const &transform) { + _transform = transform * _transform; + /*_canvas->concat(SkM44( + transform.m11, transform.m21, transform.m31, transform.m41, + transform.m12, transform.m22, transform.m32, transform.m42, + transform.m13, transform.m23, transform.m33, transform.m43, + transform.m14, transform.m24, transform.m34, transform.m44 + ));*/ +} + +void ThorVGCanvasImpl::draw(std::shared_ptr const &other, lottie::CGRect const &rect) { + /*ThorVGCanvasImpl *impl = (ThorVGCanvasImpl *)other.get(); + auto image = impl->surface()->makeImageSnapshot(); + SkPaint paint; + paint.setBlendMode(_blendMode); + paint.setAlphaf(_alpha); + _canvas->drawImageRect(image.get(), SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), SkSamplingOptions(SkFilterMode::kLinear), &paint);*/ +} + +void ThorVGCanvasImpl::flush() { + _canvas->draw(); + _canvas->sync(); + + _statsNumStrokes = 0; +} + +} diff --git a/Tests/LottieMesh/Sources/AppDelegate.swift b/Tests/LottieMetalTest/Sources/AppDelegate.swift similarity index 100% rename from Tests/LottieMesh/Sources/AppDelegate.swift rename to Tests/LottieMetalTest/Sources/AppDelegate.swift diff --git a/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift b/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift new file mode 100644 index 00000000000..7b67ef5470c --- /dev/null +++ b/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift @@ -0,0 +1,327 @@ +import Foundation +import UIKit +import LottieMetal +import LottieCpp +import RLottieBinding +import Display +import Accelerate +import QOILoader +import SoftwareLottieRenderer +import LottieSwift + +@available(iOS 13.0, *) +func areImagesEqual(_ lhs: UIImage, _ rhs: UIImage) -> UIImage? { + let lhsBuffer = try! vImage_Buffer(cgImage: lhs.cgImage!) + let rhsBuffer = try! vImage_Buffer(cgImage: rhs.cgImage!) + + let maxDifferenceCount = Int((Double(Int(lhs.size.width) * Int(lhs.size.height)) * 0.01)) + + var foundDifferenceCount = 0 + + outer: for y in 0 ..< Int(lhs.size.height) { + let lhsRowPixels = lhsBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes) + let rhsRowPixels = rhsBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes) + + for x in 0 ..< Int(lhs.size.width) { + let lhs0 = lhsRowPixels.advanced(by: x * 4 + 0).pointee + let lhs1 = lhsRowPixels.advanced(by: x * 4 + 1).pointee + let lhs2 = lhsRowPixels.advanced(by: x * 4 + 2).pointee + let lhs3 = lhsRowPixels.advanced(by: x * 4 + 3).pointee + + let rhs0 = rhsRowPixels.advanced(by: x * 4 + 0).pointee + let rhs1 = rhsRowPixels.advanced(by: x * 4 + 1).pointee + let rhs2 = rhsRowPixels.advanced(by: x * 4 + 2).pointee + let rhs3 = rhsRowPixels.advanced(by: x * 4 + 3).pointee + + let maxDiff = 25 + if abs(Int(lhs0) - Int(rhs0)) > maxDiff || abs(Int(lhs1) - Int(rhs1)) > maxDiff || abs(Int(lhs2) - Int(rhs2)) > maxDiff || abs(Int(lhs3) - Int(rhs3)) > maxDiff { + + /*if false { + lhsRowPixels.advanced(by: x * 4 + 0).pointee = 255 + lhsRowPixels.advanced(by: x * 4 + 1).pointee = 0 + lhsRowPixels.advanced(by: x * 4 + 2).pointee = 0 + lhsRowPixels.advanced(by: x * 4 + 3).pointee = 255 + }*/ + + foundDifferenceCount += 1 + } + } + } + + lhsBuffer.free() + rhsBuffer.free() + + if foundDifferenceCount > maxDifferenceCount { + let colorSpace = Unmanaged.passRetained(lhs.cgImage!.colorSpace!) + let diffImage = try! lhsBuffer.createCGImage(format: vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: colorSpace, bitmapInfo: lhs.cgImage!.bitmapInfo, version: 0, decode: nil, renderingIntent: .defaultIntent), flags: .doNotTile) + return UIImage(cgImage: diffImage) + } else { + return nil + } +} + +@available(iOS 13.0, *) +func processDrawAnimation(baseCachePath: String, path: String, name: String, size: CGSize, alwaysDraw: Bool, updateImage: @escaping (UIImage?, UIImage?) -> Void) async -> Bool { + guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { + print("Could not load \(path)") + return false + } + + guard let animation = LottieAnimation(data: data) else { + print("Could not parse animation at \(path)") + return false + } + + let layer = LottieAnimationContainer(animation: animation) + + let cacheFolderPath = cacheReferenceFolderPath(baseCachePath: baseCachePath, width: Int(size.width), name: name) + if !FileManager.default.fileExists(atPath: cacheFolderPath) { + let _ = await cacheReferenceAnimation(baseCachePath: baseCachePath, width: Int(size.width), path: path, name: name) + } + + let renderer = SoftwareLottieRenderer(animationContainer: layer) + + for i in 0 ..< min(100000, animation.frameCount) { + let frameResult = autoreleasepool { + let frameIndex = i % animation.frameCount + + let referenceImageData = try! Data(contentsOf: URL(fileURLWithPath: cacheFolderPath + "/frame\(frameIndex)")) + let referenceImage = decompressImageFrame(data: referenceImageData) + + layer.update(frameIndex) + let image = renderer.render(for: size, useReferenceRendering: true)! + + if let diffImage = areImagesEqual(image, referenceImage) { + updateImage(diffImage, referenceImage) + + print("Mismatch in frame \(frameIndex)") + return false + } else { + if alwaysDraw { + updateImage(image, referenceImage) + } + return true + } + } + + /*if #available(iOS 16.0, *) { + try? await Task.sleep(for: .seconds(0.1)) + }*/ + + if !frameResult { + return false + } + } + + return true +} + +private func processAnimationFolder(basePath: String, path: String, stopOnFailure: Bool, process: (String, String) -> Bool) -> Bool { + let directoryPath = "\(basePath)\(path.isEmpty ? "" : "/")/\(path)" + for fileName in try! FileManager.default.contentsOfDirectory(atPath: directoryPath) { + let filePath = directoryPath + "/" + fileName + var isDirectory = ObjCBool(false) + if FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory) { + if isDirectory.boolValue { + if !processAnimationFolder(basePath: basePath, path: "\(path)\(path.isEmpty ? "" : "/")/\(fileName)", stopOnFailure: stopOnFailure, process: process) { + if stopOnFailure { + return false + } + } + } else if fileName.hasSuffix("json") { + var processAnimationResult = false + autoreleasepool { + processAnimationResult = process(filePath, fileName) + } + if !processAnimationResult { + print("Error processing \(path)\(path.isEmpty ? "" : "/")\(fileName)") + if stopOnFailure { + return false + } + } else { + print("[OK] processing \(path)\(path.isEmpty ? "" : "/")\(fileName)") + } + } + } + } + return true +} + +func buildAnimationFolderItems(basePath: String, path: String) -> [(String, String)] { + var result: [(String, String)] = [] + + let directoryPath = "\(basePath)\(path.isEmpty ? "" : "/")/\(path)" + for fileName in try! FileManager.default.contentsOfDirectory(atPath: directoryPath) { + let filePath = directoryPath + "/" + fileName + var isDirectory = ObjCBool(false) + if FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory) { + if isDirectory.boolValue { + result.append(contentsOf: buildAnimationFolderItems(basePath: basePath, path: "\(path)\(path.isEmpty ? "" : "/")/\(fileName)")) + } else if fileName.hasSuffix("json") { + result.append((filePath, fileName)) + } + } + } + + return result +} + +@available (iOS 13.0, *) +private func processAnimationFolderItems(items: [(String, String)], countPerBucket: Int, stopOnFailure: Bool, process: @escaping (String, String, Bool) async -> Bool) async -> Bool { + let bucketCount = items.count / countPerBucket + var buckets: [[(String, String)]] = [] + for item in items { + if buckets.isEmpty { + buckets.append([]) + } + if buckets[buckets.count - 1].count < bucketCount { + buckets[buckets.count - 1].append(item) + } else { + buckets.append([item]) + } + } + + var count = 0 + for bucket in buckets { + for (filePath, fileName) in bucket { + var processAnimationResult = false + processAnimationResult = await process(filePath, fileName, true) + if !processAnimationResult { + print("Error processing \(fileName)") + if stopOnFailure { + return false + } + } else { + count += 1 + print("[OK \(count) / \(items.count)] processing \(fileName)") + } + } + } + + return true +} + +@available(iOS 13.0, *) +private func processAnimationFolderItemsParallel(items: [(String, String)], stopOnFailure: Bool, process: @escaping (String, String, Bool) async -> Bool) async -> Bool { + let bucketCount = items.count / 7 + var buckets: [[(String, String)]] = [] + for item in items { + if buckets.isEmpty { + buckets.append([]) + } + if buckets[buckets.count - 1].count < bucketCount { + buckets[buckets.count - 1].append(item) + } else { + buckets.append([item]) + } + } + + class AtomicCounter { + var value: Int = 0 + } + let count = AtomicCounter() + let itemCount = items.count + let countQueue = DispatchQueue(label: "com.example.count-queue") + + let result = await withTaskGroup(of: Bool.self, body: { group in + var alwaysDraw = true + for bucket in buckets { + let alwaysDrawValue = alwaysDraw + alwaysDraw = false + group.addTask(operation: { + for (filePath, fileName) in bucket { + var processAnimationResult = false + processAnimationResult = await process(filePath, fileName, alwaysDrawValue) + if !processAnimationResult { + print("Error processing \(fileName)") + if stopOnFailure { + return false + } + } else { + countQueue.async { + count.value += 1 + print("[OK \(count.value) / \(itemCount)] processing \(fileName)") + } + } + } + return true + }) + } + + for await result in group { + if !result { + return false + } + } + return true + }) + + return result +} + +@available (iOS 13.0, *) +func processAnimationFolderAsync(basePath: String, path: String, stopOnFailure: Bool, process: @escaping (String, String, Bool) async -> Bool) async -> Bool { + let items = buildAnimationFolderItems(basePath: basePath, path: path) + return await processAnimationFolderItems(items: items, countPerBucket: 1, stopOnFailure: stopOnFailure, process: process) +} + +@available(iOS 13.0, *) +func processAnimationFolderParallel(basePath: String, path: String, stopOnFailure: Bool, process: @escaping (String, String, Bool) async -> Bool) async -> Bool { + let items = buildAnimationFolderItems(basePath: basePath, path: path) + return await processAnimationFolderItemsParallel(items: items, stopOnFailure: stopOnFailure, process: process) +} + +func cacheReferenceFolderPath(baseCachePath: String, width: Int, name: String) -> String { + return baseCachePath + "/" + name + "_\(width)" +} + +func compressImageFrame(image: UIImage) -> Data { + return encodeImageQOI(image)! +} + +func decompressImageFrame(data: Data) -> UIImage { + return decodeImageQOI(data)! +} + +@MainActor +func cacheReferenceAnimation(baseCachePath: String, width: Int, path: String, name: String) -> String { + let targetFolderPath = cacheReferenceFolderPath(baseCachePath: baseCachePath, width: width, name: name) + if FileManager.default.fileExists(atPath: targetFolderPath) { + return targetFolderPath + } + + guard let referenceAnimation = Animation.filepath(path) else { + preconditionFailure("Could not parse reference animation at \(path)") + } + let referenceLayer = MainThreadAnimationLayer(animation: referenceAnimation, imageProvider: BlankImageProvider(), textProvider: DefaultTextProvider(), fontProvider: DefaultFontProvider()) + + let cacheFolderPath = NSTemporaryDirectory() + "\(UInt64.random(in: 0 ... UInt64.max))" + let _ = try? FileManager.default.createDirectory(atPath: cacheFolderPath, withIntermediateDirectories: true) + + let frameCount = Int(referenceAnimation.endFrame - referenceAnimation.startFrame) + + let size = CGSize(width: CGFloat(width), height: CGFloat(width)) + + for i in 0 ..< min(100000, frameCount) { + let frameIndex = i % frameCount + + referenceLayer.currentFrame = CGFloat(frameIndex) + referenceLayer.displayUpdate() + referenceLayer.position = referenceAnimation.bounds.center + + referenceLayer.isOpaque = false + referenceLayer.backgroundColor = nil + let referenceContext = ImageContext(width: width, height: width) + referenceContext.context.clear(CGRect(origin: CGPoint(), size: size)) + referenceContext.context.scaleBy(x: size.width / CGFloat(referenceAnimation.width), y: size.height / CGFloat(referenceAnimation.height)) + + referenceLayer.render(in: referenceContext.context) + + let referenceImage = referenceContext.makeImage() + try! compressImageFrame(image: referenceImage).write(to: URL(fileURLWithPath: cacheFolderPath + "/frame\(i)")) + } + + let _ = try! FileManager.default.moveItem(atPath: cacheFolderPath, toPath: targetFolderPath) + + return targetFolderPath +} diff --git a/Tests/LottieMetalTest/Sources/ImageUtils.swift b/Tests/LottieMetalTest/Sources/ImageUtils.swift new file mode 100644 index 00000000000..b52764bb690 --- /dev/null +++ b/Tests/LottieMetalTest/Sources/ImageUtils.swift @@ -0,0 +1,41 @@ +import Foundation +import UIKit + +private func alignUp(size: Int, align: Int) -> Int { + precondition(((align - 1) & align) == 0, "Align must be a power of two") + + let alignmentMask = align - 1 + return (size + alignmentMask) & ~alignmentMask +} + +final class ImageContext { + let context: CGContext + + init(width: Int, height: Int, isMask: Bool = false) { + let bytesPerRow: Int + let colorSpace: CGColorSpace + let bitmapInfo: CGBitmapInfo + + if isMask { + bytesPerRow = alignUp(size: width, align: 16) + colorSpace = CGColorSpaceCreateDeviceGray() + bitmapInfo = CGBitmapInfo(rawValue: 0) + } else { + bytesPerRow = alignUp(size: width * 4, align: 16) + colorSpace = CGColorSpaceCreateDeviceRGB() + bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) + } + + self.context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)! + + self.context.clear(CGRect(origin: CGPoint(), size: CGSize(width: width, height: height))) + + //context.interpolationQuality = .none + //context.setShouldAntialias(false) + //context.setAllowsAntialiasing(false) + } + + func makeImage() -> UIImage { + return UIImage(cgImage: context.makeImage()!) + } +} diff --git a/Tests/LottieMetalTest/Sources/ViewController.swift b/Tests/LottieMetalTest/Sources/ViewController.swift new file mode 100644 index 00000000000..3fa91463c3f --- /dev/null +++ b/Tests/LottieMetalTest/Sources/ViewController.swift @@ -0,0 +1,214 @@ +import Foundation +import UIKit +import LottieMetal +import LottieCpp +import RLottieBinding +import MetalEngine +import Display +import LottieSwift +import SoftwareLottieRenderer + +@available(iOS 13.0, *) +private final class ReferenceCompareTest { + private let view: UIView + private let imageView = UIImageView() + private let referenceImageView = UIImageView() + + init(view: UIView) { + lottieSwift_getPathNativeBoundingBox = { path in + return getPathNativeBoundingBox(path) + } + + self.view = view + + self.view.backgroundColor = .white + + let topInset: CGFloat = 50.0 + + self.view.addSubview(self.imageView) + self.imageView.layer.magnificationFilter = .nearest + self.imageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset), size: CGSize(width: 256.0, height: 256.0)) + self.imageView.backgroundColor = self.view.backgroundColor + self.imageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0) + + self.view.addSubview(self.referenceImageView) + self.referenceImageView.layer.magnificationFilter = .nearest + self.referenceImageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset + 256.0 + 1.0), size: CGSize(width: 256.0, height: 256.0)) + self.referenceImageView.backgroundColor = self.view.backgroundColor + self.referenceImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0) + + let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")! + + Task.detached { + let sizeMapping: [String: Int] = [ + "5170488605398795246.json": 512, + "35707580709863506.json": 512, + "35707580709863507.json": 512, + "1258816259754246.json": 512, + "1258816259754248.json": 512, + "35707580709863489.json": 512, + "1258816259754150.json": 512, + "35707580709863494.json": 512, + "5021586753580958116.json": 512, + "35707580709863509.json": 512, + "5282957555314728059.json": 512, + "fireworks.json": 512, + "750766425144033565.json": 512, + "1258816259754276.json": 1024, + "1471004892762996753.json": 1024, + "4985886809322947159.json": 1024, + "35707580709863490.json": 1024, + "4986037051573928320.json": 512, + "1258816259754029.json": 1024, + "4987794066860147124.json": 1024, + "1258816259754212.json": 1024, + "750766425144033464.json": 1024, + "750766425144033567.json": 1024, + "1391391008142393350.json": 1024 + ] + + let defaultSize = 128 + + let baseCachePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path + "/frame-cache" + let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: baseCachePath), withIntermediateDirectories: true, attributes: nil) + print("Frame cache: \(baseCachePath)") + + for (filePath, fileName) in buildAnimationFolderItems(basePath: bundlePath, path: "") { + let _ = await cacheReferenceAnimation(baseCachePath: baseCachePath, width: sizeMapping[fileName] ?? defaultSize, path: filePath, name: fileName) + } + + var continueFromName: String? + //continueFromName = "35707580709863498.json" + + let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: true, process: { path, name, alwaysDraw in + if let continueFromNameValue = continueFromName { + if continueFromNameValue == name { + continueFromName = nil + } else { + return true + } + } + + let size = sizeMapping[name] ?? defaultSize + + let result = await processDrawAnimation(baseCachePath: baseCachePath, path: path, name: name, size: CGSize(width: size, height: size), alwaysDraw: alwaysDraw, updateImage: { image, referenceImage in + DispatchQueue.main.async { + self.imageView.image = image + self.referenceImageView.image = referenceImage + } + }) + return result + }) + } + } +} + +public final class ViewController: UIViewController { + private var link: SharedDisplayLinkDriver.Link? + private var test: AnyObject? + + override public func viewDidLoad() { + super.viewDidLoad() + + SharedDisplayLinkDriver.shared.updateForegroundState(true) + + let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")! + let filePath = bundlePath + "/fireworks.json" + + let performanceFrameSize = 8 + + self.view.layer.addSublayer(MetalEngine.shared.rootLayer) + + if "".isEmpty { + if #available(iOS 13.0, *) { + self.test = ReferenceCompareTest(view: self.view) + } + } else if !"".isEmpty { + let cachedAnimation = cacheLottieMetalAnimation(path: filePath)! + let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)! + + /*let animationData = try! Data(contentsOf: URL(fileURLWithPath: filePath)) + + var startTime = CFAbsoluteTimeGetCurrent() + let animation = LottieAnimation(data: animationData)! + print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") + + startTime = CFAbsoluteTimeGetCurrent() + let animationContainer = LottieAnimationContainer(animation: animation) + animationContainer.update(0) + print("Build time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")*/ + + let lottieLayer = LottieContentLayer(content: animation) + lottieLayer.frame = CGRect(origin: CGPoint(x: 10.0, y: 50.0), size: CGSize(width: 256.0, height: 256.0)) + self.view.layer.addSublayer(lottieLayer) + lottieLayer.setNeedsUpdate() + + self.link = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { _ in + lottieLayer.frameIndex = (lottieLayer.frameIndex + 1) % animation.frameCount + lottieLayer.setNeedsUpdate() + }) + } else if "".isEmpty { + Thread { + let animationData = try! Data(contentsOf: URL(fileURLWithPath: filePath)) + + var startTime = CFAbsoluteTimeGetCurrent() + let animation = LottieAnimation(data: animationData)! + print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") + + startTime = CFAbsoluteTimeGetCurrent() + let animationContainer = LottieAnimationContainer(animation: animation) + animationContainer.update(0) + print("Build time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") + + let animationRenderer = SoftwareLottieRenderer(animationContainer: animationContainer) + + startTime = CFAbsoluteTimeGetCurrent() + var numUpdates: Int = 0 + var frameIndex = 0 + while true { + animationContainer.update(frameIndex) + let _ = animationRenderer.render(for: CGSize(width: CGFloat(performanceFrameSize), height: CGFloat(performanceFrameSize)), useReferenceRendering: false) + frameIndex = (frameIndex + 1) % animationContainer.animation.frameCount + numUpdates += 1 + let timestamp = CFAbsoluteTimeGetCurrent() + let deltaTime = timestamp - startTime + if deltaTime > 2.0 { + let updatesPerSecond = Double(numUpdates) / deltaTime + startTime = timestamp + numUpdates = 0 + print("Ours: updatesPerSecond: \(updatesPerSecond)") + } + } + }.start() + } else { + Thread { + var startTime = CFAbsoluteTimeGetCurrent() + let animationInstance = LottieInstance(data: try! Data(contentsOf: URL(fileURLWithPath: filePath)), fitzModifier: .none, colorReplacements: nil, cacheKey: "")! + print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") + + let frameBuffer = malloc(performanceFrameSize * 4 * performanceFrameSize)! + defer { + free(frameBuffer) + } + + startTime = CFAbsoluteTimeGetCurrent() + var numUpdates: Int = 0 + var frameIndex = 0 + while true { + animationInstance.renderFrame(with: Int32(frameIndex), into: frameBuffer, width: Int32(performanceFrameSize), height: Int32(performanceFrameSize), bytesPerRow: Int32(performanceFrameSize * 4)) + + frameIndex = (frameIndex + 1) % Int(animationInstance.frameCount) + numUpdates += 1 + let timestamp = CFAbsoluteTimeGetCurrent() + let deltaTime = timestamp - startTime + if deltaTime > 2.0 { + let updatesPerSecond = Double(numUpdates) / deltaTime + startTime = timestamp + numUpdates = 0 + print("Rlottie: updatesPerSecond: \(updatesPerSecond)") + } + } + }.start() + } + } +} diff --git a/Tests/LottieMetalTest/thorvg/BUILD b/Tests/LottieMetalTest/thorvg/BUILD new file mode 100644 index 00000000000..25729fc4ee6 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/BUILD @@ -0,0 +1,39 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +objc_library( + name = "thorvg", + enable_modules = True, + module_name = "thorvg", + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.mm", + "Sources/**/*.h", + "Sources/**/*.c", + "Sources/**/*.cpp", + "Sources/**/*.hpp", + ]), + copts = [ + "-Werror", + "-I{}/Sources".format(package_name()), + "-I{}/PublicHeaders/thorvg".format(package_name()), + "-I{}/Sources/common".format(package_name()), + "-I{}/Sources/loaders/raw".format(package_name()), + "-I{}/Sources/loaders/tvg".format(package_name()), + "-I{}/Sources/renderer".format(package_name()), + "-I{}/Sources/renderer/sw_engine".format(package_name()), + ], + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + deps = [ + ], + sdk_frameworks = [ + "Foundation", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/Tests/LottieMetalTest/thorvg/PublicHeaders/thorvg/thorvg.h b/Tests/LottieMetalTest/thorvg/PublicHeaders/thorvg/thorvg.h new file mode 100644 index 00000000000..5b152810cb3 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/PublicHeaders/thorvg/thorvg.h @@ -0,0 +1,2087 @@ +#ifndef _THORVG_H_ +#define _THORVG_H_ + +#include +#include +#include +#include + +#ifdef TVG_API + #undef TVG_API +#endif + +#ifndef TVG_STATIC + #ifdef _WIN32 + #if TVG_BUILD + #define TVG_API __declspec(dllexport) + #else + #define TVG_API __declspec(dllimport) + #endif + #elif (defined(__SUNPRO_C) || defined(__SUNPRO_CC)) + #define TVG_API __global + #else + #if (defined(__GNUC__) && __GNUC__ >= 4) || defined(__INTEL_COMPILER) + #define TVG_API __attribute__ ((visibility("default"))) + #else + #define TVG_API + #endif + #endif +#else + #define TVG_API +#endif + +#ifdef TVG_DEPRECATED + #undef TVG_DEPRECATED +#endif + +#ifdef _WIN32 + #define TVG_DEPRECATED __declspec(deprecated) +#elif __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) + #define TVG_DEPRECATED __attribute__ ((__deprecated__)) +#else + #define TVG_DEPRECATED +#endif + +#define _TVG_DECLARE_PRIVATE(A) \ + struct Impl; \ + Impl* pImpl; \ +protected: \ + A(const A&) = delete; \ + const A& operator=(const A&) = delete; \ + A() + +#define _TVG_DISABLE_CTOR(A) \ + A() = delete; \ + ~A() = delete + +#define _TVG_DECLARE_ACCESSOR(A) \ + friend A + +namespace tvg +{ + +class RenderMethod; +class Animation; + +/** + * @defgroup ThorVG ThorVG + * @brief ThorVG classes and enumerations providing C++ APIs. + */ + +/**@{*/ + +/** + * @brief Enumeration specifying the result from the APIs. + */ +enum class Result +{ + Success = 0, ///< The value returned in case of a correct request execution. + InvalidArguments, ///< The value returned in the event of a problem with the arguments given to the API - e.g. empty paths or null pointers. + InsufficientCondition, ///< The value returned in case the request cannot be processed - e.g. asking for properties of an object, which does not exist. + FailedAllocation, ///< The value returned in case of unsuccessful memory allocation. + MemoryCorruption, ///< The value returned in the event of bad memory handling - e.g. failing in pointer releasing or casting + NonSupport, ///< The value returned in case of choosing unsupported options. + Unknown ///< The value returned in all other cases. +}; + + +/** + * @brief Enumeration specifying the values of the path commands accepted by TVG. + * + * Not to be confused with the path commands from the svg path element (like M, L, Q, H and many others). + * TVG interprets all of them and translates to the ones from the PathCommand values. + */ +enum class PathCommand +{ + Close = 0, ///< Ends the current sub-path and connects it with its initial point. This command doesn't expect any points. + MoveTo, ///< Sets a new initial point of the sub-path and a new current point. This command expects 1 point: the starting position. + LineTo, ///< Draws a line from the current point to the given point and sets a new value of the current point. This command expects 1 point: the end-position of the line. + CubicTo ///< Draws a cubic Bezier curve from the current point to the given point using two given control points and sets a new value of the current point. This command expects 3 points: the 1st control-point, the 2nd control-point, the end-point of the curve. +}; + + +/** + * @brief Enumeration determining the ending type of a stroke in the open sub-paths. + */ +enum class StrokeCap : uint8_t +{ + Square = 0, ///< The stroke is extended in both end-points of a sub-path by a rectangle, with the width equal to the stroke width and the length equal to the half of the stroke width. For zero length sub-paths the square is rendered with the size of the stroke width. + Round, ///< The stroke is extended in both end-points of a sub-path by a half circle, with a radius equal to the half of a stroke width. For zero length sub-paths a full circle is rendered. + Butt ///< The stroke ends exactly at each of the two end-points of a sub-path. For zero length sub-paths no stroke is rendered. +}; + + +/** + * @brief Enumeration determining the style used at the corners of joined stroked path segments. + */ +enum class StrokeJoin : uint8_t +{ + Bevel = 0, ///< The outer corner of the joined path segments is bevelled at the join point. The triangular region of the corner is enclosed by a straight line between the outer corners of each stroke. + Round, ///< The outer corner of the joined path segments is rounded. The circular region is centered at the join point. + Miter ///< The outer corner of the joined path segments is spiked. The spike is created by extension beyond the join point of the outer edges of the stroke until they intersect. In case the extension goes beyond the limit, the join style is converted to the Bevel style. +}; + + +/** + * @brief Enumeration specifying how to fill the area outside the gradient bounds. + */ +enum class FillSpread : uint8_t +{ + Pad = 0, ///< The remaining area is filled with the closest stop color. + Reflect, ///< The gradient pattern is reflected outside the gradient area until the expected region is filled. + Repeat ///< The gradient pattern is repeated continuously beyond the gradient area until the expected region is filled. +}; + + +/** + * @brief Enumeration specifying the algorithm used to establish which parts of the shape are treated as the inside of the shape. + */ +enum class FillRule : uint8_t +{ + Winding = 0, ///< A line from the point to a location outside the shape is drawn. The intersections of the line with the path segment of the shape are counted. Starting from zero, if the path segment of the shape crosses the line clockwise, one is added, otherwise one is subtracted. If the resulting sum is non zero, the point is inside the shape. + EvenOdd ///< A line from the point to a location outside the shape is drawn and its intersections with the path segments of the shape are counted. If the number of intersections is an odd number, the point is inside the shape. +}; + + +/** + * @brief Enumeration indicating the method used in the composition of two objects - the target and the source. + * + * Notation: S(Source), T(Target), SA(Source Alpha), TA(Target Alpha) + * + * @see Paint::composite() + */ +enum class CompositeMethod : uint8_t +{ + None = 0, ///< No composition is applied. + ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered. + AlphaMask, ///< Alpha Masking using the compositing target's pixels as an alpha value. + InvAlphaMask, ///< Alpha Masking using the complement to the compositing target's pixels as an alpha value. + LumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the compositing target's pixels. @since 0.9 + InvLumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the complement to the compositing target's pixels. + AddMask, ///< Combines the target and source objects pixels using target alpha. (T * TA) + (S * (255 - TA)) (Experimental API) + SubtractMask, ///< Subtracts the source color from the target color while considering their respective target alpha. (T * TA) - (S * (255 - TA)) (Experimental API) + IntersectMask, ///< Computes the result by taking the minimum value between the target alpha and the source alpha and multiplies it with the target color. (T * min(TA, SA)) (Experimental API) + DifferenceMask ///< Calculates the absolute difference between the target color and the source color multiplied by the complement of the target alpha. abs(T - S * (255 - TA)) (Experimental API) +}; + + +/** + * @brief Enumeration indicates the method used for blending paint. Please refer to the respective formulas for each method. + * + * Notation: S(source paint as the top layer), D(destination as the bottom layer), Sa(source paint alpha), Da(destination alpha) + * + * @see Paint::blend() + * + * @note Experimental API + */ +enum class BlendMethod : uint8_t +{ + Normal = 0, ///< Perform the alpha blending(default). S if (Sa == 255), otherwise (Sa * S) + (255 - Sa) * D + Add, ///< Simply adds pixel values of one layer with the other. (S + D) + Screen, ///< The values of the pixels in the two layers are inverted, multiplied, and then inverted again. (S + D) - (S * D) + Multiply, ///< Takes the RGB channel values from 0 to 255 of each pixel in the top layer and multiples them with the values for the corresponding pixel from the bottom layer. (S * D) + Overlay, ///< Combines Multiply and Screen blend modes. (2 * S * D) if (2 * D < Da), otherwise (Sa * Da) - 2 * (Da - S) * (Sa - D) + Difference, ///< Subtracts the bottom layer from the top layer or the other way around, to always get a non-negative value. (S - D) if (S > D), otherwise (D - S) + Exclusion, ///< The result is twice the product of the top and bottom layers, subtracted from their sum. s + d - (2 * s * d) + SrcOver, ///< Replace the bottom layer with the top layer. + Darken, ///< Creates a pixel that retains the smallest components of the top and bottom layer pixels. min(S, D) + Lighten, ///< Only has the opposite action of Darken Only. max(S, D) + ColorDodge, ///< Divides the bottom layer by the inverted top layer. D / (255 - S) + ColorBurn, ///< Divides the inverted bottom layer by the top layer, and then inverts the result. 255 - (255 - D) / S + HardLight, ///< The same as Overlay but with the color roles reversed. (2 * S * D) if (S < Sa), otherwise (Sa * Da) - 2 * (Da - S) * (Sa - D) + SoftLight ///< The same as Overlay but with applying pure black or white does not result in pure black or white. (1 - 2 * S) * (D ^ 2) + (2 * S * D) +}; + + +/** + * @brief Enumeration specifying the engine type used for the graphics backend. For multiple backends bitwise operation is allowed. + */ +enum class CanvasEngine : uint8_t +{ + All = 0, ///< All feasible rasterizers. @since 1.0 + Sw = (1 << 1), ///< CPU rasterizer. + Gl = (1 << 2), ///< OpenGL rasterizer. + Wg = (1 << 3), ///< WebGPU rasterizer. (Experimental API) +}; + + +/** + * @brief A data structure representing a point in two-dimensional space. + */ +struct Point +{ + float x, y; +}; + + +/** + * @brief A data structure representing a three-dimensional matrix. + * + * The elements e11, e12, e21 and e22 represent the rotation matrix, including the scaling factor. + * The elements e13 and e23 determine the translation of the object along the x and y-axis, respectively. + * The elements e31 and e32 are set to 0, e33 is set to 1. + */ +struct Matrix +{ + float e11, e12, e13; + float e21, e22, e23; + float e31, e32, e33; +}; + + +/** + * @brief A data structure representing a texture mesh vertex + * + * @param pt The vertex coordinate + * @param uv The normalized texture coordinate in the range (0.0..1.0, 0.0..1.0) + * + * @note Experimental API + */ +struct Vertex +{ + Point pt; + Point uv; +}; + + +/** + * @brief A data structure representing a triange in a texture mesh + * + * @param vertex The three vertices that make up the polygon + * + * @note Experimental API + */ +struct Polygon +{ + Vertex vertex[3]; +}; + + +/** + * @class Paint + * + * @brief An abstract class for managing graphical elements. + * + * A graphical element in TVG is any object composed into a Canvas. + * Paint represents such a graphical object and its behaviors such as duplication, transformation and composition. + * TVG recommends the user to regard a paint as a set of volatile commands. They can prepare a Paint and then request a Canvas to run them. + */ +class TVG_API Paint +{ +public: + virtual ~Paint(); + + /** + * @brief Sets the angle by which the object is rotated. + * + * The angle in measured clockwise from the horizontal axis. + * The rotational axis passes through the point on the object with zero coordinates. + * + * @param[in] degree The value of the angle in degrees. + * + * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result rotate(float degree) noexcept; + + /** + * @brief Sets the scale value of the object. + * + * @param[in] factor The value of the scaling factor. The default value is 1. + * + * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result scale(float factor) noexcept; + + /** + * @brief Sets the values by which the object is moved in a two-dimensional space. + * + * The origin of the coordinate system is in the upper left corner of the canvas. + * The horizontal and vertical axes point to the right and down, respectively. + * + * @param[in] x The value of the horizontal shift. + * @param[in] y The value of the vertical shift. + * + * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result translate(float x, float y) noexcept; + + /** + * @brief Sets the matrix of the affine transformation for the object. + * + * The augmented matrix of the transformation is expected to be given. + * + * @param[in] m The 3x3 augmented matrix. + * + * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result transform(const Matrix& m) noexcept; + + /** + * @brief Gets the matrix of the affine transformation of the object. + * + * The values of the matrix can be set by the transform() API, as well by the translate(), + * scale() and rotate(). In case no transformation was applied, the identity matrix is returned. + * + * @return The augmented transformation matrix. + * + * @since 0.4 + */ + Matrix transform() noexcept; + + /** + * @brief Sets the opacity of the object. + * + * @param[in] o The opacity value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. + * + * @retval Result::Success when succeed. + * + * @note Setting the opacity with this API may require multiple render pass for composition. It is recommended to avoid changing the opacity if possible. + * @note ClipPath won't use the opacity value. (see: enum class CompositeMethod::ClipPath) + */ + Result opacity(uint8_t o) noexcept; + + /** + * @brief Sets the composition target object and the composition method. + * + * @param[in] target The paint of the target object. + * @param[in] method The method used to composite the source object with the target. + * + * @retval Result::Success when succeed, Result::InvalidArguments otherwise. + */ + Result composite(std::unique_ptr target, CompositeMethod method) noexcept; + + /** + * @brief Sets the blending method for the paint object. + * + * The blending feature allows you to combine colors to create visually appealing effects, including transparency, lighting, shading, and color mixing, among others. + * its process involves the combination of colors or images from the source paint object with the destination (the lower layer image) using blending operations. + * The blending operation is determined by the chosen @p BlendMethod, which specifies how the colors or images are combined. + * + * @param[in] method The blending method to be set. + * + * @retval Result::Success when the blending method is successfully set. + * + * @note Experimental API + */ + Result blend(BlendMethod method) const noexcept; + + /** + * @brief Gets the axis-aligned bounding box of the paint object. + * + * In case @p transform is @c true, all object's transformations are applied first, and then the bounding box is established. Otherwise, the bounding box is determined before any transformations. + * + * @param[out] x The x coordinate of the upper left corner of the object. + * @param[out] y The y coordinate of the upper left corner of the object. + * @param[out] w The width of the object. + * @param[out] h The height of the object. + * @param[in] transformed If @c true, the paint's transformations are taken into account, otherwise they aren't. + * + * @retval Result::Success when succeed, Result::InsufficientCondition otherwise. + * + * @note The bounding box doesn't indicate the actual drawing region. It's the smallest rectangle that encloses the object. + */ + Result bounds(float* x, float* y, float* w, float* h, bool transformed = false) const noexcept; + + /** + * @brief Duplicates the object. + * + * Creates a new object and sets its all properties as in the original object. + * + * @return The created object when succeed, @c nullptr otherwise. + */ + Paint* duplicate() const noexcept; + + /** + * @brief Gets the opacity value of the object. + * + * @return The opacity value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. + */ + uint8_t opacity() const noexcept; + + /** + * @brief Gets the composition target object and the composition method. + * + * @param[out] target The paint of the target object. + * + * @return The method used to composite the source object with the target. + * + * @since 0.5 + */ + CompositeMethod composite(const Paint** target) const noexcept; + + /** + * @brief Gets the blending method of the object. + * + * @return The blending method + * + * @note Experimental API + */ + BlendMethod blend() const noexcept; + + /** + * @brief Return the unique id value of the paint instance. + * + * This method can be called for checking the current concrete instance type. + * + * @return The type id of the Paint instance. + */ + uint32_t identifier() const noexcept; + + _TVG_DECLARE_PRIVATE(Paint); +}; + + +/** + * @class Fill + * + * @brief An abstract class representing the gradient fill of the Shape object. + * + * It contains the information about the gradient colors and their arrangement + * inside the gradient bounds. The gradients bounds are defined in the LinearGradient + * or RadialGradient class, depending on the type of the gradient to be used. + * It specifies the gradient behavior in case the area defined by the gradient bounds + * is smaller than the area to be filled. + */ +class TVG_API Fill +{ +public: + /** + * @brief A data structure storing the information about the color and its relative position inside the gradient bounds. + */ + struct ColorStop + { + float offset; /**< The relative position of the color. */ + uint8_t r; /**< The red color channel value in the range [0 ~ 255]. */ + uint8_t g; /**< The green color channel value in the range [0 ~ 255]. */ + uint8_t b; /**< The blue color channel value in the range [0 ~ 255]. */ + uint8_t a; /**< The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. */ + }; + + virtual ~Fill(); + + /** + * @brief Sets the parameters of the colors of the gradient and their position. + * + * @param[in] colorStops An array of ColorStop data structure. + * @param[in] cnt The count of the @p colorStops array equal to the colors number used in the gradient. + * + * @retval Result::Success when succeed. + */ + Result colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept; + + /** + * @brief Sets the FillSpread value, which specifies how to fill the area outside the gradient bounds. + * + * @param[in] s The FillSpread value. + * + * @retval Result::Success when succeed. + */ + Result spread(FillSpread s) noexcept; + + /** + * @brief Sets the matrix of the affine transformation for the gradient fill. + * + * The augmented matrix of the transformation is expected to be given. + * + * @param[in] m The 3x3 augmented matrix. + * + * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result transform(const Matrix& m) noexcept; + + /** + * @brief Gets the parameters of the colors of the gradient, their position and number. + * + * @param[out] colorStops A pointer to the memory location, where the array of the gradient's ColorStop is stored. + * + * @return The number of colors used in the gradient. This value corresponds to the length of the @p colorStops array. + */ + uint32_t colorStops(const ColorStop** colorStops) const noexcept; + + /** + * @brief Gets the FillSpread value of the fill. + * + * @return The FillSpread value of this Fill. + */ + FillSpread spread() const noexcept; + + /** + * @brief Gets the matrix of the affine transformation of the gradient fill. + * + * In case no transformation was applied, the identity matrix is returned. + * + * @return The augmented transformation matrix. + */ + Matrix transform() const noexcept; + + /** + * @brief Creates a copy of the Fill object. + * + * Return a newly created Fill object with the properties copied from the original. + * + * @return A copied Fill object when succeed, @c nullptr otherwise. + */ + Fill* duplicate() const noexcept; + + /** + * @brief Return the unique id value of the Fill instance. + * + * This method can be called for checking the current concrete instance type. + * + * @return The type id of the Fill instance. + */ + uint32_t identifier() const noexcept; + + _TVG_DECLARE_PRIVATE(Fill); +}; + + +/** + * @class Canvas + * + * @brief An abstract class for drawing graphical elements. + * + * A canvas is an entity responsible for drawing the target. It sets up the drawing engine and the buffer, which can be drawn on the screen. It also manages given Paint objects. + * + * @note A Canvas behavior depends on the raster engine though the final content of the buffer is expected to be identical. + * @warning The Paint objects belonging to one Canvas can't be shared among multiple Canvases. + */ +class TVG_API Canvas +{ +public: + Canvas(RenderMethod*); + virtual ~Canvas(); + + /** + * @brief Returns the list of the paints that currently held by the Canvas. + * + * This function provides the list of paint nodes, allowing users a direct opportunity to modify the scene tree. + * + * @warning Please avoid accessing the paints during Canvas update/draw. You can access them after calling sync(). + * @see Canvas::sync() + * + * @note Experimental API + */ + std::list& paints() noexcept; + + /** + * @brief Passes drawing elements to the Canvas using Paint objects. + * + * Only pushed paints in the canvas will be drawing targets. + * They are retained by the canvas until you call Canvas::clear(). + * + * @param[in] paint A Paint object to be drawn. + * + * @retval Result::Success When succeed. + * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument. + * @retval Result::InsufficientCondition An internal error. + * + * @note The rendering order of the paints is the same as the order as they were pushed into the canvas. Consider sorting the paints before pushing them if you intend to use layering. + * @see Canvas::paints() + * @see Canvas::clear() + */ + virtual Result push(std::unique_ptr paint) noexcept; + + /** + * @brief Clear the internal canvas resources that used for the drawing. + * + * This API sets the total number of paints pushed into the canvas to zero. + * Depending on the value of the @p free argument, the paints are either freed or retained. + * So if you need to update paint properties while maintaining the existing scene structure, you can set @p free = false. + * + * @param[in] paints If @c true, The memory occupied by paints is deallocated; otherwise, the paints will be retained on the canvas. + * @param[in] buffer If @c true, the canvas target buffer is cleared with a zero value. + * + * @retval Result::Success when succeed, Result::InsufficientCondition otherwise. + * + * @see Canvas::push() + * @see Canvas::paints() + */ + virtual Result clear(bool paints = true, bool buffer = true) noexcept; + + /** + * @brief Request the canvas to update the paint objects. + * + * If a @c nullptr is passed all paint objects retained by the Canvas are updated, + * otherwise only the paint to which the given @p paint points. + * + * @param[in] paint A pointer to the Paint object or @c nullptr. + * + * @retval Result::Success when succeed, Result::InsufficientCondition otherwise. + * + * @note The Update behavior can be asynchronous if the assigned thread number is greater than zero. + */ + virtual Result update(Paint* paint = nullptr) noexcept; + + /** + * @brief Requests the canvas to draw the Paint objects. + * + * @retval Result::Success when succeed, Result::InsufficientCondition otherwise. + * + * @note Drawing can be asynchronous if the assigned thread number is greater than zero. To guarantee the drawing is done, call sync() afterwards. + * @see Canvas::sync() + */ + virtual Result draw() noexcept; + + /** + * @brief Guarantees that drawing task is finished. + * + * The Canvas rendering can be performed asynchronously. To make sure that rendering is finished, + * the sync() must be called after the draw() regardless of threading. + * + * @retval Result::Success when succeed, Result::InsufficientCondition otherwise. + * @see Canvas::draw() + */ + virtual Result sync() noexcept; + + _TVG_DECLARE_PRIVATE(Canvas); +}; + + +/** + * @class LinearGradient + * + * @brief A class representing the linear gradient fill of the Shape object. + * + * Besides the APIs inherited from the Fill class, it enables setting and getting the linear gradient bounds. + * The behavior outside the gradient bounds depends on the value specified in the spread API. + */ +class TVG_API LinearGradient final : public Fill +{ +public: + ~LinearGradient(); + + /** + * @brief Sets the linear gradient bounds. + * + * The bounds of the linear gradient are defined as a surface constrained by two parallel lines crossing + * the given points (@p x1, @p y1) and (@p x2, @p y2), respectively. Both lines are perpendicular to the line linking + * (@p x1, @p y1) and (@p x2, @p y2). + * + * @param[in] x1 The horizontal coordinate of the first point used to determine the gradient bounds. + * @param[in] y1 The vertical coordinate of the first point used to determine the gradient bounds. + * @param[in] x2 The horizontal coordinate of the second point used to determine the gradient bounds. + * @param[in] y2 The vertical coordinate of the second point used to determine the gradient bounds. + * + * @retval Result::Success when succeed. + * + * @note In case the first and the second points are equal, an object filled with such a gradient fill is not rendered. + */ + Result linear(float x1, float y1, float x2, float y2) noexcept; + + /** + * @brief Gets the linear gradient bounds. + * + * The bounds of the linear gradient are defined as a surface constrained by two parallel lines crossing + * the given points (@p x1, @p y1) and (@p x2, @p y2), respectively. Both lines are perpendicular to the line linking + * (@p x1, @p y1) and (@p x2, @p y2). + * + * @param[out] x1 The horizontal coordinate of the first point used to determine the gradient bounds. + * @param[out] y1 The vertical coordinate of the first point used to determine the gradient bounds. + * @param[out] x2 The horizontal coordinate of the second point used to determine the gradient bounds. + * @param[out] y2 The vertical coordinate of the second point used to determine the gradient bounds. + * + * @retval Result::Success when succeed. + */ + Result linear(float* x1, float* y1, float* x2, float* y2) const noexcept; + + /** + * @brief Creates a new LinearGradient object. + * + * @return A new LinearGradient object. + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the LinearGradient class type. + * + * @return The type id of the LinearGradient class. + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_PRIVATE(LinearGradient); +}; + + +/** + * @class RadialGradient + * + * @brief A class representing the radial gradient fill of the Shape object. + * + */ +class TVG_API RadialGradient final : public Fill +{ +public: + ~RadialGradient(); + + /** + * @brief Sets the radial gradient bounds. + * + * The radial gradient bounds are defined as a circle centered in a given point (@p cx, @p cy) of a given radius. + * + * @param[in] cx The horizontal coordinate of the center of the bounding circle. + * @param[in] cy The vertical coordinate of the center of the bounding circle. + * @param[in] radius The radius of the bounding circle. + * + * @retval Result::Success when succeed, Result::InvalidArguments in case the @p radius value is zero or less. + */ + Result radial(float cx, float cy, float radius) noexcept; + + /** + * @brief Gets the radial gradient bounds. + * + * The radial gradient bounds are defined as a circle centered in a given point (@p cx, @p cy) of a given radius. + * + * @param[out] cx The horizontal coordinate of the center of the bounding circle. + * @param[out] cy The vertical coordinate of the center of the bounding circle. + * @param[out] radius The radius of the bounding circle. + * + * @retval Result::Success when succeed. + */ + Result radial(float* cx, float* cy, float* radius) const noexcept; + + /** + * @brief Creates a new RadialGradient object. + * + * @return A new RadialGradient object. + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the RadialGradient class type. + * + * @return The type id of the RadialGradient class. + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_PRIVATE(RadialGradient); +}; + + +/** + * @class Shape + * + * @brief A class representing two-dimensional figures and their properties. + * + * A shape has three major properties: shape outline, stroking, filling. The outline in the Shape is retained as the path. + * Path can be composed by accumulating primitive commands such as moveTo(), lineTo(), cubicTo(), or complete shape interfaces such as appendRect(), appendCircle(), etc. + * Path can consists of sub-paths. One sub-path is determined by a close command. + * + * The stroke of Shape is an optional property in case the Shape needs to be represented with/without the outline borders. + * It's efficient since the shape path and the stroking path can be shared with each other. It's also convenient when controlling both in one context. + */ +class TVG_API Shape final : public Paint +{ +public: + ~Shape(); + + /** + * @brief Resets the properties of the shape path. + * + * The transformation matrix, the color, the fill and the stroke properties are retained. + * + * @retval Result::Success when succeed. + * + * @note The memory, where the path data is stored, is not deallocated at this stage for caching effect. + */ + Result reset() noexcept; + + /** + * @brief Sets the initial point of the sub-path. + * + * The value of the current point is set to the given point. + * + * @param[in] x The horizontal coordinate of the initial point of the sub-path. + * @param[in] y The vertical coordinate of the initial point of the sub-path. + * + * @retval Result::Success when succeed. + */ + Result moveTo(float x, float y) noexcept; + + /** + * @brief Adds a new point to the sub-path, which results in drawing a line from the current point to the given end-point. + * + * The value of the current point is set to the given end-point. + * + * @param[in] x The horizontal coordinate of the end-point of the line. + * @param[in] y The vertical coordinate of the end-point of the line. + * + * @retval Result::Success when succeed. + * + * @note In case this is the first command in the path, it corresponds to the moveTo() call. + */ + Result lineTo(float x, float y) noexcept; + + /** + * @brief Adds new points to the sub-path, which results in drawing a cubic Bezier curve starting + * at the current point and ending at the given end-point (@p x, @p y) using the control points (@p cx1, @p cy1) and (@p cx2, @p cy2). + * + * The value of the current point is set to the given end-point. + * + * @param[in] cx1 The horizontal coordinate of the 1st control point. + * @param[in] cy1 The vertical coordinate of the 1st control point. + * @param[in] cx2 The horizontal coordinate of the 2nd control point. + * @param[in] cy2 The vertical coordinate of the 2nd control point. + * @param[in] x The horizontal coordinate of the end-point of the curve. + * @param[in] y The vertical coordinate of the end-point of the curve. + * + * @retval Result::Success when succeed. + * + * @note In case this is the first command in the path, no data from the path are rendered. + */ + Result cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept; + + /** + * @brief Closes the current sub-path by drawing a line from the current point to the initial point of the sub-path. + * + * The value of the current point is set to the initial point of the closed sub-path. + * + * @retval Result::Success when succeed. + * + * @note In case the sub-path does not contain any points, this function has no effect. + */ + Result close() noexcept; + + /** + * @brief Appends a rectangle to the path. + * + * The rectangle with rounded corners can be achieved by setting non-zero values to @p rx and @p ry arguments. + * The @p rx and @p ry values specify the radii of the ellipse defining the rounding of the corners. + * + * The position of the rectangle is specified by the coordinates of its upper left corner - @p x and @p y arguments. + * + * The rectangle is treated as a new sub-path - it is not connected with the previous sub-path. + * + * The value of the current point is set to (@p x + @p rx, @p y) - in case @p rx is greater + * than @p w/2 the current point is set to (@p x + @p w/2, @p y) + * + * @param[in] x The horizontal coordinate of the upper left corner of the rectangle. + * @param[in] y The vertical coordinate of the upper left corner of the rectangle. + * @param[in] w The width of the rectangle. + * @param[in] h The height of the rectangle. + * @param[in] rx The x-axis radius of the ellipse defining the rounded corners of the rectangle. + * @param[in] ry The y-axis radius of the ellipse defining the rounded corners of the rectangle. + * + * @retval Result::Success when succeed. + * + * @note For @p rx and @p ry greater than or equal to the half of @p w and the half of @p h, respectively, the shape become an ellipse. + */ + Result appendRect(float x, float y, float w, float h, float rx = 0, float ry = 0) noexcept; + + /** + * @brief Appends an ellipse to the path. + * + * The position of the ellipse is specified by the coordinates of its center - @p cx and @p cy arguments. + * + * The ellipse is treated as a new sub-path - it is not connected with the previous sub-path. + * + * The value of the current point is set to (@p cx, @p cy - @p ry). + * + * @param[in] cx The horizontal coordinate of the center of the ellipse. + * @param[in] cy The vertical coordinate of the center of the ellipse. + * @param[in] rx The x-axis radius of the ellipse. + * @param[in] ry The y-axis radius of the ellipse. + * + * @retval Result::Success when succeed. + */ + Result appendCircle(float cx, float cy, float rx, float ry) noexcept; + + /** + * @brief Appends a circular arc to the path. + * + * The arc is treated as a new sub-path - it is not connected with the previous sub-path. + * The current point value is set to the end-point of the arc in case @p pie is @c false, and to the center of the arc otherwise. + * + * @param[in] cx The horizontal coordinate of the center of the arc. + * @param[in] cy The vertical coordinate of the center of the arc. + * @param[in] radius The radius of the arc. + * @param[in] startAngle The start angle of the arc given in degrees, measured counter-clockwise from the horizontal line. + * @param[in] sweep The central angle of the arc given in degrees, measured counter-clockwise from @p startAngle. + * @param[in] pie Specifies whether to draw radii from the arc's center to both of its end-point - drawn if @c true. + * + * @retval Result::Success when succeed. + * + * @note Setting @p sweep value greater than 360 degrees, is equivalent to calling appendCircle(cx, cy, radius, radius). + */ + Result appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept; + + /** + * @brief Appends a given sub-path to the path. + * + * The current point value is set to the last point from the sub-path. + * For each command from the @p cmds array, an appropriate number of points in @p pts array should be specified. + * If the number of points in the @p pts array is different than the number required by the @p cmds array, the shape with this sub-path will not be displayed on the screen. + * + * @param[in] cmds The array of the commands in the sub-path. + * @param[in] cmdCnt The number of the sub-path's commands. + * @param[in] pts The array of the two-dimensional points. + * @param[in] ptsCnt The number of the points in the @p pts array. + * + * @retval Result::Success when succeed, Result::InvalidArguments otherwise. + * + * @note The interface is designed for optimal path setting if the caller has a completed path commands already. + */ + Result appendPath(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept; + + /** + * @brief Sets the stroke width for all of the figures from the path. + * + * @param[in] width The width of the stroke. The default value is 0. + * + * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result strokeWidth(float width) noexcept; + + /** + * @brief Sets the color of the stroke for all of the figures from the path. + * + * @param[in] r The red color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] g The green color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0. + * + * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result strokeFill(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept; + + /** + * @brief Sets the gradient fill of the stroke for all of the figures from the path. + * + * @param[in] f The gradient fill. + * + * @retval Result::Success When succeed. + * @retval Result::FailedAllocation An internal error with a memory allocation for an object to be filled. + * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument. + */ + Result strokeFill(std::unique_ptr f) noexcept; + + /** + * @brief Sets the dash pattern of the stroke. + * + * @param[in] dashPattern The array of consecutive pair values of the dash length and the gap length. + * @param[in] cnt The length of the @p dashPattern array. + * @param[in] offset The shift of the starting point within the repeating dash pattern from which the path's dashing begins. + * + * @retval Result::Success When succeed. + * @retval Result::FailedAllocation An internal error with a memory allocation for an object to be dashed. + * @retval Result::InvalidArguments In case @p dashPattern is @c nullptr and @p cnt > 0, @p cnt is zero, any of the dash pattern values is zero or less. + * + * @note To reset the stroke dash pattern, pass @c nullptr to @p dashPattern and zero to @p cnt. + * @warning @p cnt must be greater than 1 if the dash pattern is valid. + * + * @since 1.0 + */ + Result strokeDash(const float* dashPattern, uint32_t cnt, float offset = 0.0f) noexcept; + + /** + * @brief Sets the cap style of the stroke in the open sub-paths. + * + * @param[in] cap The cap style value. The default value is @c StrokeCap::Square. + * + * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result strokeCap(StrokeCap cap) noexcept; + + /** + * @brief Sets the join style for stroked path segments. + * + * The join style is used for joining the two line segment while stroking the path. + * + * @param[in] join The join style value. The default value is @c StrokeJoin::Bevel. + * + * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result strokeJoin(StrokeJoin join) noexcept; + + + /** + * @brief Sets the stroke miterlimit. + * + * @param[in] miterlimit The miterlimit imposes a limit on the extent of the stroke join, when the @c StrokeJoin::Miter join style is set. The default value is 4. + * + * @retval Result::Success when succeed, Result::NonSupport unsupported value, Result::FailedAllocation otherwise. + * + * @since 0.11 + */ + Result strokeMiterlimit(float miterlimit) noexcept; + + /** + * @brief Sets the solid color for all of the figures from the path. + * + * The parts of the shape defined as inner are colored. + * + * @param[in] r The red color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] g The green color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0. + * + * @retval Result::Success when succeed. + * + * @note Either a solid color or a gradient fill is applied, depending on what was set as last. + * @note ClipPath won't use the fill values. (see: enum class CompositeMethod::ClipPath) + */ + Result fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept; + + /** + * @brief Sets the gradient fill for all of the figures from the path. + * + * The parts of the shape defined as inner are filled. + * + * @param[in] f The unique pointer to the gradient fill. + * + * @retval Result::Success when succeed, Result::MemoryCorruption otherwise. + * + * @note Either a solid color or a gradient fill is applied, depending on what was set as last. + */ + Result fill(std::unique_ptr f) noexcept; + + /** + * @brief Sets the fill rule for the Shape object. + * + * @param[in] r The fill rule value. The default value is @c FillRule::Winding. + * + * @retval Result::Success when succeed. + */ + Result fill(FillRule r) noexcept; + + + /** + * @brief Sets the rendering order of the stroke and the fill. + * + * @param[in] strokeFirst If @c true the stroke is rendered before the fill, otherwise the stroke is rendered as the second one (the default option). + * + * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * + * @since 0.10 + */ + Result order(bool strokeFirst) noexcept; + + + /** + * @brief Gets the commands data of the path. + * + * @param[out] cmds The pointer to the array of the commands from the path. + * + * @return The length of the @p cmds array when succeed, zero otherwise. + */ + uint32_t pathCommands(const PathCommand** cmds) const noexcept; + + /** + * @brief Gets the points values of the path. + * + * @param[out] pts The pointer to the array of the two-dimensional points from the path. + * + * @return The length of the @p pts array when succeed, zero otherwise. + */ + uint32_t pathCoords(const Point** pts) const noexcept; + + /** + * @brief Gets the pointer to the gradient fill of the shape. + * + * @return The pointer to the gradient fill of the stroke when succeed, @c nullptr in case no fill was set. + */ + const Fill* fill() const noexcept; + + /** + * @brief Gets the solid color of the shape. + * + * @param[out] r The red color channel value in the range [0 ~ 255]. + * @param[out] g The green color channel value in the range [0 ~ 255]. + * @param[out] b The blue color channel value in the range [0 ~ 255]. + * @param[out] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. + * + * @return Result::Success when succeed. + */ + Result fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a = nullptr) const noexcept; + + /** + * @brief Gets the fill rule value. + * + * @return The fill rule value of the shape. + */ + FillRule fillRule() const noexcept; + + /** + * @brief Gets the stroke width. + * + * @return The stroke width value when succeed, zero if no stroke was set. + */ + float strokeWidth() const noexcept; + + /** + * @brief Gets the color of the shape's stroke. + * + * @param[out] r The red color channel value in the range [0 ~ 255]. + * @param[out] g The green color channel value in the range [0 ~ 255]. + * @param[out] b The blue color channel value in the range [0 ~ 255]. + * @param[out] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. + * + * @retval Result::Success when succeed, Result::InsufficientCondition otherwise. + */ + Result strokeFill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a = nullptr) const noexcept; + + /** + * @brief Gets the pointer to the gradient fill of the stroke. + * + * @return The pointer to the gradient fill of the stroke when succeed, @c nullptr otherwise. + */ + const Fill* strokeFill() const noexcept; + + /** + * @brief Gets the dash pattern of the stroke. + * + * @param[out] dashPattern The pointer to the memory, where the dash pattern array is stored. + * @param[out] offset The shift of the starting point within the repeating dash pattern. + * + * @return The length of the @p dashPattern array. + * + * @since 1.0 + */ + uint32_t strokeDash(const float** dashPattern, float* offset = nullptr) const noexcept; + + /** + * @brief Gets the cap style used for stroking the path. + * + * @return The cap style value of the stroke. + */ + StrokeCap strokeCap() const noexcept; + + /** + * @brief Gets the join style value used for stroking the path. + * + * @return The join style value of the stroke. + */ + StrokeJoin strokeJoin() const noexcept; + + /** + * @brief Gets the stroke miterlimit. + * + * @return The stroke miterlimit value when succeed, 4 if no stroke was set. + * + * @since 0.11 + */ + float strokeMiterlimit() const noexcept; + + /** + * @brief Creates a new Shape object. + * + * @return A new Shape object. + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the Shape class type. + * + * @return The type id of the Shape class. + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_PRIVATE(Shape); +}; + + +/** + * @class Picture + * + * @brief A class representing an image read in one of the supported formats: raw, svg, png, jpg, lottie(json) and etc. + * Besides the methods inherited from the Paint, it provides methods to load & draw images on the canvas. + * + * @note Supported formats are depended on the available TVG loaders. + * @note See Animation class if the picture data is animatable. + */ +class TVG_API Picture final : public Paint +{ +public: + ~Picture(); + + /** + * @brief Loads a picture data directly from a file. + * + * ThorVG efficiently caches the loaded data using the specified @p path as a key. + * This means that loading the same file again will not result in duplicate operations; + * instead, ThorVG will reuse the previously loaded picture data. + * + * @param[in] path A path to the picture file. + * + * @retval Result::Success When succeed. + * @retval Result::InvalidArguments In case the @p path is invalid. + * @retval Result::NonSupport When trying to load a file with an unknown extension. + * @retval Result::Unknown If an error occurs at a later stage. + * + * @note The Load behavior can be asynchronous if the assigned thread number is greater than zero. + * @see Initializer::init() + */ + Result load(const std::string& path) noexcept; + + /** + * @brief Loads a picture data from a memory block of a given size. + * + * ThorVG efficiently caches the loaded data using the specified @p data address as a key + * when the @p copy has @c false. This means that loading the same data again will not result in duplicate operations + * for the sharable @p data. Instead, ThorVG will reuse the previously loaded picture data. + * + * @param[in] data A pointer to a memory location where the content of the picture file is stored. + * @param[in] size The size in bytes of the memory occupied by the @p data. + * @param[in] mimeType Mimetype or extension of data such as "jpg", "jpeg", "lottie", "svg", "svg+xml", "png", etc. In case an empty string or an unknown type is provided, the loaders will be tried one by one. + * @param[in] rpath A resource directory path, if the @p data needs to access any external resources. + * @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not. + * + * @retval Result::Success When succeed. + * @retval Result::InvalidArguments In case no data are provided or the @p size is zero or less. + * @retval Result::NonSupport When trying to load a file with an unknown extension. + * @retval Result::Unknown If an error occurs at a later stage. + * + * @warning: It's the user responsibility to release the @p data memory. + * + * @note If you are unsure about the MIME type, you can provide an empty value like @c "", and thorvg will attempt to figure it out. + * @since 0.5 + */ + Result load(const char* data, uint32_t size, const std::string& mimeType, const std::string& rpath = "", bool copy = false) noexcept; + + /** + * @brief Resizes the picture content to the given width and height. + * + * The picture content is resized while keeping the default size aspect ratio. + * The scaling factor is established for each of dimensions and the smaller value is applied to both of them. + * + * @param[in] w A new width of the image in pixels. + * @param[in] h A new height of the image in pixels. + * + * @retval Result::Success when succeed, Result::InsufficientCondition otherwise. + */ + Result size(float w, float h) noexcept; + + /** + * @brief Gets the size of the image. + * + * @param[out] w The width of the image in pixels. + * @param[out] h The height of the image in pixels. + * + * @retval Result::Success when succeed. + */ + Result size(float* w, float* h) const noexcept; + + /** + * @brief Loads a raw data from a memory block with a given size. + * + * ThorVG efficiently caches the loaded data using the specified @p data address as a key + * when the @p copy has @c false. This means that loading the same data again will not result in duplicate operations + * for the sharable @p data. Instead, ThorVG will reuse the previously loaded picture data. + * + * @param[in] paint A Tvg_Paint pointer to the picture object. + * @param[in] data A pointer to a memory location where the content of the picture raw data is stored. + * @param[in] w The width of the image @p data in pixels. + * @param[in] h The height of the image @p data in pixels. + * @param[in] premultiplied If @c true, the given image data is alpha-premultiplied. + * @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not. + * + * @retval Result::Success When succeed, Result::InsufficientCondition otherwise. + * @retval Result::FailedAllocation An internal error possibly with memory allocation. + * + * @since 0.9 + */ + Result load(uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy = false) noexcept; + + /** + * @brief Sets or removes the triangle mesh to deform the image. + * + * If a mesh is provided, the transform property of the Picture will apply to the triangle mesh, and the + * image data will be used as the texture. + * + * If @p triangles is @c nullptr, or @p triangleCnt is 0, the mesh will be removed. + * + * Only raster image types are supported at this time (png, jpg). Vector types like svg and tvg do not support. + * mesh deformation. However, if required you should be able to render a vector image to a raster image and then apply a mesh. + * + * @param[in] triangles An array of Polygons(triangles) that make up the mesh, or null to remove the mesh. + * @param[in] triangleCnt The number of Polygons(triangles) provided, or 0 to remove the mesh. + * + * @retval Result::Success When succeed. + * @retval Result::Unknown If fails + * + * @note The Polygons are copied internally, so modifying them after calling Mesh::mesh has no affect. + * @warning Please do not use it, this API is not official one. It could be modified in the next version. + * + * @note Experimental API + */ + Result mesh(const Polygon* triangles, uint32_t triangleCnt) noexcept; + + /** + * @brief Return the number of triangles in the mesh, and optionally get a pointer to the array of triangles in the mesh. + * + * @param[out] triangles Optional. A pointer to the array of Polygons used by this mesh. + * + * @return The number of polygons in the array. + * + * @note Modifying the triangles returned by this method will modify them directly within the mesh. + * @warning Please do not use it, this API is not official one. It could be modified in the next version. + * + * @note Experimental API + */ + uint32_t mesh(const Polygon** triangles) const noexcept; + + /** + * @brief Creates a new Picture object. + * + * @return A new Picture object. + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the Picture class type. + * + * @return The type id of the Picture class. + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_ACCESSOR(Animation); + _TVG_DECLARE_PRIVATE(Picture); +}; + + +/** + * @class Scene + * + * @brief A class to composite children paints. + * + * As the traditional graphics rendering method, TVG also enables scene-graph mechanism. + * This feature supports an array function for managing the multiple paints as one group paint. + * + * As a group, the scene can be transformed, made translucent and composited with other target paints, + * its children will be affected by the scene world. + */ +class TVG_API Scene final : public Paint +{ +public: + ~Scene(); + + /** + * @brief Passes drawing elements to the Scene using Paint objects. + * + * Only the paints pushed into the scene will be the drawn targets. + * The paints are retained by the scene until Scene::clear() is called. + * + * @param[in] paint A Paint object to be drawn. + * + * @retval Result::Success when succeed, Result::MemoryCorruption otherwise. + * + * @note The rendering order of the paints is the same as the order as they were pushed. Consider sorting the paints before pushing them if you intend to use layering. + * @see Scene::paints() + * @see Scene::clear() + */ + Result push(std::unique_ptr paint) noexcept; + + /** + * @brief Returns the list of the paints that currently held by the Scene. + * + * This function provides the list of paint nodes, allowing users a direct opportunity to modify the scene tree. + * + * @warning Please avoid accessing the paints during Scene update/draw. You can access them after calling Canvas::sync(). + * @see Canvas::sync() + * @see Scene::push() + * @see Scene::clear() + * + * @note Experimental API + */ + std::list& paints() noexcept; + + /** + * @brief Sets the total number of the paints pushed into the scene to be zero. + * Depending on the value of the @p free argument, the paints are freed or not. + * + * @param[in] free If @c true, the memory occupied by paints is deallocated, otherwise it is not. + * + * @retval Result::Success when succeed + * + * @warning If you don't free the paints they become dangled. They are supposed to be reused, otherwise you are responsible for their lives. Thus please use the @p free argument only when you know how it works, otherwise it's not recommended. + * + * @since 0.2 + */ + Result clear(bool free = true) noexcept; + + /** + * @brief Creates a new Scene object. + * + * @return A new Scene object. + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the Scene class type. + * + * @return The type id of the Scene class. + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_PRIVATE(Scene); +}; + + +/** + * @class Text + * + * @brief A class to represent text objects in a graphical context, allowing for rendering and manipulation of unicode text. + * + * @note Experimental API + */ +class TVG_API Text final : public Paint +{ +public: + ~Text(); + + /** + * @brief Sets the font properties for the text. + * + * This function allows you to define the font characteristics used for text rendering. + * It sets the font name, size and optionally the style. + * + * @param[in] name The name of the font. This should correspond to a font available in the canvas. + * @param[in] size The size of the font in points. This determines how large the text will appear. + * @param[in] style The style of the font. It can be used to set the font to 'italic'. + * If not specified, the default style is used. Only 'italic' style is supported currently. + * + * @retval Result::Success when the font properties are set successfully. + * @retval Result::InsufficientCondition when the specified @p name cannot be found. + * + * @note Experimental API + */ + Result font(const char* name, float size, const char* style = nullptr) noexcept; + + /** + * @brief Assigns the given unicode text to be rendered. + * + * This function sets the unicode string that will be displayed by the rendering system. + * The text is set according to the specified UTF encoding method, which defaults to UTF-8. + * + * @param[in] text The multi-byte text encoded with utf8 string to be rendered. + * + * @retval Result::Success when succeed. + * + * @note Experimental API + */ + Result text(const char* text) noexcept; + + /** + * @brief Sets the text color. + * + * @param[in] r The red color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] g The green color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0. + * + * @retval Result::Success when succeed. + * @retval Result::InsufficientCondition when the font has not been set up prior to this operation. + * + * @see Text::font() + * + * @note Experimental API + */ + Result fill(uint8_t r, uint8_t g, uint8_t b) noexcept; + + /** + * @brief Sets the gradient fill for all of the figures from the text. + * + * The parts of the text defined as inner are filled. + * + * @param[in] f The unique pointer to the gradient fill. + * + * @retval Result::Success when succeed, Result::MemoryCorruption otherwise. + * @retval Result::InsufficientCondition when the font has not been set up prior to this operation. + * + * @note Either a solid color or a gradient fill is applied, depending on what was set as last. + * @note Experimental API + * + * @see Text::font() + */ + Result fill(std::unique_ptr f) noexcept; + + /** + * @brief Loads a scalable font data(ttf) from a file. + * + * ThorVG efficiently caches the loaded data using the specified @p path as a key. + * This means that loading the same file again will not result in duplicate operations; + * instead, ThorVG will reuse the previously loaded font data. + * + * @param[in] path The path to the font file. + * + * @retval Result::Success When succeed. + * @retval Result::InvalidArguments In case the @p path is invalid. + * @retval Result::NonSupport When trying to load a file with an unknown extension. + * @retval Result::Unknown If an error occurs at a later stage. + * + * @note Experimental API + * + * @see Text::unload(const std::string& path) + */ + static Result load(const std::string& path) noexcept; + + /** + * @brief Unloads the specified scalable font data (TTF) that was previously loaded. + * + * This function is used to release resources associated with a font file that has been loaded into memory. + * + * @param[in] path The file path of the loaded font. + * + * @retval Result::Success Successfully unloads the font data. + * @retval Result::InsufficientCondition Fails if the loader is not initialized. + * + * @note If the font data is currently in use, it will not be immediately unloaded. + * @note Experimental API + * + * @see Text::load(const std::string& path) + */ + static Result unload(const std::string& path) noexcept; + + /** + * @brief Creates a new Text object. + * + * @return A new Text object. + * + * @note Experimental API + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the Text class type. + * + * @return The type id of the Text class. + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_PRIVATE(Text); +}; + + +/** + * @class SwCanvas + * + * @brief A class for the rendering graphical elements with a software raster engine. + */ +class TVG_API SwCanvas final : public Canvas +{ +public: + ~SwCanvas(); + + /** + * @brief Enumeration specifying the methods of combining the 8-bit color channels into 32-bit color. + */ + enum Colorspace : uint8_t + { + ABGR8888 = 0, ///< The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied. (a << 24 | b << 16 | g << 8 | r) + ARGB8888, ///< The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied. (a << 24 | r << 16 | g << 8 | b) + ABGR8888S, ///< The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied. @since 0.12 + ARGB8888S, ///< The channels are joined in the order: alpha, red, green, blue. Colors are un-alpha-premultiplied. @since 0.12 + }; + + /** + * @brief Enumeration specifying the methods of Memory Pool behavior policy. + * @since 0.4 + */ + enum MempoolPolicy : uint8_t + { + Default = 0, ///< Default behavior that ThorVG is designed to. + Shareable, ///< Memory Pool is shared among the SwCanvases. + Individual ///< Allocate designated memory pool that is only used by current instance. + }; + + /** + * @brief Sets the drawing target for the rasterization. + * + * The buffer of a desirable size should be allocated and owned by the caller. + * + * @param[in] buffer A pointer to a memory block of the size @p stride x @p h, where the raster data are stored. + * @param[in] stride The stride of the raster image - greater than or equal to @p w. + * @param[in] w The width of the raster image. + * @param[in] h The height of the raster image. + * @param[in] cs The value specifying the way the 32-bits colors should be read/written. + * + * @retval Result::Success When succeed. + * @retval Result::MemoryCorruption When casting in the internal function implementation failed. + * @retval Result::InvalidArguments In case no valid pointer is provided or the width, or the height or the stride is zero. + * @retval Result::NonSupport In case the software engine is not supported. + * + * @warning Do not access @p buffer during Canvas::draw() - Canvas::sync(). It should not be accessed while TVG is writing on it. + */ + Result target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept; + + /** + * @brief Set sw engine memory pool behavior policy. + * + * Basically ThorVG draws a lot of shapes, it allocates/deallocates a few chunk of memory + * while processing rendering. It internally uses one shared memory pool + * which can be reused among the canvases in order to avoid memory overhead. + * + * Thus ThorVG suggests using a memory pool policy to satisfy user demands, + * if it needs to guarantee the thread-safety of the internal data access. + * + * @param[in] policy The method specifying the Memory Pool behavior. The default value is @c MempoolPolicy::Default. + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition If the canvas contains some paints already. + * @retval Result::NonSupport In case the software engine is not supported. + * + * @note When @c policy is set as @c MempoolPolicy::Individual, the current instance of canvas uses its own individual + * memory data, which is not shared with others. This is necessary when the canvas is accessed on a worker-thread. + * + * @warning It's not allowed after pushing any paints. + * + * @since 0.4 + */ + Result mempool(MempoolPolicy policy) noexcept; + + /** + * @brief Creates a new SwCanvas object. + * @return A new SwCanvas object. + */ + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(SwCanvas); +}; + + +/** + * @class GlCanvas + * + * @brief A class for the rendering graphic elements with a GL raster engine. + * + * @warning Please do not use it. This class is not fully supported yet. + * + * @note Experimental API + */ +class TVG_API GlCanvas final : public Canvas +{ +public: + ~GlCanvas(); + + /** + * @brief Sets the drawing target for rasterization. + * + * This function specifies the drawing target where the rasterization will occur. It can target + * a specific framebuffer object (FBO) or the main surface. + * + * @param[in] id The GL target ID, usually indicating the FBO ID. A value of @c 0 specifies the main surface. + * @param[in] w The width (in pixels) of the raster image. + * @param[in] h The height (in pixels) of the raster image. + * + * @warning This API is experimental and not officially supported. It may be modified or removed in future versions. + * @warning Drawing on the main surface is currently not permitted. If the identifier (@p id) is set to @c 0, the operation will be aborted. + * + * @note Currently, this only allows the GL_RGBA8 color space format. + * @note Experimental API + */ + Result target(int32_t id, uint32_t w, uint32_t h) noexcept; + + /** + * @brief Creates a new GlCanvas object. + * + * @return A new GlCanvas object. + * + * @note Experimental API + */ + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(GlCanvas); +}; + + +/** + * @class WgCanvas + * + * @brief A class for the rendering graphic elements with a WebGPU raster engine. + * + * @warning Please do not use it. This class is not fully supported yet. + * + * @note Experimental API + */ +class TVG_API WgCanvas final : public Canvas +{ +public: + ~WgCanvas(); + + /** + * @brief Sets the target window for the rasterization. + * + * @warning Please do not use it, this API is not official one. It could be modified in the next version. + * + * @note Experimental API + */ + Result target(void* window, uint32_t w, uint32_t h) noexcept; + + /** + * @brief Creates a new WgCanvas object. + * + * @return A new WgCanvas object. + * + * @note Experimental API + */ + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(WgCanvas); +}; + + +/** + * @class Initializer + * + * @brief A class that enables initialization and termination of the TVG engines. + */ +class TVG_API Initializer final +{ +public: + /** + * @brief Initializes TVG engines. + * + * TVG requires the running-engine environment. + * TVG runs its own task-scheduler for parallelizing rendering tasks efficiently. + * You can indicate the number of threads, the count of which is designated @p threads. + * In the initialization step, TVG will generate/spawn the threads as set by @p threads count. + * + * @param[in] threads The number of additional threads. Zero indicates only the main thread is to be used. + * @param[in] engine The engine types to initialize. This is relative to the Canvas types, in which it will be used. For multiple backends bitwise operation is allowed. + * + * @retval Result::Success When succeed. + * @retval Result::FailedAllocation An internal error possibly with memory allocation. + * @retval Result::NonSupport In case the engine type is not supported on the system. + * @retval Result::Unknown Others. + * + * @note The Initializer keeps track of the number of times it was called. Threads count is fixed at the first init() call. + * @see Initializer::term() + */ + static Result init(uint32_t threads, CanvasEngine engine = tvg::CanvasEngine::All) noexcept; + + /** + * @brief Terminates TVG engines. + * + * @param[in] engine The engine types to terminate. This is relative to the Canvas types, in which it will be used. For multiple backends bitwise operation is allowed + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition In case there is nothing to be terminated. + * @retval Result::NonSupport In case the engine type is not supported on the system. + * @retval Result::Unknown Others. + * + * @note Initializer does own reference counting for multiple calls. + * @see Initializer::init() + */ + static Result term(CanvasEngine engine = tvg::CanvasEngine::All) noexcept; + + _TVG_DISABLE_CTOR(Initializer); +}; + + +/** + * @class Animation + * + * @brief The Animation class enables manipulation of animatable images. + * + * This class supports the display and control of animation frames. + * + * @since 0.13 + */ + +class TVG_API Animation +{ +public: + ~Animation(); + + /** + * @brief Specifies the current frame in the animation. + * + * @param[in] no The index of the animation frame to be displayed. The index should be less than the totalFrame(). + * + * @retval Result::Success Successfully set the frame. + * @retval Result::InsufficientCondition if the given @p no is the same as the current frame value. + * @retval Result::NonSupport The current Picture data does not support animations. + * + * @note For efficiency, ThorVG ignores updates to the new frame value if the difference from the current frame value + * is less than 0.001. In such cases, it returns @c Result::InsufficientCondition. + * + * @see totalFrame() + * + */ + Result frame(float no) noexcept; + + /** + * @brief Retrieves a picture instance associated with this animation instance. + * + * This function provides access to the picture instance that can be used to load animation formats, such as Lottie(json). + * After setting up the picture, it can be pushed to the designated canvas, enabling control over animation frames + * with this Animation instance. + * + * @return A picture instance that is tied to this animation. + * + * @warning The picture instance is owned by Animation. It should not be deleted manually. + * + */ + Picture* picture() const noexcept; + + /** + * @brief Retrieves the current frame number of the animation. + * + * @return The current frame number of the animation, between 0 and totalFrame() - 1. + * + * @note If the Picture is not properly configured, this function will return 0. + * + * @see Animation::frame(float no) + * @see Animation::totalFrame() + * + */ + float curFrame() const noexcept; + + /** + * @brief Retrieves the total number of frames in the animation. + * + * @return The total number of frames in the animation. + * + * @note Frame numbering starts from 0. + * @note If the Picture is not properly configured, this function will return 0. + * + */ + float totalFrame() const noexcept; + + /** + * @brief Retrieves the duration of the animation in seconds. + * + * @return The duration of the animation in seconds. + * + * @note If the Picture is not properly configured, this function will return 0. + * + */ + float duration() const noexcept; + + /** + * @brief Specifies the playback segment of the animation. + * + * The set segment is designated as the play area of the animation. + * This is useful for playing a specific segment within the entire animation. + * After setting, the number of animation frames and the playback time are calculated + * by mapping the playback segment as the entire range. + * + * @param[in] begin segment start. + * @param[in] end segment end. + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition In case the animation is not loaded. + * @retval Result::InvalidArguments When the given parameter is invalid. + * @retval Result::NonSupport When it's not animatable. + * + * @note Range from 0.0~1.0 + * @note If a marker has been specified, its range will be disregarded. + * @see LottieAnimation::segment(const char* marker) + * @note Experimental API + */ + Result segment(float begin, float end) noexcept; + + /** + * @brief Gets the current segment. + * + * @param[out] begin segment start. + * @param[out] end segment end. + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition In case the animation is not loaded. + * @retval Result::InvalidArguments When the given parameter is invalid. + * @retval Result::NonSupport When it's not animatable. + * + * @note Experimental API + */ + Result segment(float* begin, float* end = nullptr) noexcept; + + /** + * @brief Creates a new Animation object. + * + * @return A new Animation object. + * + */ + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(Animation); +}; + + +/** + * @class Saver + * + * @brief A class for exporting a paint object into a specified file, from which to recover the paint data later. + * + * ThorVG provides a feature for exporting & importing paint data. The Saver role is to export the paint data to a file. + * It's useful when you need to save the composed scene or image from a paint object and recreate it later. + * + * The file format is decided by the extension name(i.e. "*.tvg") while the supported formats depend on the TVG packaging environment. + * If it doesn't support the file format, the save() method returns the @c Result::NonSuppport result. + * + * Once you export a paint to the file successfully, you can recreate it using the Picture class. + * + * @see Picture::load() + * + * @since 0.5 + */ +class TVG_API Saver final +{ +public: + ~Saver(); + + /** + * @brief Sets the base background content for the saved image. + * + * @param[in] paint The paint to be drawn as the background image for the saving paint. + * + * @note Experimental API + */ + Result background(std::unique_ptr paint) noexcept; + + /** + * @brief Exports the given @p paint data to the given @p path + * + * If the saver module supports any compression mechanism, it will optimize the data size. + * This might affect the encoding/decoding time in some cases. You can turn off the compression + * if you wish to optimize for speed. + * + * @param[in] paint The paint to be saved with all its associated properties. + * @param[in] path A path to the file, in which the paint data is to be saved. + * @param[in] quality The encoded quality level. @c 0 is the minimum, @c 100 is the maximum value(recommended). + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition If currently saving other resources. + * @retval Result::NonSupport When trying to save a file with an unknown extension or in an unsupported format. + * @retval Result::MemoryCorruption An internal error. + * @retval Result::Unknown In case an empty paint is to be saved. + * + * @note Saving can be asynchronous if the assigned thread number is greater than zero. To guarantee the saving is done, call sync() afterwards. + * @see Saver::sync() + * + * @since 0.5 + */ + Result save(std::unique_ptr paint, const std::string& path, uint32_t quality = 100) noexcept; + + /** + * @brief Export the provided animation data to the specified file path. + * + * This function exports the given animation data to the provided file path. You can also specify the desired frame rate in frames per second (FPS) by providing the fps parameter. + * + * @param[in] animation The animation to be saved, including all associated properties. + * @param[in] path The path to the file where the animation will be saved. + * @param[in] quality The encoded quality level. @c 0 is the minimum, @c 100 is the maximum value(recommended). + * @param[in] fps The desired frames per second (FPS). For example, to encode data at 60 FPS, pass 60. Pass 0 to keep the original frame data. + * + * @retval Result::Success if the export succeeds. + * @retval Result::InsufficientCondition if there are ongoing resource-saving operations. + * @retval Result::NonSupport if an attempt is made to save the file with an unknown extension or in an unsupported format. + * @retval Result::MemoryCorruption in case of an internal error. + * @retval Result::Unknown if attempting to save an empty paint. + * + * @note A higher frames per second (FPS) would result in a larger file size. It is recommended to use the default value. + * @note Saving can be asynchronous if the assigned thread number is greater than zero. To guarantee the saving is done, call sync() afterwards. + * + * @see Saver::sync() + * + * @note Experimental API + */ + Result save(std::unique_ptr animation, const std::string& path, uint32_t quality = 100, uint32_t fps = 0) noexcept; + + /** + * @brief Guarantees that the saving task is finished. + * + * The behavior of the Saver works on a sync/async basis, depending on the threading setting of the Initializer. + * Thus, if you wish to have a benefit of it, you must call sync() after the save() in the proper delayed time. + * Otherwise, you can call sync() immediately. + * + * @retval Result::Success when succeed. + * @retval Result::InsufficientCondition otherwise. + * + * @note The asynchronous tasking is dependent on the Saver module implementation. + * @see Saver::save() + * + * @since 0.5 + */ + Result sync() noexcept; + + /** + * @brief Creates a new Saver object. + * + * @return A new Saver object. + * + * @since 0.5 + */ + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(Saver); +}; + + +/** + * @class Accessor + * + * @brief The Accessor is a utility class to debug the Scene structure by traversing the scene-tree. + * + * The Accessor helps you search specific nodes to read the property information, figure out the structure of the scene tree and its size. + * + * @warning We strongly warn you not to change the paints of a scene unless you really know the design-structure. + * + * @since 0.10 + */ +class TVG_API Accessor final +{ +public: + ~Accessor(); + + /** + * @brief Set the access function for traversing the Picture scene tree nodes. + * + * @param[in] picture The picture node to traverse the internal scene-tree. + * @param[in] func The callback function calling for every paint nodes of the Picture. + * + * @return Return the given @p picture instance. + * + * @note The bitmap based picture might not have the scene-tree. + */ + std::unique_ptr set(std::unique_ptr picture, std::function func) noexcept; + + /** + * @brief Creates a new Accessor object. + * + * @return A new Accessor object. + */ + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(Accessor); +}; + + +/** + * @brief The cast() function is a utility function used to cast a 'Paint' to type 'T'. + * @since 0.11 + */ +template +std::unique_ptr cast(Paint* paint) +{ + return std::unique_ptr(static_cast(paint)); +} + + +/** + * @brief The cast() function is a utility function used to cast a 'Fill' to type 'T'. + * @since 0.11 + */ +template +std::unique_ptr cast(Fill* fill) +{ + return std::unique_ptr(static_cast(fill)); +} + + +/** @}*/ + +} //namespace + +#endif //_THORVG_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgArray.h b/Tests/LottieMetalTest/thorvg/Sources/common/tvgArray.h new file mode 100644 index 00000000000..8178bd0e420 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgArray.h @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_ARRAY_H_ +#define _TVG_ARRAY_H_ + +#include +#include +#include + +namespace tvg +{ + +template +struct Array +{ + T* data = nullptr; + uint32_t count = 0; + uint32_t reserved = 0; + + Array(){} + + Array(int32_t size) + { + reserve(size); + } + + Array(const Array& rhs) + { + reset(); + *this = rhs; + } + + void push(T element) + { + if (count + 1 > reserved) { + reserved = count + (count + 2) / 2; + data = static_cast(realloc(data, sizeof(T) * reserved)); + } + data[count++] = element; + } + + void push(Array& rhs) + { + if (rhs.count == 0) return; + grow(rhs.count); + memcpy(data + count, rhs.data, rhs.count * sizeof(T)); + count += rhs.count; + } + + bool reserve(uint32_t size) + { + if (size > reserved) { + reserved = size; + data = static_cast(realloc(data, sizeof(T) * reserved)); + } + return true; + } + + bool grow(uint32_t size) + { + return reserve(count + size); + } + + const T& operator[](size_t idx) const + { + return data[idx]; + } + + T& operator[](size_t idx) + { + return data[idx]; + } + + const T* begin() const + { + return data; + } + + T* begin() + { + return data; + } + + T* end() + { + return data + count; + } + + const T* end() const + { + return data + count; + } + + const T& last() const + { + return data[count - 1]; + } + + const T& first() const + { + return data[0]; + } + + T& last() + { + return data[count - 1]; + } + + T& first() + { + return data[0]; + } + + void pop() + { + if (count > 0) --count; + } + + void reset() + { + free(data); + data = nullptr; + count = reserved = 0; + } + + void clear() + { + count = 0; + } + + bool empty() const + { + return count == 0; + } + + template + void sort() + { + qsort(data, 0, static_cast(count) - 1); + } + + void operator=(const Array& rhs) + { + reserve(rhs.count); + if (rhs.count > 0) memcpy(data, rhs.data, sizeof(T) * rhs.count); + count = rhs.count; + } + + ~Array() + { + free(data); + } + +private: + template + void qsort(T* arr, int32_t low, int32_t high) + { + if (low < high) { + int32_t i = low; + int32_t j = high; + T tmp = arr[low]; + while (i < j) { + while (i < j && !COMPARE{}(arr[j], tmp)) --j; + if (i < j) { + arr[i] = arr[j]; + ++i; + } + while (i < j && COMPARE{}(arr[i], tmp)) ++i; + if (i < j) { + arr[j] = arr[i]; + --j; + } + } + arr[i] = tmp; + qsort(arr, low, i - 1); + qsort(arr, i + 1, high); + } + } +}; + +} + +#endif //_TVG_ARRAY_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgCompressor.cpp b/Tests/LottieMetalTest/thorvg/Sources/common/tvgCompressor.cpp new file mode 100644 index 00000000000..709cb22fdd4 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgCompressor.cpp @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * Lempel–Ziv–Welch (LZW) encoder/decoder by Guilherme R. Lampert(guilherme.ronaldo.lampert@gmail.com) + + * This is the compression scheme used by the GIF image format and the Unix 'compress' tool. + * Main differences from this implementation is that End Of Input (EOI) and Clear Codes (CC) + * are not stored in the output and the max code length in bits is 12, vs 16 in compress. + * + * EOI is simply detected by the end of the data stream, while CC happens if the + * dictionary gets filled. Data is written/read from bit streams, which handle + * byte-alignment for us in a transparent way. + + * The decoder relies on the hardcoded data layout produced by the encoder, since + * no additional reconstruction data is added to the output, so they must match. + * The nice thing about LZW is that we can reconstruct the dictionary directly from + * the stream of codes generated by the encoder, so this avoids storing additional + * headers in the bit stream. + + * The output code length is variable. It starts with the minimum number of bits + * required to store the base byte-sized dictionary and automatically increases + * as the dictionary gets larger (it starts at 9-bits and grows to 10-bits when + * code 512 is added, then 11-bits when 1024 is added, and so on). If the dictionary + * is filled (4096 items for a 12-bits dictionary), the whole thing is cleared and + * the process starts over. This is the main reason why the encoder and the decoder + * must match perfectly, since the lengths of the codes will not be specified with + * the data itself. + + * USEFUL LINKS: + * https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch + * http://rosettacode.org/wiki/LZW_compression + * http://www.cs.duke.edu/csed/curious/compression/lzw.html + * http://www.cs.cf.ac.uk/Dave/Multimedia/node214.html + * http://marknelson.us/1989/10/01/lzw-data-compression/ + */ +#include "config.h" + + + +#include +#include +#include "tvgCompressor.h" + +namespace tvg { + + +/************************************************************************/ +/* LZW Implementation */ +/************************************************************************/ + + +//LZW Dictionary helper: +constexpr int Nil = -1; +constexpr int MaxDictBits = 12; +constexpr int StartBits = 9; +constexpr int FirstCode = (1 << (StartBits - 1)); // 256 +constexpr int MaxDictEntries = (1 << MaxDictBits); // 4096 + + +//Round up to the next power-of-two number, e.g. 37 => 64 +static int nextPowerOfTwo(int num) +{ + --num; + for (size_t i = 1; i < sizeof(num) * 8; i <<= 1) { + num = num | num >> i; + } + return ++num; +} + + +struct BitStreamWriter +{ + uint8_t* stream; //Growable buffer to store our bits. Heap allocated & owned by the class instance. + int bytesAllocated; //Current size of heap-allocated stream buffer *in bytes*. + int granularity; //Amount bytesAllocated multiplies by when auto-resizing in appendBit(). + int currBytePos; //Current byte being written to, from 0 to bytesAllocated-1. + int nextBitPos; //Bit position within the current byte to access next. 0 to 7. + int numBitsWritten; //Number of bits in use from the stream buffer, not including byte-rounding padding. + + void internalInit() + { + stream = nullptr; + bytesAllocated = 0; + granularity = 2; + currBytePos = 0; + nextBitPos = 0; + numBitsWritten = 0; + } + + uint8_t* allocBytes(const int bytesWanted, uint8_t * oldPtr, const int oldSize) + { + auto newMemory = static_cast(malloc(bytesWanted)); + memset(newMemory, 0, bytesWanted); + + if (oldPtr) { + memcpy(newMemory, oldPtr, oldSize); + free(oldPtr); + } + return newMemory; + } + + BitStreamWriter() + { + /* 8192 bits for a start (1024 bytes). It will resize if needed. + Default granularity is 2. */ + internalInit(); + allocate(8192); + } + + BitStreamWriter(const int initialSizeInBits, const int growthGranularity = 2) + { + internalInit(); + setGranularity(growthGranularity); + allocate(initialSizeInBits); + } + + ~BitStreamWriter() + { + free(stream); + } + + void allocate(int bitsWanted) + { + //Require at least a byte. + if (bitsWanted <= 0) bitsWanted = 8; + + //Round upwards if needed: + if ((bitsWanted % 8) != 0) bitsWanted = nextPowerOfTwo(bitsWanted); + + //We might already have the required count. + const int sizeInBytes = bitsWanted / 8; + if (sizeInBytes <= bytesAllocated) return; + + stream = allocBytes(sizeInBytes, stream, bytesAllocated); + bytesAllocated = sizeInBytes; + } + + void appendBit(const int bit) + { + const uint32_t mask = uint32_t(1) << nextBitPos; + stream[currBytePos] = (stream[currBytePos] & ~mask) | (-bit & mask); + ++numBitsWritten; + + if (++nextBitPos == 8) { + nextBitPos = 0; + if (++currBytePos == bytesAllocated) allocate(bytesAllocated * granularity * 8); + } + } + + void appendBitsU64(const uint64_t num, const int bitCount) + { + for (int b = 0; b < bitCount; ++b) { + const uint64_t mask = uint64_t(1) << b; + const int bit = !!(num & mask); + appendBit(bit); + } + } + + uint8_t* release() + { + auto oldPtr = stream; + internalInit(); + return oldPtr; + } + + void setGranularity(const int growthGranularity) + { + granularity = (growthGranularity >= 2) ? growthGranularity : 2; + } + + int getByteCount() const + { + int usedBytes = numBitsWritten / 8; + int leftovers = numBitsWritten % 8; + if (leftovers != 0) ++usedBytes; + return usedBytes; + } +}; + + +struct BitStreamReader +{ + const uint8_t* stream; // Pointer to the external bit stream. Not owned by the reader. + const int sizeInBytes; // Size of the stream *in bytes*. Might include padding. + const int sizeInBits; // Size of the stream *in bits*, padding *not* include. + int currBytePos = 0; // Current byte being read in the stream. + int nextBitPos = 0; // Bit position within the current byte to access next. 0 to 7. + int numBitsRead = 0; // Total bits read from the stream so far. Never includes byte-rounding padding. + + BitStreamReader(const uint8_t* bitStream, const int byteCount, const int bitCount) : stream(bitStream), sizeInBytes(byteCount), sizeInBits(bitCount) + { + } + + bool readNextBit(int& bitOut) + { + if (numBitsRead >= sizeInBits) return false; //We are done. + + const uint32_t mask = uint32_t(1) << nextBitPos; + bitOut = !!(stream[currBytePos] & mask); + ++numBitsRead; + + if (++nextBitPos == 8) { + nextBitPos = 0; + ++currBytePos; + } + return true; + } + + uint64_t readBitsU64(const int bitCount) + { + uint64_t num = 0; + for (int b = 0; b < bitCount; ++b) { + int bit; + if (!readNextBit(bit)) break; + /* Based on a "Stanford bit-hack": + http://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching */ + const uint64_t mask = uint64_t(1) << b; + num = (num & ~mask) | (-bit & mask); + } + return num; + } + + bool isEndOfStream() const + { + return numBitsRead >= sizeInBits; + } +}; + + +struct Dictionary +{ + struct Entry + { + int code; + int value; + }; + + //Dictionary entries 0-255 are always reserved to the byte/ASCII range. + int size; + Entry entries[MaxDictEntries]; + + Dictionary() + { + /* First 256 dictionary entries are reserved to the byte/ASCII range. + Additional entries follow for the character sequences found in the input. + Up to 4096 - 256 (MaxDictEntries - FirstCode). */ + size = FirstCode; + + for (int i = 0; i < size; ++i) { + entries[i].code = Nil; + entries[i].value = i; + } + } + + int findIndex(const int code, const int value) const + { + if (code == Nil) return value; + + //Linear search for now. + //TODO: Worth optimizing with a proper hash-table? + for (int i = 0; i < size; ++i) { + if (entries[i].code == code && entries[i].value == value) return i; + } + return Nil; + } + + bool add(const int code, const int value) + { + if (size == MaxDictEntries) return false; + entries[size].code = code; + entries[size].value = value; + ++size; + return true; + } + + bool flush(int & codeBitsWidth) + { + if (size == (1 << codeBitsWidth)) { + ++codeBitsWidth; + if (codeBitsWidth > MaxDictBits) { + //Clear the dictionary (except the first 256 byte entries). + codeBitsWidth = StartBits; + size = FirstCode; + return true; + } + } + return false; + } +}; + + +static bool outputByte(int code, uint8_t*& output, int outputSizeBytes, int& bytesDecodedSoFar) +{ + if (bytesDecodedSoFar >= outputSizeBytes) return false; + *output++ = static_cast(code); + ++bytesDecodedSoFar; + return true; +} + + +static bool outputSequence(const Dictionary& dict, int code, uint8_t*& output, int outputSizeBytes, int& bytesDecodedSoFar, int& firstByte) +{ + /* A sequence is stored backwards, so we have to write + it to a temp then output the buffer in reverse. */ + int i = 0; + uint8_t sequence[MaxDictEntries]; + + do { + sequence[i++] = dict.entries[code].value; + code = dict.entries[code].code; + } while (code >= 0); + + firstByte = sequence[--i]; + + for (; i >= 0; --i) { + if (!outputByte(sequence[i], output, outputSizeBytes, bytesDecodedSoFar)) return false; + } + return true; +} + + +uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes) +{ + int code = Nil; + int prevCode = Nil; + int firstByte = 0; + int bytesDecoded = 0; + int codeBitsWidth = StartBits; + auto uncompressed = (uint8_t*) malloc(sizeof(uint8_t) * uncompressedSizeBytes); + auto ptr = uncompressed; + + /* We'll reconstruct the dictionary based on the bit stream codes. + Unlike Huffman encoding, we don't store the dictionary as a prefix to the data. */ + Dictionary dictionary; + BitStreamReader bitStream(compressed, compressedSizeBytes, compressedSizeBits); + + /* We check to avoid an overflow of the user buffer. + If the buffer is smaller than the decompressed size, we break the loop and return the current decompression count. */ + while (!bitStream.isEndOfStream()) { + code = static_cast(bitStream.readBitsU64(codeBitsWidth)); + + if (prevCode == Nil) { + if (!outputByte(code, ptr, uncompressedSizeBytes, bytesDecoded)) break; + firstByte = code; + prevCode = code; + continue; + } + if (code >= dictionary.size) { + if (!outputSequence(dictionary, prevCode, ptr, uncompressedSizeBytes, bytesDecoded, firstByte)) break; + if (!outputByte(firstByte, ptr, uncompressedSizeBytes, bytesDecoded)) break; + } else if (!outputSequence(dictionary, code, ptr, uncompressedSizeBytes, bytesDecoded, firstByte)) break; + + dictionary.add(prevCode, firstByte); + if (dictionary.flush(codeBitsWidth)) prevCode = Nil; + else prevCode = code; + } + + return uncompressed; +} + + +uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits) +{ + //LZW encoding context: + int code = Nil; + int codeBitsWidth = StartBits; + Dictionary dictionary; + + //Output bit stream we write to. This will allocate memory as needed to accommodate the encoded data. + BitStreamWriter bitStream; + + for (; uncompressedSizeBytes > 0; --uncompressedSizeBytes, ++uncompressed) { + const int value = *uncompressed; + const int index = dictionary.findIndex(code, value); + + if (index != Nil) { + code = index; + continue; + } + + //Write the dictionary code using the minimum bit-with: + bitStream.appendBitsU64(code, codeBitsWidth); + + //Flush it when full so we can restart the sequences. + if (!dictionary.flush(codeBitsWidth)) { + //There's still space for this sequence. + dictionary.add(code, value); + } + code = value; + } + + //Residual code at the end: + if (code != Nil) bitStream.appendBitsU64(code, codeBitsWidth); + + //Pass ownership of the compressed data buffer to the user pointer: + *compressedSizeBytes = bitStream.getByteCount(); + *compressedSizeBits = bitStream.numBitsWritten; + + return bitStream.release(); +} + + +/************************************************************************/ +/* B64 Implementation */ +/************************************************************************/ + + +size_t b64Decode(const char* encoded, const size_t len, char** decoded) +{ + static constexpr const char B64_INDEX[256] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + + if (!decoded || !encoded || len == 0) return 0; + + auto reserved = 3 * (1 + (len >> 2)) + 1; + auto output = static_cast(malloc(reserved * sizeof(char))); + if (!output) return 0; + output[reserved - 1] = '\0'; + + size_t idx = 0; + + while (*encoded && *(encoded + 1)) { + if (*encoded <= 0x20) { + ++encoded; + continue; + } + + auto value1 = B64_INDEX[(size_t)encoded[0]]; + auto value2 = B64_INDEX[(size_t)encoded[1]]; + output[idx++] = (value1 << 2) + ((value2 & 0x30) >> 4); + + if (!encoded[2] || encoded[3] < 0 || encoded[2] == '=' || encoded[2] == '.') break; + auto value3 = B64_INDEX[(size_t)encoded[2]]; + output[idx++] = ((value2 & 0x0f) << 4) + ((value3 & 0x3c) >> 2); + + if (!encoded[3] || encoded[3] < 0 || encoded[3] == '=' || encoded[3] == '.') break; + auto value4 = B64_INDEX[(size_t)encoded[3]]; + output[idx++] = ((value3 & 0x03) << 6) + value4; + encoded += 4; + } + *decoded = output; + return reserved; +} + + +} \ No newline at end of file diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgCompressor.h b/Tests/LottieMetalTest/thorvg/Sources/common/tvgCompressor.h new file mode 100644 index 00000000000..0756127ec61 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgCompressor.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_COMPRESSOR_H_ +#define _TVG_COMPRESSOR_H_ + +#include + +namespace tvg +{ + uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits); + uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes); + size_t b64Decode(const char* encoded, const size_t len, char** decoded); +} + +#endif //_TVG_COMPRESSOR_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgFormat.h b/Tests/LottieMetalTest/thorvg/Sources/common/tvgFormat.h new file mode 100644 index 00000000000..2159de52add --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgFormat.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_FORMAT_H_ +#define _TVG_FORMAT_H_ + +/* TODO: Need to consider whether uin8_t is enough size for extension... + Rather than optimal data, we can use enough size and data compress? */ + +using TvgBinByte = uint8_t; +using TvgBinCounter = uint32_t; +using TvgBinTag = TvgBinByte; +using TvgBinFlag = TvgBinByte; + + +//Header +#define TVG_HEADER_SIZE 33 //TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + 2*SIZE(float) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE +#define TVG_HEADER_SIGNATURE "ThorVG" +#define TVG_HEADER_SIGNATURE_LENGTH 6 +#define TVG_HEADER_VERSION "010000" //Major 01, Minor 00, Micro 00 +#define TVG_HEADER_VERSION_LENGTH 6 +#define TVG_HEADER_RESERVED_LENGTH 1 //Storing flags for extensions +#define TVG_HEADER_COMPRESS_SIZE 12 //TVG_HEADER_UNCOMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE_BITS +//Compress Size +#define TVG_HEADER_UNCOMPRESSED_SIZE 4 //SIZE (TvgBinCounter) +#define TVG_HEADER_COMPRESSED_SIZE 4 //SIZE (TvgBinCounter) +#define TVG_HEADER_COMPRESSED_SIZE_BITS 4 //SIZE (TvgBinCounter) +//Reserved Flag +#define TVG_HEAD_FLAG_COMPRESSED 0x01 + +//Paint Type +#define TVG_TAG_CLASS_PICTURE (TvgBinTag)0xfc +#define TVG_TAG_CLASS_SHAPE (TvgBinTag)0xfd +#define TVG_TAG_CLASS_SCENE (TvgBinTag)0xfe + + +//Paint +#define TVG_TAG_PAINT_OPACITY (TvgBinTag)0x10 +#define TVG_TAG_PAINT_TRANSFORM (TvgBinTag)0x11 +#define TVG_TAG_PAINT_CMP_TARGET (TvgBinTag)0x01 +#define TVG_TAG_PAINT_CMP_METHOD (TvgBinTag)0x20 + + +//Shape +#define TVG_TAG_SHAPE_PATH (TvgBinTag)0x40 +#define TVG_TAG_SHAPE_STROKE (TvgBinTag)0x41 +#define TVG_TAG_SHAPE_FILL (TvgBinTag)0x42 +#define TVG_TAG_SHAPE_COLOR (TvgBinTag)0x43 +#define TVG_TAG_SHAPE_FILLRULE (TvgBinTag)0x44 + + +//Stroke +#define TVG_TAG_SHAPE_STROKE_CAP (TvgBinTag)0x50 +#define TVG_TAG_SHAPE_STROKE_JOIN (TvgBinTag)0x51 +#define TVG_TAG_SHAPE_STROKE_WIDTH (TvgBinTag)0x52 +#define TVG_TAG_SHAPE_STROKE_COLOR (TvgBinTag)0x53 +#define TVG_TAG_SHAPE_STROKE_FILL (TvgBinTag)0x54 +#define TVG_TAG_SHAPE_STROKE_DASHPTRN (TvgBinTag)0x55 +#define TVG_TAG_SHAPE_STROKE_MITERLIMIT (TvgBinTag)0x56 +#define TVG_TAG_SHAPE_STROKE_ORDER (TvgBinTag)0x57 +#define TVG_TAG_SHAPE_STROKE_DASH_OFFSET (TvgBinTag)0x58 + + +//Fill +#define TVG_TAG_FILL_LINEAR_GRADIENT (TvgBinTag)0x60 +#define TVG_TAG_FILL_RADIAL_GRADIENT (TvgBinTag)0x61 +#define TVG_TAG_FILL_COLORSTOPS (TvgBinTag)0x62 +#define TVG_TAG_FILL_FILLSPREAD (TvgBinTag)0x63 +#define TVG_TAG_FILL_TRANSFORM (TvgBinTag)0x64 +#define TVG_TAG_FILL_RADIAL_GRADIENT_FOCAL (TvgBinTag)0x65 + +//Picture +#define TVG_TAG_PICTURE_RAW_IMAGE (TvgBinTag)0x70 +#define TVG_TAG_PICTURE_MESH (TvgBinTag)0x71 + +#endif //_TVG_FORMAT_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgInlist.h b/Tests/LottieMetalTest/thorvg/Sources/common/tvgInlist.h new file mode 100644 index 00000000000..ff28cfd48ea --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgInlist.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_INLIST_H_ +#define _TVG_INLIST_H_ + +namespace tvg { + +//NOTE: declare this in your list item +#define INLIST_ITEM(T) \ + T* prev; \ + T* next + +template +struct Inlist +{ + T* head = nullptr; + T* tail = nullptr; + + void free() + { + while (head) { + auto t = head; + head = t->next; + delete(t); + } + head = tail = nullptr; + } + + void back(T* element) + { + if (tail) { + tail->next = element; + element->prev = tail; + element->next = nullptr; + tail = element; + } else { + head = tail = element; + element->prev = nullptr; + element->next = nullptr; + } + } + + void front(T* element) + { + if (head) { + head->prev = element; + element->prev = nullptr; + element->next = head; + head = element; + } else { + head = tail = element; + element->prev = nullptr; + element->next = nullptr; + } + } + + T* back() + { + if (!tail) return nullptr; + auto t = tail; + tail = t->prev; + if (!tail) head = nullptr; + return t; + } + + T* front() + { + if (!head) return nullptr; + auto t = head; + head = t->next; + if (!head) tail = nullptr; + return t; + } + + void remove(T* element) + { + if (element->prev) element->prev->next = element->next; + if (element->next) element->next->prev = element->prev; + if (element == head) head = element->next; + if (element == tail) tail = element->prev; + } + + bool empty() + { + return head ? false : true; + } +}; + +} + +#endif // _TVG_INLIST_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgLines.cpp b/Tests/LottieMetalTest/thorvg/Sources/common/tvgLines.cpp new file mode 100644 index 00000000000..217b4917cb0 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgLines.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" +#include "tvgLines.h" + +#define BEZIER_EPSILON 1e-2f + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static float _lineLengthApprox(const Point& pt1, const Point& pt2) +{ + /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. + With alpha = 1, beta = 3/8, giving results with the largest error less + than 7% compared to the exact value. */ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + if (diff.x < 0) diff.x = -diff.x; + if (diff.y < 0) diff.y = -diff.y; + return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f); +} + + +static float _lineLength(const Point& pt1, const Point& pt2) +{ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + return sqrtf(diff.x * diff.x + diff.y * diff.y); +} + + +template +float _bezLength(const Bezier& cur, LengthFunc lineLengthFunc) +{ + Bezier left, right; + auto len = lineLengthFunc(cur.start, cur.ctrl1) + lineLengthFunc(cur.ctrl1, cur.ctrl2) + lineLengthFunc(cur.ctrl2, cur.end); + auto chord = lineLengthFunc(cur.start, cur.end); + + if (fabsf(len - chord) > BEZIER_EPSILON) { + tvg::bezSplit(cur, left, right); + return _bezLength(left, lineLengthFunc) + _bezLength(right, lineLengthFunc); + } + return len; +} + + +template +float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc) +{ + auto biggest = 1.0f; + auto smallest = 0.0f; + auto t = 0.5f; + + //just in case to prevent an infinite loop + if (at <= 0) return 0.0f; + if (at >= length) return 1.0f; + + while (true) { + auto right = bz; + Bezier left; + bezSplitLeft(right, t, left); + length = _bezLength(left, lineLengthFunc); + if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) { + break; + } + if (length < at) { + smallest = t; + t = (t + biggest) * 0.5f; + } else { + biggest = t; + t = (smallest + t) * 0.5f; + } + } + return t; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +namespace tvg +{ + +float lineLength(const Point& pt1, const Point& pt2) +{ + return _lineLength(pt1, pt2); +} + + +void lineSplitAt(const Line& cur, float at, Line& left, Line& right) +{ + auto len = lineLength(cur.pt1, cur.pt2); + auto dx = ((cur.pt2.x - cur.pt1.x) / len) * at; + auto dy = ((cur.pt2.y - cur.pt1.y) / len) * at; + left.pt1 = cur.pt1; + left.pt2.x = left.pt1.x + dx; + left.pt2.y = left.pt1.y + dy; + right.pt1 = left.pt2; + right.pt2 = cur.pt2; +} + + +void bezSplit(const Bezier& cur, Bezier& left, Bezier& right) +{ + auto c = (cur.ctrl1.x + cur.ctrl2.x) * 0.5f; + left.ctrl1.x = (cur.start.x + cur.ctrl1.x) * 0.5f; + right.ctrl2.x = (cur.ctrl2.x + cur.end.x) * 0.5f; + left.start.x = cur.start.x; + right.end.x = cur.end.x; + left.ctrl2.x = (left.ctrl1.x + c) * 0.5f; + right.ctrl1.x = (right.ctrl2.x + c) * 0.5f; + left.end.x = right.start.x = (left.ctrl2.x + right.ctrl1.x) * 0.5f; + + c = (cur.ctrl1.y + cur.ctrl2.y) * 0.5f; + left.ctrl1.y = (cur.start.y + cur.ctrl1.y) * 0.5f; + right.ctrl2.y = (cur.ctrl2.y + cur.end.y) * 0.5f; + left.start.y = cur.start.y; + right.end.y = cur.end.y; + left.ctrl2.y = (left.ctrl1.y + c) * 0.5f; + right.ctrl1.y = (right.ctrl2.y + c) * 0.5f; + left.end.y = right.start.y = (left.ctrl2.y + right.ctrl1.y) * 0.5f; +} + + +float bezLength(const Bezier& cur) +{ + return _bezLength(cur, _lineLength); +} + + +float bezLengthApprox(const Bezier& cur) +{ + return _bezLength(cur, _lineLengthApprox); +} + + +void bezSplitLeft(Bezier& cur, float at, Bezier& left) +{ + left.start = cur.start; + + left.ctrl1.x = cur.start.x + at * (cur.ctrl1.x - cur.start.x); + left.ctrl1.y = cur.start.y + at * (cur.ctrl1.y - cur.start.y); + + left.ctrl2.x = cur.ctrl1.x + at * (cur.ctrl2.x - cur.ctrl1.x); //temporary holding spot + left.ctrl2.y = cur.ctrl1.y + at * (cur.ctrl2.y - cur.ctrl1.y); //temporary holding spot + + cur.ctrl2.x = cur.ctrl2.x + at * (cur.end.x - cur.ctrl2.x); + cur.ctrl2.y = cur.ctrl2.y + at * (cur.end.y - cur.ctrl2.y); + + cur.ctrl1.x = left.ctrl2.x + at * (cur.ctrl2.x - left.ctrl2.x); + cur.ctrl1.y = left.ctrl2.y + at * (cur.ctrl2.y - left.ctrl2.y); + + left.ctrl2.x = left.ctrl1.x + at * (left.ctrl2.x - left.ctrl1.x); + left.ctrl2.y = left.ctrl1.y + at * (left.ctrl2.y - left.ctrl1.y); + + left.end.x = cur.start.x = left.ctrl2.x + at * (cur.ctrl1.x - left.ctrl2.x); + left.end.y = cur.start.y = left.ctrl2.y + at * (cur.ctrl1.y - left.ctrl2.y); +} + + +float bezAt(const Bezier& bz, float at, float length) +{ + return _bezAt(bz, at, length, _lineLength); +} + + +float bezAtApprox(const Bezier& bz, float at, float length) +{ + return _bezAt(bz, at, length, _lineLengthApprox); +} + + +void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right) +{ + right = cur; + auto t = bezAt(right, at, bezLength(right)); + bezSplitLeft(right, t, left); +} + + +Point bezPointAt(const Bezier& bz, float t) +{ + Point cur; + auto it = 1.0f - t; + + auto ax = bz.start.x * it + bz.ctrl1.x * t; + auto bx = bz.ctrl1.x * it + bz.ctrl2.x * t; + auto cx = bz.ctrl2.x * it + bz.end.x * t; + ax = ax * it + bx * t; + bx = bx * it + cx * t; + cur.x = ax * it + bx * t; + + float ay = bz.start.y * it + bz.ctrl1.y * t; + float by = bz.ctrl1.y * it + bz.ctrl2.y * t; + float cy = bz.ctrl2.y * it + bz.end.y * t; + ay = ay * it + by * t; + by = by * it + cy * t; + cur.y = ay * it + by * t; + + return cur; +} + + +float bezAngleAt(const Bezier& bz, float t) +{ + if (t < 0 || t > 1) return 0; + + //derivate + // p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 * + // t^2) * p2 + t^2 * p3) + float mt = 1.0f - t; + float d = t * t; + float a = -mt * mt; + float b = 1 - 4 * t + 3 * d; + float c = 2 * t - 3 * d; + + Point pt ={a * bz.start.x + b * bz.ctrl1.x + c * bz.ctrl2.x + d * bz.end.x, a * bz.start.y + b * bz.ctrl1.y + c * bz.ctrl2.y + d * bz.end.y}; + pt.x *= 3; + pt.y *= 3; + + return mathRad2Deg(atan2(pt.x, pt.y)); +} + + +} \ No newline at end of file diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgLines.h b/Tests/LottieMetalTest/thorvg/Sources/common/tvgLines.h new file mode 100644 index 00000000000..d900782b562 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgLines.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LINES_H_ +#define _TVG_LINES_H_ + +#include "tvgCommon.h" + +namespace tvg +{ + +struct Line +{ + Point pt1; + Point pt2; +}; + +float lineLength(const Point& pt1, const Point& pt2); +void lineSplitAt(const Line& cur, float at, Line& left, Line& right); + + +struct Bezier +{ + Point start; + Point ctrl1; + Point ctrl2; + Point end; +}; + +void bezSplit(const Bezier&cur, Bezier& left, Bezier& right); +float bezLength(const Bezier& cur); +void bezSplitLeft(Bezier& cur, float at, Bezier& left); +float bezAt(const Bezier& bz, float at, float length); +void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right); +Point bezPointAt(const Bezier& bz, float t); +float bezAngleAt(const Bezier& bz, float t); + +float bezLengthApprox(const Bezier& cur); +float bezAtApprox(const Bezier& bz, float at, float length); +} + +#endif //_TVG_LINES_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgLock.h b/Tests/LottieMetalTest/thorvg/Sources/common/tvgLock.h new file mode 100644 index 00000000000..5dd3d5a6248 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgLock.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOCK_H_ +#define _TVG_LOCK_H_ + +#ifdef THORVG_THREAD_SUPPORT + +#include + +namespace tvg { + + struct Key + { + std::mutex mtx; + }; + + struct ScopedLock + { + Key* key = nullptr; + + ScopedLock(Key& k) + { + k.mtx.lock(); + key = &k; + } + + ~ScopedLock() + { + key->mtx.unlock(); + } + }; + +} + +#else //THORVG_THREAD_SUPPORT + +namespace tvg { + + struct Key {}; + + struct ScopedLock + { + ScopedLock(Key& key) {} + }; + +} + +#endif //THORVG_THREAD_SUPPORT + +#endif //_TVG_LOCK_H_ + diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgMath.cpp b/Tests/LottieMetalTest/thorvg/Sources/common/tvgMath.cpp new file mode 100644 index 00000000000..37a8879cb56 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgMath.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" + + +bool mathInverse(const Matrix* m, Matrix* out) +{ + auto det = m->e11 * (m->e22 * m->e33 - m->e32 * m->e23) - + m->e12 * (m->e21 * m->e33 - m->e23 * m->e31) + + m->e13 * (m->e21 * m->e32 - m->e22 * m->e31); + + if (mathZero(det)) return false; + + auto invDet = 1 / det; + + out->e11 = (m->e22 * m->e33 - m->e32 * m->e23) * invDet; + out->e12 = (m->e13 * m->e32 - m->e12 * m->e33) * invDet; + out->e13 = (m->e12 * m->e23 - m->e13 * m->e22) * invDet; + out->e21 = (m->e23 * m->e31 - m->e21 * m->e33) * invDet; + out->e22 = (m->e11 * m->e33 - m->e13 * m->e31) * invDet; + out->e23 = (m->e21 * m->e13 - m->e11 * m->e23) * invDet; + out->e31 = (m->e21 * m->e32 - m->e31 * m->e22) * invDet; + out->e32 = (m->e31 * m->e12 - m->e11 * m->e32) * invDet; + out->e33 = (m->e11 * m->e22 - m->e21 * m->e12) * invDet; + + return true; +} + + +Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs) +{ + Matrix m; + + m.e11 = lhs->e11 * rhs->e11 + lhs->e12 * rhs->e21 + lhs->e13 * rhs->e31; + m.e12 = lhs->e11 * rhs->e12 + lhs->e12 * rhs->e22 + lhs->e13 * rhs->e32; + m.e13 = lhs->e11 * rhs->e13 + lhs->e12 * rhs->e23 + lhs->e13 * rhs->e33; + + m.e21 = lhs->e21 * rhs->e11 + lhs->e22 * rhs->e21 + lhs->e23 * rhs->e31; + m.e22 = lhs->e21 * rhs->e12 + lhs->e22 * rhs->e22 + lhs->e23 * rhs->e32; + m.e23 = lhs->e21 * rhs->e13 + lhs->e22 * rhs->e23 + lhs->e23 * rhs->e33; + + m.e31 = lhs->e31 * rhs->e11 + lhs->e32 * rhs->e21 + lhs->e33 * rhs->e31; + m.e32 = lhs->e31 * rhs->e12 + lhs->e32 * rhs->e22 + lhs->e33 * rhs->e32; + m.e33 = lhs->e31 * rhs->e13 + lhs->e32 * rhs->e23 + lhs->e33 * rhs->e33; + + return m; +} + + +void mathRotate(Matrix* m, float degree) +{ + if (degree == 0.0f) return; + + auto radian = degree / 180.0f * MATH_PI; + auto cosVal = cosf(radian); + auto sinVal = sinf(radian); + + m->e12 = m->e11 * -sinVal; + m->e11 *= cosVal; + m->e21 = m->e22 * sinVal; + m->e22 *= cosVal; +} + + +bool mathIdentity(const Matrix* m) +{ + if (m->e11 != 1.0f || m->e12 != 0.0f || m->e13 != 0.0f || + m->e21 != 0.0f || m->e22 != 1.0f || m->e23 != 0.0f || + m->e31 != 0.0f || m->e32 != 0.0f || m->e33 != 1.0f) { + return false; + } + return true; +} + + +void mathMultiply(Point* pt, const Matrix* transform) +{ + auto tx = pt->x * transform->e11 + pt->y * transform->e12 + transform->e13; + auto ty = pt->x * transform->e21 + pt->y * transform->e22 + transform->e23; + pt->x = tx; + pt->y = ty; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgMath.h b/Tests/LottieMetalTest/thorvg/Sources/common/tvgMath.h new file mode 100644 index 00000000000..60ea1d21b04 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgMath.h @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_MATH_H_ +#define _TVG_MATH_H_ + + #define _USE_MATH_DEFINES + +#include +#include +#include "tvgCommon.h" + +#define MATH_PI 3.14159265358979323846f +#define MATH_PI2 1.57079632679489661923f +#define PATH_KAPPA 0.552284f + +#define mathMin(x, y) (((x) < (y)) ? (x) : (y)) +#define mathMax(x, y) (((x) > (y)) ? (x) : (y)) + + +bool mathInverse(const Matrix* m, Matrix* out); +Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs); +void mathRotate(Matrix* m, float degree); +bool mathIdentity(const Matrix* m); +void mathMultiply(Point* pt, const Matrix* transform); + + +static inline float mathDeg2Rad(float degree) +{ + return degree * (MATH_PI / 180.0f); +} + + +static inline float mathRad2Deg(float radian) +{ + return radian * (180.0f / MATH_PI); +} + + +static inline bool mathZero(float a) +{ + return (fabsf(a) < FLT_EPSILON) ? true : false; +} + + +static inline bool mathEqual(float a, float b) +{ + return (fabsf(a - b) < FLT_EPSILON); +} + + +static inline bool mathEqual(const Matrix& a, const Matrix& b) +{ + if (!mathEqual(a.e11, b.e11) || !mathEqual(a.e12, b.e12) || !mathEqual(a.e13, b.e13) || + !mathEqual(a.e21, b.e21) || !mathEqual(a.e22, b.e22) || !mathEqual(a.e23, b.e23) || + !mathEqual(a.e31, b.e31) || !mathEqual(a.e32, b.e32) || !mathEqual(a.e33, b.e33)) { + return false; + } + return true; +} + + +static inline bool mathRightAngle(const Matrix* m) +{ + auto radian = fabsf(atan2f(m->e21, m->e11)); + if (radian < FLT_EPSILON || mathEqual(radian, MATH_PI2) || mathEqual(radian, MATH_PI)) return true; + return false; +} + + +static inline bool mathSkewed(const Matrix* m) +{ + return (fabsf(m->e21 + m->e12) > FLT_EPSILON); +} + + +static inline void mathIdentity(Matrix* m) +{ + m->e11 = 1.0f; + m->e12 = 0.0f; + m->e13 = 0.0f; + m->e21 = 0.0f; + m->e22 = 1.0f; + m->e23 = 0.0f; + m->e31 = 0.0f; + m->e32 = 0.0f; + m->e33 = 1.0f; +} + + +static inline void mathTransform(Matrix* transform, Point* coord) +{ + auto x = coord->x; + auto y = coord->y; + coord->x = x * transform->e11 + y * transform->e12 + transform->e13; + coord->y = x * transform->e21 + y * transform->e22 + transform->e23; +} + + +static inline void mathScale(Matrix* m, float sx, float sy) +{ + m->e11 *= sx; + m->e22 *= sy; +} + + +static inline void mathScaleR(Matrix* m, float x, float y) +{ + if (x != 1.0f) { + m->e11 *= x; + m->e21 *= x; + } + if (y != 1.0f) { + m->e22 *= y; + m->e12 *= y; + } +} + + +static inline void mathTranslate(Matrix* m, float x, float y) +{ + m->e13 += x; + m->e23 += y; +} + + +static inline void mathTranslateR(Matrix* m, float x, float y) +{ + if (x == 0.0f && y == 0.0f) return; + m->e13 += (x * m->e11 + y * m->e12); + m->e23 += (x * m->e21 + y * m->e22); +} + + +static inline void mathLog(Matrix* m) +{ + TVGLOG("MATH", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m->e11, m->e12, m->e13, m->e21, m->e22, m->e23, m->e31, m->e32, m->e33); +} + + +static inline float mathLength(const Point* a, const Point* b) +{ + auto x = b->x - a->x; + auto y = b->y - a->y; + + if (x < 0) x = -x; + if (y < 0) y = -y; + + return (x > y) ? (x + 0.375f * y) : (y + 0.375f * x); +} + + +static inline Point operator-(const Point& lhs, const Point& rhs) +{ + return {lhs.x - rhs.x, lhs.y - rhs.y}; +} + + +static inline Point operator+(const Point& lhs, const Point& rhs) +{ + return {lhs.x + rhs.x, lhs.y + rhs.y}; +} + + +static inline Point operator*(const Point& lhs, float rhs) +{ + return {lhs.x * rhs, lhs.y * rhs}; +} + + +static inline Point operator*(const float& lhs, const Point& rhs) +{ + return {lhs * rhs.x, lhs * rhs.y}; +} + + +static inline Point operator/(const Point& lhs, const float rhs) +{ + return {lhs.x / rhs, lhs.y / rhs}; +} + + +template +static inline T mathLerp(const T &start, const T &end, float t) +{ + return static_cast(start + (end - start) * t); +} + + +#endif //_TVG_MATH_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgStr.cpp b/Tests/LottieMetalTest/thorvg/Sources/common/tvgStr.cpp new file mode 100644 index 00000000000..1336f2447ce --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgStr.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" +#include +#include +#include +#include "tvgMath.h" +#include "tvgStr.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static inline bool _floatExact(float a, float b) +{ + return memcmp(&a, &b, sizeof(float)) == 0; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +namespace tvg { + +/* + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtof-l-wcstof-wcstof-l?view=msvc-160 + * + * src should be one of the following form : + * + * [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits] + * [whitespace] [sign] {INF | INFINITY} + * [whitespace] [sign] NAN [sequence] + * + * No hexadecimal form supported + * no sequence supported after NAN + */ +float strToFloat(const char *nPtr, char **endPtr) +{ + if (endPtr) *endPtr = (char *) (nPtr); + if (!nPtr) return 0.0f; + + auto a = nPtr; + auto iter = nPtr; + auto val = 0.0f; + unsigned long long integerPart = 0; + int minus = 1; + + //ignore leading whitespaces + while (isspace(*iter)) iter++; + + //signed or not + if (*iter == '-') { + minus = -1; + iter++; + } else if (*iter == '+') { + iter++; + } + + if (tolower(*iter) == 'i') { + if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'f')) iter += 3; + else goto error; + + if (tolower(*(iter)) == 'i') { + if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'i') && (tolower(*(iter + 3)) == 't') && + (tolower(*(iter + 4)) == 'y')) + iter += 5; + else goto error; + } + if (endPtr) *endPtr = (char *) (iter); + return (minus == -1) ? -INFINITY : INFINITY; + } + + if (tolower(*iter) == 'n') { + if ((tolower(*(iter + 1)) == 'a') && (tolower(*(iter + 2)) == 'n')) iter += 3; + else goto error; + + if (endPtr) *endPtr = (char *) (iter); + return (minus == -1) ? -NAN : NAN; + } + + //Optional: integer part before dot + if (isdigit(*iter)) { + for (; isdigit(*iter); iter++) { + integerPart = integerPart * 10ULL + (unsigned long long) (*iter - '0'); + } + a = iter; + } else if (*iter != '.') { + goto success; + } + + val = static_cast(integerPart); + + //Optional: decimal part after dot + if (*iter == '.') { + unsigned long long decimalPart = 0; + unsigned long long pow10 = 1; + int count = 0; + + iter++; + + if (isdigit(*iter)) { + for (; isdigit(*iter); iter++, count++) { + if (count < 19) { + decimalPart = decimalPart * 10ULL + +static_cast(*iter - '0'); + pow10 *= 10ULL; + } + } + } else if (isspace(*iter)) { //skip if there is a space after the dot. + a = iter; + goto success; + } + + val += static_cast(decimalPart) / static_cast(pow10); + a = iter; + } + + //Optional: exponent + if (*iter == 'e' || *iter == 'E') { + ++iter; + + //Exception: svg may have 'em' unit for fonts. ex) 5em, 10.5em + if ((*iter == 'm') || (*iter == 'M')) { + //TODO: We don't support font em unit now, but has to multiply val * font size later... + a = iter + 1; + goto success; + } + + //signed or not + int minus_e = 1; + + if (*iter == '-') { + minus_e = -1; + ++iter; + } else if (*iter == '+') { + iter++; + } + + unsigned int exponentPart = 0; + + if (isdigit(*iter)) { + while (*iter == '0') iter++; + for (; isdigit(*iter); iter++) { + exponentPart = exponentPart * 10U + static_cast(*iter - '0'); + } + } else if (!isdigit(*(a - 1))) { + a = nPtr; + goto success; + } else if (*iter == 0) { + goto success; + } + + //if ((_floatExact(val, 2.2250738585072011f)) && ((minus_e * static_cast(exponentPart)) <= -308)) { + if ((_floatExact(val, 1.175494351f)) && ((minus_e * static_cast(exponentPart)) <= -38)) { + //val *= 1.0e-308f; + val *= 1.0e-38f; + a = iter; + goto success; + } + + a = iter; + auto scale = 1.0f; + + while (exponentPart >= 8U) { + scale *= 1E8; + exponentPart -= 8U; + } + while (exponentPart > 0U) { + scale *= 10.0f; + exponentPart--; + } + val = (minus_e == -1) ? (val / scale) : (val * scale); + } else if ((iter > nPtr) && !isdigit(*(iter - 1))) { + a = nPtr; + goto success; + } + +success: + if (endPtr) *endPtr = (char *)(a); + if (!std::isfinite(val)) return 0.0f; + + return minus * val; + +error: + if (endPtr) *endPtr = (char *)(nPtr); + return 0.0f; +} + + +int str2int(const char* str, size_t n) +{ + int ret = 0; + for(size_t i = 0; i < n; ++i) { + ret = ret * 10 + (str[i] - '0'); + } + return ret; +} + +char* strDuplicate(const char *str, size_t n) +{ + auto len = strlen(str); + if (len < n) n = len; + + auto ret = (char *) malloc(n + 1); + if (!ret) return nullptr; + ret[n] = '\0'; + + return (char *) memcpy(ret, str, n); +} + +char* strDirname(const char* path) +{ + const char *ptr = strrchr(path, '/'); +#ifdef _WIN32 + if (ptr) ptr = strrchr(ptr + 1, '\\'); +#endif + int len = int(ptr + 1 - path); // +1 to include '/' + return strDuplicate(path, len); +} + +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/common/tvgStr.h b/Tests/LottieMetalTest/thorvg/Sources/common/tvgStr.h new file mode 100644 index 00000000000..9e5f9ba9b57 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/common/tvgStr.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_STR_H_ +#define _TVG_STR_H_ + +#include + +namespace tvg +{ + +float strToFloat(const char *nPtr, char **endPtr); //convert to float +int str2int(const char* str, size_t n); //convert to integer +char* strDuplicate(const char *str, size_t n); //copy the string +char* strDirname(const char* path); //return the full directory name + +} +#endif //_TVG_STR_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/config.h b/Tests/LottieMetalTest/thorvg/Sources/config.h new file mode 100644 index 00000000000..4946b8b11a4 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/config.h @@ -0,0 +1,8 @@ +#ifndef config_h +#define config_h + +#define THORVG_VERSION_STRING "1.0.0" +#define THORVG_SW_RASTER_SUPPORT true +#define THORVG_NEON_VECTOR_SUPPORT true + +#endif /* config_h */ diff --git a/Tests/LottieMetalTest/thorvg/Sources/loaders/raw/tvgRawLoader.cpp b/Tests/LottieMetalTest/thorvg/Sources/loaders/raw/tvgRawLoader.cpp new file mode 100644 index 00000000000..77f7d901490 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/loaders/raw/tvgRawLoader.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "tvgLoader.h" +#include "tvgRawLoader.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +RawLoader::RawLoader() : ImageLoader(FileType::Raw) +{ +} + + +RawLoader::~RawLoader() +{ + if (copy) free(surface.buf32); +} + + +bool RawLoader::open(const uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy) +{ + if (!LoadModule::read()) return true; + + if (!data || w == 0 || h == 0) return false; + + this->w = (float)w; + this->h = (float)h; + this->copy = copy; + + if (copy) { + surface.buf32 = (uint32_t*)malloc(sizeof(uint32_t) * w * h); + if (!surface.buf32) return false; + memcpy((void*)surface.buf32, data, sizeof(uint32_t) * w * h); + } + else surface.buf32 = const_cast(data); + + //setup the surface + surface.stride = w; + surface.w = w; + surface.h = h; + surface.cs = ColorSpace::ARGB8888; + surface.channelSize = sizeof(uint32_t); + surface.premultiplied = premultiplied; + + return true; +} + + +bool RawLoader::read() +{ + LoadModule::read(); + + return true; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/loaders/raw/tvgRawLoader.h b/Tests/LottieMetalTest/thorvg/Sources/loaders/raw/tvgRawLoader.h new file mode 100644 index 00000000000..4d51aefbd2e --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/loaders/raw/tvgRawLoader.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_RAW_LOADER_H_ +#define _TVG_RAW_LOADER_H_ + +class RawLoader : public ImageLoader +{ +public: + bool copy = false; + + RawLoader(); + ~RawLoader(); + + using LoadModule::open; + bool open(const uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy); + bool read() override; +}; + + +#endif //_TVG_RAW_LOADER_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgBinInterpreter.cpp b/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgBinInterpreter.cpp new file mode 100644 index 00000000000..4028c50b5ea --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgBinInterpreter.cpp @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include + +#ifdef _WIN32 + #include +#elif defined(__linux__) + #include +#else + #include +#endif + +#include "tvgTvgCommon.h" +#include "tvgShape.h" +#include "tvgFill.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct TvgBinBlock +{ + TvgBinTag type; + TvgBinCounter length; + const char* data; + const char* end; +}; + +static Paint* _parsePaint(TvgBinBlock baseBlock); + + +static TvgBinBlock _readBlock(const char *ptr) +{ + TvgBinBlock block; + block.type = *ptr; + READ_UI32(&block.length, ptr + SIZE(TvgBinTag)); + block.data = ptr + SIZE(TvgBinTag) + SIZE(TvgBinCounter); + block.end = block.data + block.length; + return block; +} + + +static bool _parseCmpTarget(const char *ptr, const char *end, Paint *paint) +{ + auto block = _readBlock(ptr); + if (block.end > end) return false; + + if (block.type != TVG_TAG_PAINT_CMP_METHOD) return false; + if (block.length != SIZE(TvgBinFlag)) return false; + + auto cmpMethod = static_cast(*block.data); + + ptr = block.end; + + auto cmpBlock = _readBlock(ptr); + if (cmpBlock.end > end) return false; + + paint->composite(unique_ptr(_parsePaint(cmpBlock)), cmpMethod); + + return true; +} + + +static bool _parsePaintProperty(TvgBinBlock block, Paint *paint) +{ + switch (block.type) { + case TVG_TAG_PAINT_OPACITY: { + if (block.length != SIZE(uint8_t)) return false; + paint->opacity(*block.data); + return true; + } + case TVG_TAG_PAINT_TRANSFORM: { + if (block.length != SIZE(Matrix)) return false; + Matrix matrix; + memcpy(&matrix, block.data, SIZE(Matrix)); + paint->transform(matrix); + return true; + } + case TVG_TAG_PAINT_CMP_TARGET: { + if (block.length < SIZE(TvgBinTag) + SIZE(TvgBinCounter)) return false; + return _parseCmpTarget(block.data, block.end, paint); + } + } + return false; +} + + +static bool _parseScene(TvgBinBlock block, Paint *paint) +{ + auto scene = static_cast(paint); + + //Case2: Base Paint Properties + if (_parsePaintProperty(block, scene)) return true; + + //Case3: A Child paint + if (auto paint = _parsePaint(block)) { + scene->push(unique_ptr(paint)); + return true; + } + + return false; +} + + +static bool _parseShapePath(const char *ptr, const char *end, Shape *shape) +{ + uint32_t cmdCnt, ptsCnt; + + READ_UI32(&cmdCnt, ptr); + ptr += SIZE(cmdCnt); + + READ_UI32(&ptsCnt, ptr); + ptr += SIZE(ptsCnt); + + auto cmds = (TvgBinFlag*) ptr; + ptr += SIZE(TvgBinFlag) * cmdCnt; + + auto pts = (Point*) ptr; + ptr += SIZE(Point) * ptsCnt; + + if (ptr > end) return false; + + /* Recover to PathCommand(4 bytes) from TvgBinFlag(1 byte) */ + PathCommand* inCmds = (PathCommand*)alloca(sizeof(PathCommand) * cmdCnt); + for (uint32_t i = 0; i < cmdCnt; ++i) { + inCmds[i] = static_cast(cmds[i]); + } + + shape->appendPath(inCmds, cmdCnt, pts, ptsCnt); + + return true; +} + + +static unique_ptr _parseShapeFill(const char *ptr, const char *end) +{ + unique_ptr fillGrad; + + while (ptr < end) { + auto block = _readBlock(ptr); + if (block.end > end) return nullptr; + + switch (block.type) { + case TVG_TAG_FILL_RADIAL_GRADIENT: { + if (block.length != 3 * SIZE(float)) return nullptr; + + auto ptr = block.data; + float x, y, radius; + + READ_FLOAT(&x, ptr); + ptr += SIZE(float); + READ_FLOAT(&y, ptr); + ptr += SIZE(float); + READ_FLOAT(&radius, ptr); + + auto fillGradRadial = RadialGradient::gen(); + fillGradRadial->radial(x, y, radius); + fillGrad = std::move(fillGradRadial); + break; + } + case TVG_TAG_FILL_RADIAL_GRADIENT_FOCAL: { + if (block.length != 3 * SIZE(float)) return nullptr; + + auto ptr = block.data; + float x, y, radius; + + READ_FLOAT(&x, ptr); + ptr += SIZE(float); + READ_FLOAT(&y, ptr); + ptr += SIZE(float); + READ_FLOAT(&radius, ptr); + + if (auto fillGradRadial = static_cast(fillGrad.get())) { + P(fillGradRadial)->fx = x; + P(fillGradRadial)->fy = y; + P(fillGradRadial)->fr = radius; + } + break; + } + case TVG_TAG_FILL_LINEAR_GRADIENT: { + if (block.length != 4 * SIZE(float)) return nullptr; + + auto ptr = block.data; + float x1, y1, x2, y2; + + READ_FLOAT(&x1, ptr); + ptr += SIZE(float); + READ_FLOAT(&y1, ptr); + ptr += SIZE(float); + READ_FLOAT(&x2, ptr); + ptr += SIZE(float); + READ_FLOAT(&y2, ptr); + + auto fillGradLinear = LinearGradient::gen(); + fillGradLinear->linear(x1, y1, x2, y2); + fillGrad = std::move(fillGradLinear); + break; + } + case TVG_TAG_FILL_FILLSPREAD: { + if (!fillGrad) return nullptr; + if (block.length != SIZE(TvgBinFlag)) return nullptr; + fillGrad->spread((FillSpread) *block.data); + break; + } + case TVG_TAG_FILL_COLORSTOPS: { + if (!fillGrad) return nullptr; + if (block.length == 0 || block.length & 0x07) return nullptr; + uint32_t stopsCnt = block.length >> 3; // 8 bytes per ColorStop + if (stopsCnt > 1023) return nullptr; + Fill::ColorStop* stops = (Fill::ColorStop*)alloca(sizeof(Fill::ColorStop) * stopsCnt); + auto p = block.data; + for (uint32_t i = 0; i < stopsCnt; i++, p += 8) { + READ_FLOAT(&stops[i].offset, p); + stops[i].r = p[4]; + stops[i].g = p[5]; + stops[i].b = p[6]; + stops[i].a = p[7]; + } + fillGrad->colorStops(stops, stopsCnt); + break; + } + case TVG_TAG_FILL_TRANSFORM: { + if (!fillGrad || block.length != SIZE(Matrix)) return nullptr; + Matrix gradTransform; + memcpy(&gradTransform, block.data, SIZE(Matrix)); + fillGrad->transform(gradTransform); + break; + } + default: { + TVGLOG("TVG", "Unsupported tag %d (0x%x) used as one of the fill properties, %d bytes skipped", block.type, block.type, block.length); + break; + } + } + ptr = block.end; + } + return fillGrad; +} + + +static bool _parseShapeStrokeDashPattern(const char *ptr, const char *end, Shape *shape) +{ + uint32_t dashPatternCnt; + READ_UI32(&dashPatternCnt, ptr); + ptr += SIZE(uint32_t); + if (dashPatternCnt > 0) { + float* dashPattern = static_cast(malloc(sizeof(float) * dashPatternCnt)); + if (!dashPattern) return false; + memcpy(dashPattern, ptr, sizeof(float) * dashPatternCnt); + ptr += SIZE(float) * dashPatternCnt; + + if (ptr > end) { + free(dashPattern); + return false; + } + + shape->strokeDash(dashPattern, dashPatternCnt); + free(dashPattern); + } + return true; +} + + +static bool _parseShapeStroke(const char *ptr, const char *end, Shape *shape) +{ + while (ptr < end) { + auto block = _readBlock(ptr); + if (block.end > end) return false; + + switch (block.type) { + case TVG_TAG_SHAPE_STROKE_CAP: { + if (block.length != SIZE(TvgBinFlag)) return false; + shape->strokeCap((StrokeCap) *block.data); + break; + } + case TVG_TAG_SHAPE_STROKE_JOIN: { + if (block.length != SIZE(TvgBinFlag)) return false; + shape->strokeJoin((StrokeJoin) *block.data); + break; + } + case TVG_TAG_SHAPE_STROKE_ORDER: { + if (block.length != SIZE(TvgBinFlag)) return false; + P(shape)->strokeFirst((bool) *block.data); + break; + } + case TVG_TAG_SHAPE_STROKE_WIDTH: { + if (block.length != SIZE(float)) return false; + float width; + READ_FLOAT(&width, block.data); + shape->strokeWidth(width); + break; + } + case TVG_TAG_SHAPE_STROKE_COLOR: { + if (block.length != 4) return false; + shape->strokeFill(block.data[0], block.data[1], block.data[2], block.data[3]); + break; + } + case TVG_TAG_SHAPE_STROKE_FILL: { + auto fill = _parseShapeFill(block.data, block.end); + if (!fill) return false; + shape->strokeFill(std::move(fill)); + break; + } + case TVG_TAG_SHAPE_STROKE_DASHPTRN: { + if (!_parseShapeStrokeDashPattern(block.data, block.end, shape)) return false; + break; + } + case TVG_TAG_SHAPE_STROKE_MITERLIMIT: { + if (block.length != SIZE(float)) return false; + float miterlimit; + READ_FLOAT(&miterlimit, block.data); + shape->strokeMiterlimit(miterlimit); + break; + } + case TVG_TAG_SHAPE_STROKE_DASH_OFFSET: { + if (block.length != SIZE(float)) return false; + float offset; + READ_FLOAT(&offset, block.data); + P(shape)->rs.stroke->dashOffset = offset; + break; + } + default: { + TVGLOG("TVG", "Unsupported tag %d (0x%x) used as one of stroke properties, %d bytes skipped", block.type, block.type, block.length); + break; + } + } + ptr = block.end; + } + return true; +} + + +static bool _parseShape(TvgBinBlock block, Paint* paint) +{ + auto shape = static_cast(paint); + + //Case1: Shape specific properties + switch (block.type) { + case TVG_TAG_SHAPE_PATH: { + return _parseShapePath(block.data, block.end, shape); + } + case TVG_TAG_SHAPE_STROKE: { + return _parseShapeStroke(block.data, block.end, shape); + } + case TVG_TAG_SHAPE_FILL: { + auto fill = _parseShapeFill(block.data, block.end); + if (!fill) return false; + shape->fill(std::move(fill)); + return true; + } + case TVG_TAG_SHAPE_COLOR: { + if (block.length != 4) return false; + shape->fill(block.data[0], block.data[1], block.data[2], block.data[3]); + return true; + } + case TVG_TAG_SHAPE_FILLRULE: { + if (block.length != SIZE(TvgBinFlag)) return false; + shape->fill((FillRule)*block.data); + return true; + } + } + + //Case2: Base Paint Properties + return _parsePaintProperty(block, shape); +} + + +static bool _parsePicture(TvgBinBlock block, Paint* paint) +{ + auto picture = static_cast(paint); + + switch (block.type) { + case TVG_TAG_PICTURE_RAW_IMAGE: { + if (block.length < 2 * SIZE(uint32_t)) return false; + + auto ptr = block.data; + uint32_t w, h; + + READ_UI32(&w, ptr); + ptr += SIZE(uint32_t); + READ_UI32(&h, ptr); + ptr += SIZE(uint32_t); + + auto size = w * h * SIZE(uint32_t); + if (block.length != 2 * SIZE(uint32_t) + size) return false; + + picture->load((uint32_t*) ptr, w, h, true, true); + + return true; + } + case TVG_TAG_PICTURE_MESH: { + if (block.length < 1 * SIZE(uint32_t)) return false; + + auto ptr = block.data; + uint32_t meshCnt; + READ_UI32(&meshCnt, ptr); + ptr += SIZE(uint32_t); + + auto size = meshCnt * SIZE(Polygon); + if (block.length != SIZE(uint32_t) + size) return false; + + picture->mesh((Polygon*) ptr, meshCnt); + + return true; + } + //Base Paint Properties + default: { + if (_parsePaintProperty(block, picture)) return true; + } + } + + //Vector Picture won't be requested since Saver replaces it with the Scene + return false; +} + + +static Paint* _parsePaint(TvgBinBlock baseBlock) +{ + bool (*parser)(TvgBinBlock, Paint*); + Paint *paint; + + //1. Decide the type of paint. + switch (baseBlock.type) { + case TVG_TAG_CLASS_SCENE: { + paint = Scene::gen().release(); + parser = _parseScene; + break; + } + case TVG_TAG_CLASS_SHAPE: { + paint = Shape::gen().release(); + parser = _parseShape; + break; + } + case TVG_TAG_CLASS_PICTURE: { + paint = Picture::gen().release(); + parser = _parsePicture; + break; + } + default: { + TVGERR("TVG", "Invalid Paint Type %d (0x%x)", baseBlock.type, baseBlock.type); + return nullptr; + } + } + + auto ptr = baseBlock.data; + + //2. Read Subsequent properties of the current paint. + while (ptr < baseBlock.end) { + auto block = _readBlock(ptr); + if (block.end > baseBlock.end) return paint; + if (!parser(block, paint)) { + TVGERR("TVG", "Encountered the wrong paint properties... Paint Class %d (0x%x)", baseBlock.type, baseBlock.type); + return paint; + } + ptr = block.end; + } + return paint; +} + + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Scene* TvgBinInterpreter::run(const char *ptr, const char* end) +{ + auto scene = Scene::gen(); + if (!scene) return nullptr; + + while (ptr < end) { + auto block = _readBlock(ptr); + if (block.end > end) { + TVGERR("TVG", "Corrupted tvg file."); + return nullptr; + } + scene->push(unique_ptr(_parsePaint(block))); + ptr = block.end; + } + + return scene.release(); +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgCommon.h b/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgCommon.h new file mode 100644 index 00000000000..974167945dd --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgCommon.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_TVG_COMMON_H_ +#define _TVG_TVG_COMMON_H_ + +#include "tvgCommon.h" +#include "tvgFormat.h" + +#define SIZE(A) sizeof(A) +#define READ_UI32(dst, src) memcpy(dst, (src), sizeof(uint32_t)) +#define READ_FLOAT(dst, src) memcpy(dst, (src), sizeof(float)) + + +/* Interface for Tvg Binary Interpreter */ +class TvgBinInterpreterBase +{ +public: + virtual ~TvgBinInterpreterBase() {} + + /* ptr: points the tvg binary body (after header) + end: end of the tvg binary data */ + virtual Scene* run(const char* ptr, const char* end) = 0; +}; + + +/* Version 0 */ +class TvgBinInterpreter : public TvgBinInterpreterBase +{ +public: + Scene* run(const char* ptr, const char* end) override; +}; + + +#endif //_TVG_TVG_COMMON_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgLoader.cpp b/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgLoader.cpp new file mode 100644 index 00000000000..5fb776c4926 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgLoader.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "tvgLoader.h" +#include "tvgTvgLoader.h" +#include "tvgCompressor.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +void TvgLoader::clear(bool all) +{ + if (copy) free((char*)data); + ptr = data = nullptr; + size = 0; + copy = false; + + delete(interpreter); + interpreter = nullptr; + + if (all) delete(root); +} + + +/* WARNING: Header format shall not change! */ +bool TvgLoader::readHeader() +{ + if (!ptr) return false; + + //Make sure the size is large enough to hold the header + if (size < TVG_HEADER_SIZE) return false; + + //1. Signature + if (memcmp(ptr, TVG_HEADER_SIGNATURE, TVG_HEADER_SIGNATURE_LENGTH)) return false; + ptr += TVG_HEADER_SIGNATURE_LENGTH; + + //2. Version + char version[TVG_HEADER_VERSION_LENGTH + 1]; + memcpy(version, ptr, TVG_HEADER_VERSION_LENGTH); + version[TVG_HEADER_VERSION_LENGTH - 1] = '\0'; + ptr += TVG_HEADER_VERSION_LENGTH; + this->version = atoi(version); + if (this->version > THORVG_VERSION_NUMBER()) { + TVGLOG("TVG", "This TVG file expects a higher version(%d) of ThorVG symbol(%d)", this->version, THORVG_VERSION_NUMBER()); + } + + //3. View Size + READ_FLOAT(&w, ptr); + ptr += SIZE(float); + READ_FLOAT(&h, ptr); + ptr += SIZE(float); + + //4. Reserved + if (*ptr & TVG_HEAD_FLAG_COMPRESSED) compressed = true; + ptr += TVG_HEADER_RESERVED_LENGTH; + + //5. Compressed Size if any + if (compressed) { + auto p = ptr; + + //TVG_HEADER_UNCOMPRESSED_SIZE + memcpy(&uncompressedSize, p, sizeof(uint32_t)); + p += SIZE(uint32_t); + + //TVG_HEADER_COMPRESSED_SIZE + memcpy(&compressedSize, p, sizeof(uint32_t)); + p += SIZE(uint32_t); + + //TVG_HEADER_COMPRESSED_SIZE_BITS + memcpy(&compressedSizeBits, p, sizeof(uint32_t)); + } + + ptr += TVG_HEADER_COMPRESS_SIZE; + + //Decide the proper Tvg Binary Interpreter based on the current file version + if (this->version >= 0) interpreter = new TvgBinInterpreter; + + return true; +} + + +void TvgLoader::run(unsigned tid) +{ + auto data = const_cast(ptr); + + if (compressed) { + data = (char*) lzwDecode((uint8_t*) data, compressedSize, compressedSizeBits, uncompressedSize); + root = interpreter->run(data, data + uncompressedSize); + free(data); + } else { + root = interpreter->run(data, this->data + size); + } + + clear(false); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +TvgLoader::TvgLoader() : ImageLoader(FileType::Tvg) +{ +} + + +TvgLoader::~TvgLoader() +{ + this->done(); + clear(); +} + + +bool TvgLoader::open(const string &path) +{ + clear(); + + ifstream f; + f.open(path, ifstream::in | ifstream::binary | ifstream::ate); + + if (!f.is_open()) return false; + + size = f.tellg(); + f.seekg(0, ifstream::beg); + + copy = true; + data = (char*)malloc(size); + if (!data) { + clear(); + f.close(); + return false; + } + + if (!f.read((char*)data, size)) + { + clear(); + f.close(); + return false; + } + + f.close(); + + ptr = data; + + return readHeader(); +} + + +bool TvgLoader::open(const char *data, uint32_t size, TVG_UNUSED const string& rpath, bool copy) +{ + clear(); + + if (copy) { + this->data = (char*)malloc(size); + if (!this->data) return false; + memcpy((char*)this->data, data, size); + } else this->data = data; + + this->ptr = this->data; + this->size = size; + this->copy = copy; + + return readHeader(); +} + + +bool TvgLoader::resize(Paint* paint, float w, float h) +{ + if (!paint) return false; + + auto sx = w / this->w; + auto sy = h / this->h; + + //Scale + auto scale = sx < sy ? sx : sy; + paint->scale(scale); + + //Align + float tx = 0, ty = 0; + auto sw = this->w * scale; + auto sh = this->h * scale; + if (sw > sh) ty -= (h - sh) * 0.5f; + else tx -= (w - sw) * 0.5f; + paint->translate(-tx, -ty); + + return true; +} + + +bool TvgLoader::read() +{ + if (!ptr || size == 0) return false; + + //the loading has been already completed + if (root || !LoadModule::read()) return true; + + TaskScheduler::request(this); + + return true; +} + + +Paint* TvgLoader::paint() +{ + this->done(); + auto ret = root; + root = nullptr; + return ret; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgLoader.h b/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgLoader.h new file mode 100644 index 00000000000..d267d44b335 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/loaders/tvg/tvgTvgLoader.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_TVG_LOADER_H_ +#define _TVG_TVG_LOADER_H_ + +#include "tvgTaskScheduler.h" +#include "tvgTvgCommon.h" + + +class TvgLoader : public ImageLoader, public Task +{ +public: + const char* data = nullptr; + const char* ptr = nullptr; + uint32_t size = 0; + uint16_t version = 0; + Scene* root = nullptr; + TvgBinInterpreterBase* interpreter = nullptr; + uint32_t uncompressedSize = 0; + uint32_t compressedSize = 0; + uint32_t compressedSizeBits = 0; + bool copy = false; + bool compressed = false; + + TvgLoader(); + ~TvgLoader(); + + bool open(const string &path) override; + bool open(const char *data, uint32_t size, const string& rpath, bool copy) override; + bool read() override; + bool resize(Paint* paint, float w, float h) override; + + Paint* paint() override; + +private: + bool readHeader(); + void run(unsigned tid) override; + void clear(bool all = true); +}; + +#endif //_TVG_TVG_LOADER_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwCommon.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwCommon.h new file mode 100644 index 00000000000..a3beb06eb93 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwCommon.h @@ -0,0 +1,583 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_SW_COMMON_H_ +#define _TVG_SW_COMMON_H_ + +#include "tvgCommon.h" +#include "tvgRender.h" + +#include + +#if 0 +#include +static double timeStamp() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (tv.tv_sec + tv.tv_usec / 1000000.0); +} +#endif + +#define SW_CURVE_TYPE_POINT 0 +#define SW_CURVE_TYPE_CUBIC 1 +#define SW_ANGLE_PI (180L << 16) +#define SW_ANGLE_2PI (SW_ANGLE_PI << 1) +#define SW_ANGLE_PI2 (SW_ANGLE_PI >> 1) + +using SwCoord = signed long; +using SwFixed = signed long long; + + +static inline float TO_FLOAT(SwCoord val) +{ + return static_cast(val) / 64.0f; +} + +struct SwPoint +{ + SwCoord x, y; + + SwPoint& operator+=(const SwPoint& rhs) + { + x += rhs.x; + y += rhs.y; + return *this; + } + + SwPoint operator+(const SwPoint& rhs) const + { + return {x + rhs.x, y + rhs.y}; + } + + SwPoint operator-(const SwPoint& rhs) const + { + return {x - rhs.x, y - rhs.y}; + } + + bool operator==(const SwPoint& rhs) const + { + return (x == rhs.x && y == rhs.y); + } + + bool operator!=(const SwPoint& rhs) const + { + return (x != rhs.x || y != rhs.y); + } + + bool zero() const + { + if (x == 0 && y == 0) return true; + else return false; + } + + bool small() const + { + //2 is epsilon... + if (abs(x) < 2 && abs(y) < 2) return true; + else return false; + } + + Point toPoint() const + { + return {TO_FLOAT(x), TO_FLOAT(y)}; + } +}; + +struct SwSize +{ + SwCoord w, h; +}; + +struct SwOutline +{ + Array pts; //the outline's points + Array cntrs; //the contour end points + Array types; //curve type + Array closed; //opened or closed path? + FillRule fillRule; +}; + +struct SwSpan +{ + uint16_t x, y; + uint16_t len; + uint8_t coverage; +}; + +struct SwRleData +{ + SwSpan *spans; + uint32_t alloc; + uint32_t size; +}; + +struct SwBBox +{ + SwPoint min, max; + + void reset() + { + min.x = min.y = max.x = max.y = 0; + } +}; + +struct SwFill +{ + struct SwLinear { + float dx, dy; + float len; + float offset; + }; + + struct SwRadial { + float a11, a12, a13; + float a21, a22, a23; + float fx, fy, fr; + float dx, dy, dr; + float invA, a; + }; + + union { + SwLinear linear; + SwRadial radial; + }; + + uint32_t* ctable; + FillSpread spread; + + bool translucent; +}; + +struct SwStrokeBorder +{ + uint32_t ptsCnt; + uint32_t maxPts; + SwPoint* pts; + uint8_t* tags; + int32_t start; //index of current sub-path start point + bool movable; //true: for ends of lineto borders +}; + +struct SwStroke +{ + SwFixed angleIn; + SwFixed angleOut; + SwPoint center; + SwFixed lineLength; + SwFixed subPathAngle; + SwPoint ptStartSubPath; + SwFixed subPathLineLength; + SwFixed width; + SwFixed miterlimit; + SwFill* fill = nullptr; + SwStrokeBorder borders[2]; + float sx, sy; + StrokeCap cap; + StrokeJoin join; + StrokeJoin joinSaved; + bool firstPt; + bool closedSubPath; + bool handleWideStrokes; +}; + +struct SwDashStroke +{ + SwOutline* outline = nullptr; + float curLen = 0; + int32_t curIdx = 0; + Point ptStart = {0, 0}; + Point ptCur = {0, 0}; + float* pattern = nullptr; + uint32_t cnt = 0; + bool curOpGap = false; + bool move = true; +}; + +struct SwShape +{ + SwOutline* outline = nullptr; + SwStroke* stroke = nullptr; + SwFill* fill = nullptr; + SwRleData* rle = nullptr; + SwRleData* strokeRle = nullptr; + SwBBox bbox; //Keep it boundary without stroke region. Using for optimal filling. + + bool fastTrack = false; //Fast Track: axis-aligned rectangle without any clips? +}; + +struct SwImage +{ + SwOutline* outline = nullptr; + SwRleData* rle = nullptr; + union { + pixel_t* data; //system based data pointer + uint32_t* buf32; //for explicit 32bits channels + uint8_t* buf8; //for explicit 8bits grayscale + }; + uint32_t w, h, stride; + int32_t ox = 0; //offset x + int32_t oy = 0; //offset y + float scale; + uint8_t channelSize; + + bool direct = false; //draw image directly (with offset) + bool scaled = false; //draw scaled image +}; + +typedef uint8_t(*SwMask)(uint8_t s, uint8_t d, uint8_t a); //src, dst, alpha +typedef uint32_t(*SwBlender)(uint32_t s, uint32_t d, uint8_t a); //src, dst, alpha +typedef uint32_t(*SwJoin)(uint8_t r, uint8_t g, uint8_t b, uint8_t a); //color channel join +typedef uint8_t(*SwAlpha)(uint8_t*); //blending alpha + +struct SwCompositor; + +struct SwSurface : Surface +{ + SwJoin join; + SwAlpha alphas[4]; //Alpha:2, InvAlpha:3, Luma:4, InvLuma:5 + SwBlender blender = nullptr; //blender (optional) + SwCompositor* compositor = nullptr; //compositor (optional) + BlendMethod blendMethod; //blending method (uint8_t) + + SwAlpha alpha(CompositeMethod method) + { + auto idx = (int)(method) - 2; //0: None, 1: ClipPath + return alphas[idx > 3 ? 0 : idx]; //CompositeMethod has only four Matting methods. + } + + SwSurface() + { + } + + SwSurface(const SwSurface* rhs) : Surface(rhs) + { + join = rhs->join; + memcpy(alphas, rhs->alphas, sizeof(alphas)); + blender = rhs->blender; + compositor = rhs->compositor; + blendMethod = rhs->blendMethod; + } +}; + +struct SwCompositor : Compositor +{ + SwSurface* recoverSfc; //Recover surface when composition is started + SwCompositor* recoverCmp; //Recover compositor when composition is done + SwImage image; + SwBBox bbox; + bool valid; +}; + +struct SwMpool +{ + SwOutline* outline; + SwOutline* strokeOutline; + SwOutline* dashOutline; + unsigned allocSize; +}; + +static inline SwCoord TO_SWCOORD(float val) +{ + return SwCoord(val * 64.0f); +} + +static inline uint32_t JOIN(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3) +{ + return (c0 << 24 | c1 << 16 | c2 << 8 | c3); +} + +static inline uint32_t ALPHA_BLEND(uint32_t c, uint32_t a) +{ + return (((((c >> 8) & 0x00ff00ff) * a + 0x00ff00ff) & 0xff00ff00) + + ((((c & 0x00ff00ff) * a + 0x00ff00ff) >> 8) & 0x00ff00ff)); +} + +static inline uint32_t INTERPOLATE(uint32_t s, uint32_t d, uint8_t a) +{ + return (((((((s >> 8) & 0xff00ff) - ((d >> 8) & 0xff00ff)) * a) + (d & 0xff00ff00)) & 0xff00ff00) + ((((((s & 0xff00ff) - (d & 0xff00ff)) * a) >> 8) + (d & 0xff00ff)) & 0xff00ff)); +} + +static inline uint8_t INTERPOLATE8(uint8_t s, uint8_t d, uint8_t a) +{ + return (((s) * (a) + 0xff) >> 8) + (((d) * ~(a) + 0xff) >> 8); +} + +static inline SwCoord HALF_STROKE(float width) +{ + return TO_SWCOORD(width * 0.5f); +} + +static inline uint8_t A(uint32_t c) +{ + return ((c) >> 24); +} + +static inline uint8_t IA(uint32_t c) +{ + return (~(c) >> 24); +} + +static inline uint8_t C1(uint32_t c) +{ + return ((c) >> 16); +} + +static inline uint8_t C2(uint32_t c) +{ + return ((c) >> 8); +} + +static inline uint8_t C3(uint32_t c) +{ + return (c); +} + +static inline uint32_t opBlendInterp(uint32_t s, uint32_t d, uint8_t a) +{ + return INTERPOLATE(s, d, a); +} + +static inline uint32_t opBlendNormal(uint32_t s, uint32_t d, uint8_t a) +{ + auto t = ALPHA_BLEND(s, a); + return t + ALPHA_BLEND(d, IA(t)); +} + +static inline uint32_t opBlendPreNormal(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + return s + ALPHA_BLEND(d, IA(s)); +} + +static inline uint32_t opBlendSrcOver(uint32_t s, TVG_UNUSED uint32_t d, TVG_UNUSED uint8_t a) +{ + return s; +} + +//TODO: BlendMethod could remove the alpha parameter. +static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + //if (s > d) => s - d + //else => d - s + auto c1 = (C1(s) > C1(d)) ? (C1(s) - C1(d)) : (C1(d) - C1(s)); + auto c2 = (C2(s) > C2(d)) ? (C2(s) - C2(d)) : (C2(d) - C2(s)); + auto c3 = (C3(s) > C3(d)) ? (C3(s) - C3(d)) : (C3(d) - C3(s)); + return JOIN(255, c1, c2, c3); +} + +static inline uint32_t opBlendExclusion(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + //A + B - 2AB + auto c1 = std::min(255, C1(s) + C1(d) - std::min(255, (C1(s) * C1(d)) << 1)); + auto c2 = std::min(255, C2(s) + C2(d) - std::min(255, (C2(s) * C2(d)) << 1)); + auto c3 = std::min(255, C3(s) + C3(d) - std::min(255, (C3(s) * C3(d)) << 1)); + return JOIN(255, c1, c2, c3); +} + +static inline uint32_t opBlendAdd(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + // s + d + auto c1 = std::min(C1(s) + C1(d), 255); + auto c2 = std::min(C2(s) + C2(d), 255); + auto c3 = std::min(C3(s) + C3(d), 255); + return JOIN(255, c1, c2, c3); +} + +static inline uint32_t opBlendScreen(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + // s + d - s * d + auto c1 = C1(s) + C1(d) - MULTIPLY(C1(s), C1(d)); + auto c2 = C2(s) + C2(d) - MULTIPLY(C2(s), C2(d)); + auto c3 = C3(s) + C3(d) - MULTIPLY(C3(s), C3(d)); + return JOIN(255, c1, c2, c3); +} + + +static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + // s * d + auto c1 = MULTIPLY(C1(s), C1(d)); + auto c2 = MULTIPLY(C2(s), C2(d)); + auto c3 = MULTIPLY(C3(s), C3(d)); + return JOIN(255, c1, c2, c3); +} + + +static inline uint32_t opBlendOverlay(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + // if (2 * d < da) => 2 * s * d, + // else => 1 - 2 * (1 - s) * (1 - d) + auto c1 = (C1(d) < 128) ? std::min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d)))); + auto c2 = (C2(d) < 128) ? std::min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d)))); + auto c3 = (C3(d) < 128) ? std::min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d)))); + return JOIN(255, c1, c2, c3); +} + +static inline uint32_t opBlendDarken(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + // min(s, d) + auto c1 = std::min(C1(s), C1(d)); + auto c2 = std::min(C2(s), C2(d)); + auto c3 = std::min(C3(s), C3(d)); + return JOIN(255, c1, c2, c3); +} + +static inline uint32_t opBlendLighten(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + // max(s, d) + auto c1 = std::max(C1(s), C1(d)); + auto c2 = std::max(C2(s), C2(d)); + auto c3 = std::max(C3(s), C3(d)); + return JOIN(255, c1, c2, c3); +} + +static inline uint32_t opBlendColorDodge(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + // d / (1 - s) + auto is = 0xffffffff - s; + auto c1 = (C1(is) > 0) ? (C1(d) / C1(is)) : C1(d); + auto c2 = (C2(is) > 0) ? (C2(d) / C2(is)) : C2(d); + auto c3 = (C3(is) > 0) ? (C3(d) / C3(is)) : C3(d); + return JOIN(255, c1, c2, c3); +} + +static inline uint32_t opBlendColorBurn(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + // 1 - (1 - d) / s + auto id = 0xffffffff - d; + auto c1 = 255 - ((C1(s) > 0) ? (C1(id) / C1(s)) : C1(id)); + auto c2 = 255 - ((C2(s) > 0) ? (C2(id) / C2(s)) : C2(id)); + auto c3 = 255 - ((C3(s) > 0) ? (C3(id) / C3(s)) : C3(id)); + return JOIN(255, c1, c2, c3); +} + +static inline uint32_t opBlendHardLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + auto c1 = (C1(s) < 128) ? std::min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d)))); + auto c2 = (C2(s) < 128) ? std::min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d)))); + auto c3 = (C3(s) < 128) ? std::min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d)))); + return JOIN(255, c1, c2, c3); +} + +static inline uint32_t opBlendSoftLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +{ + //(255 - 2 * s) * (d * d) + (2 * s * b) + auto c1 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C1(s)), MULTIPLY(C1(d), C1(d))) + 2 * MULTIPLY(C1(s), C1(d))); + auto c2 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C2(s)), MULTIPLY(C2(d), C2(d))) + 2 * MULTIPLY(C2(s), C2(d))); + auto c3 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C3(s)), MULTIPLY(C3(d), C3(d))) + 2 * MULTIPLY(C3(s), C3(d))); + return JOIN(255, c1, c2, c3); +} + + +int64_t mathMultiply(int64_t a, int64_t b); +int64_t mathDivide(int64_t a, int64_t b); +int64_t mathMulDiv(int64_t a, int64_t b, int64_t c); +void mathRotate(SwPoint& pt, SwFixed angle); +SwFixed mathTan(SwFixed angle); +SwFixed mathAtan(const SwPoint& pt); +SwFixed mathCos(SwFixed angle); +SwFixed mathSin(SwFixed angle); +void mathSplitCubic(SwPoint* base); +SwFixed mathDiff(SwFixed angle1, SwFixed angle2); +SwFixed mathLength(const SwPoint& pt); +bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); +SwFixed mathMean(SwFixed angle1, SwFixed angle2); +SwPoint mathTransform(const Point* to, const Matrix* transform); +bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack); +bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee); + +void shapeReset(SwShape* shape); +bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite); +bool shapePrepared(const SwShape* shape); +bool shapeGenRle(SwShape* shape, const RenderShape* rshape, bool antiAlias); +void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid); +void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* transform); +bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid); +void shapeFree(SwShape* shape); +void shapeDelStroke(SwShape* shape); +bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable); +bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable); +void shapeResetFill(SwShape* shape); +void shapeResetStrokeFill(SwShape* shape); +void shapeDelFill(SwShape* shape); +void shapeDelStrokeFill(SwShape* shape); + +void strokeReset(SwStroke* stroke, const RenderShape* shape, const Matrix* transform); +bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline); +SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid); +void strokeFree(SwStroke* stroke); + +bool imagePrepare(SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid); +bool imageGenRle(SwImage* image, const SwBBox& renderRegion, bool antiAlias); +void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid); +void imageReset(SwImage* image); +void imageFree(SwImage* image); + +bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable); +void fillReset(SwFill* fill); +void fillFree(SwFill* fill); + +//OPTIMIZE_ME: Skip the function pointer access +void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask maskOp, uint8_t opacity); //composite masking ver. +void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask maskOp, uint8_t opacity); //direct masking ver. +void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a); //blending ver. +void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver. +void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //matting ver. + +void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask op, uint8_t a); //composite masking ver. +void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask op, uint8_t a) ; //direct masking ver. +void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a); //blending ver. +void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver. +void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //matting ver. + +SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias); +SwRleData* rleRender(const SwBBox* bbox); +void rleFree(SwRleData* rle); +void rleReset(SwRleData* rle); +void rleMerge(SwRleData* rle, SwRleData* clip1, SwRleData* clip2); +void rleClipPath(SwRleData* rle, const SwRleData* clip); +void rleClipRect(SwRleData* rle, const SwBBox* clip); + +SwMpool* mpoolInit(uint32_t threads); +bool mpoolTerm(SwMpool* mpool); +bool mpoolClear(SwMpool* mpool); +SwOutline* mpoolReqOutline(SwMpool* mpool, unsigned idx); +void mpoolRetOutline(SwMpool* mpool, unsigned idx); +SwOutline* mpoolReqStrokeOutline(SwMpool* mpool, unsigned idx); +void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx); +SwOutline* mpoolReqDashOutline(SwMpool* mpool, unsigned idx); +void mpoolRetDashOutline(SwMpool* mpool, unsigned idx); + +bool rasterCompositor(SwSurface* surface); +bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id); +bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& bbox, uint8_t opacity); +bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id); +bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h); +void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len); +void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len); +void rasterUnpremultiply(Surface* surface); +void rasterPremultiply(Surface* surface); +bool rasterConvertCS(Surface* surface, ColorSpace to); + +#endif /* _TVG_SW_COMMON_H_ */ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwFill.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwFill.cpp new file mode 100644 index 00000000000..60763068d49 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwFill.cpp @@ -0,0 +1,784 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" +#include "tvgSwCommon.h" +#include "tvgFill.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +#define RADIAL_A_THRESHOLD 0.0005f +#define GRADIENT_STOP_SIZE 1024 +#define FIXPT_BITS 8 +#define FIXPT_SIZE (1<radial.a + * B = 2 * (dr * fr + rx * dx + ry * dy) + * C = fr^2 - rx^2 - ry^2 + * Derivatives are computed with respect to dx. + * This procedure aims to optimize and eliminate the need to calculate all values from the beginning + * for consecutive x values with a constant y. The Taylor series expansions are computed as long as + * its terms are non-zero. + */ +static void _calculateCoefficients(const SwFill* fill, uint32_t x, uint32_t y, float& b, float& deltaB, float& det, float& deltaDet, float& deltaDeltaDet) +{ + auto radial = &fill->radial; + + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + + b = (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy) * radial->invA; + deltaB = (radial->a11 * radial->dx + radial->a21 * radial->dy) * radial->invA; + + auto rr = rx * rx + ry * ry; + auto deltaRr = 2.0f * (rx * radial->a11 + ry * radial->a21) * radial->invA; + auto deltaDeltaRr = 2.0f * (radial->a11 * radial->a11 + radial->a21 * radial->a21) * radial->invA; + + det = b * b + (rr - radial->fr * radial->fr) * radial->invA; + deltaDet = 2.0f * b * deltaB + deltaB * deltaB + deltaRr + deltaDeltaRr; + deltaDeltaDet = 2.0f * deltaB * deltaB + deltaDeltaRr; +} + + +static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity) +{ + if (!fill->ctable) { + fill->ctable = static_cast(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t))); + if (!fill->ctable) return false; + } + + const Fill::ColorStop* colors; + auto cnt = fdata->colorStops(&colors); + if (cnt == 0 || !colors) return false; + + auto pColors = colors; + + auto a = MULTIPLY(pColors->a, opacity); + if (a < 255) fill->translucent = true; + + auto r = pColors->r; + auto g = pColors->g; + auto b = pColors->b; + auto rgba = surface->join(r, g, b, a); + + auto inc = 1.0f / static_cast(GRADIENT_STOP_SIZE); + auto pos = 1.5f * inc; + uint32_t i = 0; + + fill->ctable[i++] = ALPHA_BLEND(rgba | 0xff000000, a); + + while (pos <= pColors->offset) { + fill->ctable[i] = fill->ctable[i - 1]; + ++i; + pos += inc; + } + + for (uint32_t j = 0; j < cnt - 1; ++j) { + auto curr = colors + j; + auto next = curr + 1; + auto delta = 1.0f / (next->offset - curr->offset); + auto a2 = MULTIPLY(next->a, opacity); + if (!fill->translucent && a2 < 255) fill->translucent = true; + + auto rgba2 = surface->join(next->r, next->g, next->b, a2); + + while (pos < next->offset && i < GRADIENT_STOP_SIZE) { + auto t = (pos - curr->offset) * delta; + auto dist = static_cast(255 * t); + auto dist2 = 255 - dist; + + auto color = INTERPOLATE(rgba, rgba2, dist2); + fill->ctable[i] = ALPHA_BLEND((color | 0xff000000), (color >> 24)); + + ++i; + pos += inc; + } + rgba = rgba2; + a = a2; + } + rgba = ALPHA_BLEND((rgba | 0xff000000), a); + + for (; i < GRADIENT_STOP_SIZE; ++i) + fill->ctable[i] = rgba; + + //Make sure the last color stop is represented at the end of the table + fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba; + + return true; +} + + +bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* transform) +{ + float x1, x2, y1, y2; + if (linear->linear(&x1, &y1, &x2, &y2) != Result::Success) return false; + + fill->linear.dx = x2 - x1; + fill->linear.dy = y2 - y1; + fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy; + + if (fill->linear.len < FLT_EPSILON) return true; + + fill->linear.dx /= fill->linear.len; + fill->linear.dy /= fill->linear.len; + fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1; + + auto gradTransform = linear->transform(); + bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform)); + + if (isTransformation) { + if (transform) gradTransform = mathMultiply(transform, &gradTransform); + } else if (transform) { + gradTransform = *transform; + isTransformation = true; + } + + if (isTransformation) { + Matrix invTransform; + if (!mathInverse(&gradTransform, &invTransform)) return false; + + fill->linear.offset += fill->linear.dx * invTransform.e13 + fill->linear.dy * invTransform.e23; + + auto dx = fill->linear.dx; + fill->linear.dx = dx * invTransform.e11 + fill->linear.dy * invTransform.e21; + fill->linear.dy = dx * invTransform.e12 + fill->linear.dy * invTransform.e22; + + fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy; + } + + return true; +} + + +bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* transform) +{ + auto cx = P(radial)->cx; + auto cy = P(radial)->cy; + auto r = P(radial)->r; + auto fx = P(radial)->fx; + auto fy = P(radial)->fy; + auto fr = P(radial)->fr; + + if (r < FLT_EPSILON) return true; + + fill->radial.dr = r - fr; + fill->radial.dx = cx - fx; + fill->radial.dy = cy - fy; + fill->radial.fr = fr; + fill->radial.fx = fx; + fill->radial.fy = fy; + fill->radial.a = fill->radial.dr * fill->radial.dr - fill->radial.dx * fill->radial.dx - fill->radial.dy * fill->radial.dy; + + //This condition fulfills the SVG 1.1 std: + //the focal point, if outside the end circle, is moved to be on the end circle + //See: the SVG 2 std requirements: https://www.w3.org/TR/SVG2/pservers.html#RadialGradientNotes + if (fill->radial.a < 0) { + auto dist = sqrtf(fill->radial.dx * fill->radial.dx + fill->radial.dy * fill->radial.dy); + fill->radial.fx = cx + r * (fx - cx) / dist; + fill->radial.fy = cy + r * (fy - cy) / dist; + fill->radial.dx = cx - fill->radial.fx; + fill->radial.dy = cy - fill->radial.fy; + // Prevent loss of precision on Apple Silicon when dr=dy and dx=0 due to FMA + // https://github.com/thorvg/thorvg/issues/2014 + auto dr2 = fill->radial.dr * fill->radial.dr; + auto dx2 = fill->radial.dx * fill->radial.dx; + auto dy2 = fill->radial.dy * fill->radial.dy; + + fill->radial.a = dr2 - dx2 - dy2; + } + + if (fill->radial.a > 0) fill->radial.invA = 1.0f / fill->radial.a; + + auto gradTransform = radial->transform(); + bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform)); + + if (transform) { + if (isTransformation) gradTransform = mathMultiply(transform, &gradTransform); + else { + gradTransform = *transform; + isTransformation = true; + } + } + + if (isTransformation) { + Matrix invTransform; + if (!mathInverse(&gradTransform, &invTransform)) return false; + fill->radial.a11 = invTransform.e11; + fill->radial.a12 = invTransform.e12; + fill->radial.a13 = invTransform.e13; + fill->radial.a21 = invTransform.e21; + fill->radial.a22 = invTransform.e22; + fill->radial.a23 = invTransform.e23; + } else { + fill->radial.a11 = fill->radial.a22 = 1.0f; + fill->radial.a12 = fill->radial.a13 = 0.0f; + fill->radial.a21 = fill->radial.a23 = 0.0f; + } + return true; +} + + +static inline uint32_t _clamp(const SwFill* fill, int32_t pos) +{ + switch (fill->spread) { + case FillSpread::Pad: { + if (pos >= GRADIENT_STOP_SIZE) pos = GRADIENT_STOP_SIZE - 1; + else if (pos < 0) pos = 0; + break; + } + case FillSpread::Repeat: { + pos = pos % GRADIENT_STOP_SIZE; + if (pos < 0) pos = GRADIENT_STOP_SIZE + pos; + break; + } + case FillSpread::Reflect: { + auto limit = GRADIENT_STOP_SIZE * 2; + pos = pos % limit; + if (pos < 0) pos = limit + pos; + if (pos >= GRADIENT_STOP_SIZE) pos = (limit - pos - 1); + break; + } + } + return pos; +} + + +static inline uint32_t _fixedPixel(const SwFill* fill, int32_t pos) +{ + int32_t i = (pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS; + return fill->ctable[_clamp(fill, i)]; +} + + +static inline uint32_t _pixel(const SwFill* fill, float pos) +{ + auto i = static_cast(pos * (GRADIENT_STOP_SIZE - 1) + 0.5f); + return fill->ctable[_clamp(fill, i)]; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity) +{ + //edge case + if (fill->radial.a < RADIAL_A_THRESHOLD) { + auto radial = &fill->radial; + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + + if (opacity == 255) { + for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + *dst = opBlendNormal(_pixel(fill, x0), *dst, alpha(cmp)); + rx += radial->a11; + ry += radial->a21; + } + } else { + for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + *dst = opBlendNormal(_pixel(fill, x0), *dst, MULTIPLY(opacity, alpha(cmp))); + rx += radial->a11; + ry += radial->a21; + } + } + } else { + float b, deltaB, det, deltaDet, deltaDeltaDet; + _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet); + + if (opacity == 255) { + for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) { + *dst = opBlendNormal(_pixel(fill, sqrtf(det) - b), *dst, alpha(cmp)); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } + } else { + for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) { + *dst = opBlendNormal(_pixel(fill, sqrtf(det) - b), *dst, MULTIPLY(opacity, alpha(cmp))); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } + } + } +} + + +void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a) +{ + if (fill->radial.a < RADIAL_A_THRESHOLD) { + auto radial = &fill->radial; + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + for (uint32_t i = 0; i < len; ++i, ++dst) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + *dst = op(_pixel(fill, x0), *dst, a); + rx += radial->a11; + ry += radial->a21; + } + } else { + float b, deltaB, det, deltaDet, deltaDeltaDet; + _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet); + + for (uint32_t i = 0; i < len; ++i, ++dst) { + *dst = op(_pixel(fill, sqrtf(det) - b), *dst, a); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } + } +} + + +void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask maskOp, uint8_t a) +{ + if (fill->radial.a < RADIAL_A_THRESHOLD) { + auto radial = &fill->radial; + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + for (uint32_t i = 0 ; i < len ; ++i, ++dst) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + auto src = MULTIPLY(a, A(_pixel(fill, x0))); + *dst = maskOp(src, *dst, ~src); + rx += radial->a11; + ry += radial->a21; + } + } else { + float b, deltaB, det, deltaDet, deltaDeltaDet; + _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet); + + for (uint32_t i = 0 ; i < len ; ++i, ++dst) { + auto src = MULTIPLY(a, A(_pixel(fill, sqrtf(det) - b))); + *dst = maskOp(src, *dst, ~src); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } + } +} + + +void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask maskOp, uint8_t a) +{ + if (fill->radial.a < RADIAL_A_THRESHOLD) { + auto radial = &fill->radial; + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + for (uint32_t i = 0 ; i < len ; ++i, ++dst, ++cmp) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + auto src = MULTIPLY(A(A(_pixel(fill, x0))), a); + auto tmp = maskOp(src, *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + rx += radial->a11; + ry += radial->a21; + } + } else { + float b, deltaB, det, deltaDet, deltaDeltaDet; + _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet); + + for (uint32_t i = 0 ; i < len ; ++i, ++dst, ++cmp) { + auto src = MULTIPLY(A(_pixel(fill, sqrtf(det))), a); + auto tmp = maskOp(src, *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + deltaDet += deltaDeltaDet; + b += deltaB; + } + } +} + + +void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a) +{ + if (fill->radial.a < RADIAL_A_THRESHOLD) { + auto radial = &fill->radial; + auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx; + auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy; + + if (a == 255) { + for (uint32_t i = 0; i < len; ++i, ++dst) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + auto tmp = op(_pixel(fill, x0), *dst, 255); + *dst = op2(tmp, *dst, 255); + rx += radial->a11; + ry += radial->a21; + } + } else { + for (uint32_t i = 0; i < len; ++i, ++dst) { + auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy); + auto tmp = op(_pixel(fill, x0), *dst, 255); + auto tmp2 = op2(tmp, *dst, 255); + *dst = INTERPOLATE(tmp2, *dst, a); + rx += radial->a11; + ry += radial->a21; + } + } + } else { + float b, deltaB, det, deltaDet, deltaDeltaDet; + _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet); + if (a == 255) { + for (uint32_t i = 0 ; i < len ; ++i, ++dst) { + auto tmp = op(_pixel(fill, sqrtf(det) - b), *dst, 255); + *dst = op2(tmp, *dst, 255); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } + } else { + for (uint32_t i = 0 ; i < len ; ++i, ++dst) { + auto tmp = op(_pixel(fill, sqrtf(det) - b), *dst, 255); + auto tmp2 = op2(tmp, *dst, 255); + *dst = INTERPOLATE(tmp2, *dst, a); + det += deltaDet; + deltaDet += deltaDeltaDet; + b += deltaB; + } + } + } +} + + +void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity) +{ + //Rotation + float rx = x + 0.5f; + float ry = y + 0.5f; + float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); + float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); + + if (opacity == 255) { + if (mathZero(inc)) { + auto color = _fixedPixel(fill, static_cast(t * FIXPT_SIZE)); + for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) { + *dst = opBlendNormal(color, *dst, alpha(cmp)); + } + return; + } + + auto vMax = static_cast(INT32_MAX >> (FIXPT_BITS + 1)); + auto vMin = -vMax; + auto v = t + (inc * len); + + //we can use fixed point math + if (v < vMax && v > vMin) { + auto t2 = static_cast(t * FIXPT_SIZE); + auto inc2 = static_cast(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j, ++dst, cmp += csize) { + *dst = opBlendNormal(_fixedPixel(fill, t2), *dst, alpha(cmp)); + t2 += inc2; + } + //we have to fallback to float math + } else { + uint32_t counter = 0; + while (counter++ < len) { + *dst = opBlendNormal(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, alpha(cmp)); + ++dst; + t += inc; + cmp += csize; + } + } + } else { + if (mathZero(inc)) { + auto color = _fixedPixel(fill, static_cast(t * FIXPT_SIZE)); + for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) { + *dst = opBlendNormal(color, *dst, MULTIPLY(alpha(cmp), opacity)); + } + return; + } + + auto vMax = static_cast(INT32_MAX >> (FIXPT_BITS + 1)); + auto vMin = -vMax; + auto v = t + (inc * len); + + //we can use fixed point math + if (v < vMax && v > vMin) { + auto t2 = static_cast(t * FIXPT_SIZE); + auto inc2 = static_cast(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j, ++dst, cmp += csize) { + *dst = opBlendNormal(_fixedPixel(fill, t2), *dst, MULTIPLY(alpha(cmp), opacity)); + t2 += inc2; + } + //we have to fallback to float math + } else { + uint32_t counter = 0; + while (counter++ < len) { + *dst = opBlendNormal(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, MULTIPLY(opacity, alpha(cmp))); + ++dst; + t += inc; + cmp += csize; + } + } + } +} + + +void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask maskOp, uint8_t a) +{ + //Rotation + float rx = x + 0.5f; + float ry = y + 0.5f; + float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); + float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); + + if (mathZero(inc)) { + auto src = MULTIPLY(a, A(_fixedPixel(fill, static_cast(t * FIXPT_SIZE)))); + for (uint32_t i = 0; i < len; ++i, ++dst) { + *dst = maskOp(src, *dst, ~src); + } + return; + } + + auto vMax = static_cast(INT32_MAX >> (FIXPT_BITS + 1)); + auto vMin = -vMax; + auto v = t + (inc * len); + + //we can use fixed point math + if (v < vMax && v > vMin) { + auto t2 = static_cast(t * FIXPT_SIZE); + auto inc2 = static_cast(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j, ++dst) { + auto src = MULTIPLY(_fixedPixel(fill, t2), a); + *dst = maskOp(src, *dst, ~src); + t2 += inc2; + } + //we have to fallback to float math + } else { + uint32_t counter = 0; + while (counter++ < len) { + auto src = MULTIPLY(_pixel(fill, t / GRADIENT_STOP_SIZE), a); + *dst = maskOp(src, *dst, ~src); + ++dst; + t += inc; + } + } +} + + +void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask maskOp, uint8_t a) +{ + //Rotation + float rx = x + 0.5f; + float ry = y + 0.5f; + float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); + float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); + + if (mathZero(inc)) { + auto src = A(_fixedPixel(fill, static_cast(t * FIXPT_SIZE))); + src = MULTIPLY(src, a); + for (uint32_t i = 0; i < len; ++i, ++dst, ++cmp) { + auto tmp = maskOp(src, *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + } + return; + } + + auto vMax = static_cast(INT32_MAX >> (FIXPT_BITS + 1)); + auto vMin = -vMax; + auto v = t + (inc * len); + + //we can use fixed point math + if (v < vMax && v > vMin) { + auto t2 = static_cast(t * FIXPT_SIZE); + auto inc2 = static_cast(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j, ++dst, ++cmp) { + auto src = MULTIPLY(a, A(_fixedPixel(fill, t2))); + auto tmp = maskOp(src, *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + t2 += inc2; + } + //we have to fallback to float math + } else { + uint32_t counter = 0; + while (counter++ < len) { + auto src = MULTIPLY(A(_pixel(fill, t / GRADIENT_STOP_SIZE)), a); + auto tmp = maskOp(src, *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + ++dst; + ++cmp; + t += inc; + } + } +} + + +void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a) +{ + //Rotation + float rx = x + 0.5f; + float ry = y + 0.5f; + float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); + float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); + + if (mathZero(inc)) { + auto color = _fixedPixel(fill, static_cast(t * FIXPT_SIZE)); + for (uint32_t i = 0; i < len; ++i, ++dst) { + *dst = op(color, *dst, a); + } + return; + } + + auto vMax = static_cast(INT32_MAX >> (FIXPT_BITS + 1)); + auto vMin = -vMax; + auto v = t + (inc * len); + + //we can use fixed point math + if (v < vMax && v > vMin) { + auto t2 = static_cast(t * FIXPT_SIZE); + auto inc2 = static_cast(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j, ++dst) { + *dst = op(_fixedPixel(fill, t2), *dst, a); + t2 += inc2; + } + //we have to fallback to float math + } else { + uint32_t counter = 0; + while (counter++ < len) { + *dst = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, a); + ++dst; + t += inc; + } + } +} + + +void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a) +{ + //Rotation + float rx = x + 0.5f; + float ry = y + 0.5f; + float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); + float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); + + if (mathZero(inc)) { + auto color = _fixedPixel(fill, static_cast(t * FIXPT_SIZE)); + if (a == 255) { + for (uint32_t i = 0; i < len; ++i, ++dst) { + auto tmp = op(color, *dst, a); + *dst = op2(tmp, *dst, 255); + } + } else { + for (uint32_t i = 0; i < len; ++i, ++dst) { + auto tmp = op(color, *dst, a); + auto tmp2 = op2(tmp, *dst, 255); + *dst = INTERPOLATE(tmp2, *dst, a); + } + } + return; + } + + auto vMax = static_cast(INT32_MAX >> (FIXPT_BITS + 1)); + auto vMin = -vMax; + auto v = t + (inc * len); + + if (a == 255) { + //we can use fixed point math + if (v < vMax && v > vMin) { + auto t2 = static_cast(t * FIXPT_SIZE); + auto inc2 = static_cast(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j, ++dst) { + auto tmp = op(_fixedPixel(fill, t2), *dst, 255); + *dst = op2(tmp, *dst, 255); + t2 += inc2; + } + //we have to fallback to float math + } else { + uint32_t counter = 0; + while (counter++ < len) { + auto tmp = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, 255); + *dst = op2(tmp, *dst, 255); + ++dst; + t += inc; + } + } + } else { + //we can use fixed point math + if (v < vMax && v > vMin) { + auto t2 = static_cast(t * FIXPT_SIZE); + auto inc2 = static_cast(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j, ++dst) { + auto tmp = op(_fixedPixel(fill, t2), *dst, 255); + auto tmp2 = op2(tmp, *dst, 255); + *dst = INTERPOLATE(tmp2, *dst, a); + t2 += inc2; + } + //we have to fallback to float math + } else { + uint32_t counter = 0; + while (counter++ < len) { + auto tmp = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, 255); + auto tmp2 = op2(tmp, *dst, 255); + *dst = INTERPOLATE(tmp2, *dst, a); + ++dst; + t += inc; + } + } + } +} + + +bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable) +{ + if (!fill) return false; + + fill->spread = fdata->spread(); + + if (ctable) { + if (!_updateColorTable(fill, fdata, surface, opacity)) return false; + } + + if (fdata->identifier() == TVG_CLASS_ID_LINEAR) { + return _prepareLinear(fill, static_cast(fdata), transform); + } else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) { + return _prepareRadial(fill, static_cast(fdata), transform); + } + + //LOG: What type of gradient?! + + return false; +} + + +void fillReset(SwFill* fill) +{ + if (fill->ctable) { + free(fill->ctable); + fill->ctable = nullptr; + } + fill->translucent = false; +} + + +void fillFree(SwFill* fill) +{ + if (!fill) return; + + if (fill->ctable) free(fill->ctable); + + free(fill); +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwImage.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwImage.cpp new file mode 100644 index 00000000000..c1629455017 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwImage.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" +#include "tvgSwCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static inline bool _onlyShifted(const Matrix* m) +{ + if (mathEqual(m->e11, 1.0f) && mathEqual(m->e22, 1.0f) && mathZero(m->e12) && mathZero(m->e21)) return true; + return false; +} + + +static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* transform, SwMpool* mpool, unsigned tid) +{ + image->outline = mpoolReqOutline(mpool, tid); + auto outline = image->outline; + + outline->pts.reserve(5); + outline->types.reserve(5); + outline->cntrs.reserve(1); + outline->closed.reserve(1); + + Point to[4]; + if (mesh->triangleCnt > 0) { + // TODO: Optimise me. We appear to calculate this exact min/max bounding area in multiple + // places. We should be able to re-use one we have already done? Also see: + // tvgPicture.h --> bounds + // tvgSwRasterTexmap.h --> _rasterTexmapPolygonMesh + // + // TODO: Should we calculate the exact path(s) of the triangle mesh instead? + // i.e. copy tvgSwShape.capp -> _genOutline? + // + // TODO: Cntrs? + auto triangles = mesh->triangles; + auto min = triangles[0].vertex[0].pt; + auto max = triangles[0].vertex[0].pt; + + for (uint32_t i = 0; i < mesh->triangleCnt; ++i) { + if (triangles[i].vertex[0].pt.x < min.x) min.x = triangles[i].vertex[0].pt.x; + else if (triangles[i].vertex[0].pt.x > max.x) max.x = triangles[i].vertex[0].pt.x; + if (triangles[i].vertex[0].pt.y < min.y) min.y = triangles[i].vertex[0].pt.y; + else if (triangles[i].vertex[0].pt.y > max.y) max.y = triangles[i].vertex[0].pt.y; + + if (triangles[i].vertex[1].pt.x < min.x) min.x = triangles[i].vertex[1].pt.x; + else if (triangles[i].vertex[1].pt.x > max.x) max.x = triangles[i].vertex[1].pt.x; + if (triangles[i].vertex[1].pt.y < min.y) min.y = triangles[i].vertex[1].pt.y; + else if (triangles[i].vertex[1].pt.y > max.y) max.y = triangles[i].vertex[1].pt.y; + + if (triangles[i].vertex[2].pt.x < min.x) min.x = triangles[i].vertex[2].pt.x; + else if (triangles[i].vertex[2].pt.x > max.x) max.x = triangles[i].vertex[2].pt.x; + if (triangles[i].vertex[2].pt.y < min.y) min.y = triangles[i].vertex[2].pt.y; + else if (triangles[i].vertex[2].pt.y > max.y) max.y = triangles[i].vertex[2].pt.y; + } + to[0] = {min.x, min.y}; + to[1] = {max.x, min.y}; + to[2] = {max.x, max.y}; + to[3] = {min.x, max.y}; + } else { + auto w = static_cast(image->w); + auto h = static_cast(image->h); + to[0] = {0, 0}; + to[1] = {w, 0}; + to[2] = {w, h}; + to[3] = {0, h}; + } + + for (int i = 0; i < 4; i++) { + outline->pts.push(mathTransform(&to[i], transform)); + outline->types.push(SW_CURVE_TYPE_POINT); + } + + outline->pts.push(outline->pts[0]); + outline->types.push(SW_CURVE_TYPE_POINT); + outline->cntrs.push(outline->pts.count - 1); + outline->closed.push(true); + + image->outline = outline; + + return true; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool imagePrepare(SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid) +{ + image->direct = _onlyShifted(transform); + + //Fast track: Non-transformed image but just shifted. + if (image->direct) { + image->ox = -static_cast(round(transform->e13)); + image->oy = -static_cast(round(transform->e23)); + //Figure out the scale factor by transform matrix + } else { + auto scaleX = sqrtf((transform->e11 * transform->e11) + (transform->e21 * transform->e21)); + auto scaleY = sqrtf((transform->e22 * transform->e22) + (transform->e12 * transform->e12)); + image->scale = (fabsf(scaleX - scaleY) > 0.01f) ? 1.0f : scaleX; + + if (mathZero(transform->e12) && mathZero(transform->e21)) image->scaled = true; + else image->scaled = false; + } + + if (!_genOutline(image, mesh, transform, mpool, tid)) return false; + return mathUpdateOutlineBBox(image->outline, clipRegion, renderRegion, image->direct); +} + + +bool imageGenRle(SwImage* image, const SwBBox& renderRegion, bool antiAlias) +{ + if ((image->rle = rleRender(image->rle, image->outline, renderRegion, antiAlias))) return true; + + return false; +} + + +void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid) +{ + mpoolRetOutline(mpool, tid); + image->outline = nullptr; +} + + +void imageReset(SwImage* image) +{ + rleReset(image->rle); +} + + +void imageFree(SwImage* image) +{ + rleFree(image->rle); +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwMath.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwMath.cpp new file mode 100644 index 00000000000..ad5a2b7371c --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwMath.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" +#include "tvgSwCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static float TO_RADIAN(SwFixed angle) +{ + return (float(angle) / 65536.0f) * (MATH_PI / 180.0f); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwFixed mathMean(SwFixed angle1, SwFixed angle2) +{ + return angle1 + mathDiff(angle1, angle2) / 2; +} + + +bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) +{ + auto d1 = base[2] - base[3]; + auto d2 = base[1] - base[2]; + auto d3 = base[0] - base[1]; + + if (d1.small()) { + if (d2.small()) { + if (d3.small()) { + angleIn = angleMid = angleOut = 0; + return true; + } else { + angleIn = angleMid = angleOut = mathAtan(d3); + } + } else { + if (d3.small()) { + angleIn = angleMid = angleOut = mathAtan(d2); + } else { + angleIn = angleMid = mathAtan(d2); + angleOut = mathAtan(d3); + } + } + } else { + if (d2.small()) { + if (d3.small()) { + angleIn = angleMid = angleOut = mathAtan(d1); + } else { + angleIn = mathAtan(d1); + angleOut = mathAtan(d3); + angleMid = mathMean(angleIn, angleOut); + } + } else { + if (d3.small()) { + angleIn = mathAtan(d1); + angleMid = angleOut = mathAtan(d2); + } else { + angleIn = mathAtan(d1); + angleMid = mathAtan(d2); + angleOut = mathAtan(d3); + } + } + } + + auto theta1 = abs(mathDiff(angleIn, angleMid)); + auto theta2 = abs(mathDiff(angleMid, angleOut)); + + if ((theta1 < (SW_ANGLE_PI / 8)) && (theta2 < (SW_ANGLE_PI / 8))) return true; + return false; +} + + +int64_t mathMultiply(int64_t a, int64_t b) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + int64_t c = (a * b + 0x8000L) >> 16; + return (s > 0) ? c : -c; +} + + +int64_t mathDivide(int64_t a, int64_t b) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + int64_t q = b > 0 ? ((a << 16) + (b >> 1)) / b : 0x7FFFFFFFL; + return (s < 0 ? -q : q); +} + + +int64_t mathMulDiv(int64_t a, int64_t b, int64_t c) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + if (c < 0) { + c = -c; + s = -s; + } + int64_t d = c > 0 ? (a * b + (c >> 1)) / c : 0x7FFFFFFFL; + + return (s > 0 ? d : -d); +} + + +void mathRotate(SwPoint& pt, SwFixed angle) +{ + if (angle == 0 || pt.zero()) return; + + Point v = pt.toPoint(); + + auto radian = TO_RADIAN(angle); + auto cosv = cosf(radian); + auto sinv = sinf(radian); + + pt.x = SwCoord(roundf((v.x * cosv - v.y * sinv) * 64.0f)); + pt.y = SwCoord(roundf((v.x * sinv + v.y * cosv) * 64.0f)); +} + + +SwFixed mathTan(SwFixed angle) +{ + if (angle == 0) return 0; + return SwFixed(tanf(TO_RADIAN(angle)) * 65536.0f); +} + + +SwFixed mathAtan(const SwPoint& pt) +{ + if (pt.zero()) return 0; + return SwFixed(atan2f(TO_FLOAT(pt.y), TO_FLOAT(pt.x)) * (180.0f / MATH_PI) * 65536.0f); +} + + +SwFixed mathSin(SwFixed angle) +{ + if (angle == 0) return 0; + return mathCos(SW_ANGLE_PI2 - angle); +} + + +SwFixed mathCos(SwFixed angle) +{ + return SwFixed(cosf(TO_RADIAN(angle)) * 65536.0f); +} + + +SwFixed mathLength(const SwPoint& pt) +{ + if (pt.zero()) return 0; + + //trivial case + if (pt.x == 0) return abs(pt.y); + if (pt.y == 0) return abs(pt.x); + + auto v = pt.toPoint(); + //return static_cast(sqrtf(v.x * v.x + v.y * v.y) * 65536.0f); + + /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. + With alpha = 1, beta = 3/8, giving results with the largest error less + than 7% compared to the exact value. */ + if (v.x < 0) v.x = -v.x; + if (v.y < 0) v.y = -v.y; + return static_cast((v.x > v.y) ? (v.x + v.y * 0.375f) : (v.y + v.x * 0.375f)); +} + + +void mathSplitCubic(SwPoint* base) +{ + SwCoord a, b, c, d; + + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = (base[0].x + c) >> 1; + base[5].x = b = (base[3].x + d) >> 1; + c = (c + d) >> 1; + base[2].x = a = (a + c) >> 1; + base[4].x = b = (b + c) >> 1; + base[3].x = (a + b) >> 1; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = (base[0].y + c) >> 1; + base[5].y = b = (base[3].y + d) >> 1; + c = (c + d) >> 1; + base[2].y = a = (a + c) >> 1; + base[4].y = b = (b + c) >> 1; + base[3].y = (a + b) >> 1; +} + + +SwFixed mathDiff(SwFixed angle1, SwFixed angle2) +{ + auto delta = angle2 - angle1; + + delta %= SW_ANGLE_2PI; + if (delta < 0) delta += SW_ANGLE_2PI; + if (delta > SW_ANGLE_PI) delta -= SW_ANGLE_2PI; + + return delta; +} + + +SwPoint mathTransform(const Point* to, const Matrix* transform) +{ + if (!transform) return {TO_SWCOORD(to->x), TO_SWCOORD(to->y)}; + + auto tx = to->x * transform->e11 + to->y * transform->e12 + transform->e13; + auto ty = to->x * transform->e21 + to->y * transform->e22 + transform->e23; + + return {TO_SWCOORD(tx), TO_SWCOORD(ty)}; +} + + +bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee) +{ + clipee.max.x = (clipee.max.x < clipper.max.x) ? clipee.max.x : clipper.max.x; + clipee.max.y = (clipee.max.y < clipper.max.y) ? clipee.max.y : clipper.max.y; + clipee.min.x = (clipee.min.x > clipper.min.x) ? clipee.min.x : clipper.min.x; + clipee.min.y = (clipee.min.y > clipper.min.y) ? clipee.min.y : clipper.min.y; + + //Check valid region + if (clipee.max.x - clipee.min.x < 1 && clipee.max.y - clipee.min.y < 1) return false; + + //Check boundary + if (clipee.min.x >= clipper.max.x || clipee.min.y >= clipper.max.y || + clipee.max.x <= clipper.min.x || clipee.max.y <= clipper.min.y) return false; + + return true; +} + + +bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack) +{ + if (!outline) return false; + + if (outline->pts.empty() || outline->cntrs.empty()) { + renderRegion.reset(); + return false; + } + + auto pt = outline->pts.begin(); + + auto xMin = pt->x; + auto xMax = pt->x; + auto yMin = pt->y; + auto yMax = pt->y; + + for (++pt; pt < outline->pts.end(); ++pt) { + if (xMin > pt->x) xMin = pt->x; + if (xMax < pt->x) xMax = pt->x; + if (yMin > pt->y) yMin = pt->y; + if (yMax < pt->y) yMax = pt->y; + } + //Since no antialiasing is applied in the Fast Track case, + //the rasterization region has to be rearranged. + //https://github.com/Samsung/thorvg/issues/916 + if (fastTrack) { + renderRegion.min.x = static_cast(round(xMin / 64.0f)); + renderRegion.max.x = static_cast(round(xMax / 64.0f)); + renderRegion.min.y = static_cast(round(yMin / 64.0f)); + renderRegion.max.y = static_cast(round(yMax / 64.0f)); + } else { + renderRegion.min.x = xMin >> 6; + renderRegion.max.x = (xMax + 63) >> 6; + renderRegion.min.y = yMin >> 6; + renderRegion.max.y = (yMax + 63) >> 6; + } + return mathClipBBox(clipRegion, renderRegion); +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwMemPool.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwMemPool.cpp new file mode 100644 index 00000000000..b85d9438732 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwMemPool.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgSwCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwOutline* mpoolReqOutline(SwMpool* mpool, unsigned idx) +{ + return &mpool->outline[idx]; +} + + +void mpoolRetOutline(SwMpool* mpool, unsigned idx) +{ + mpool->outline[idx].pts.clear(); + mpool->outline[idx].cntrs.clear(); + mpool->outline[idx].types.clear(); + mpool->outline[idx].closed.clear(); +} + + +SwOutline* mpoolReqStrokeOutline(SwMpool* mpool, unsigned idx) +{ + return &mpool->strokeOutline[idx]; +} + + +void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx) +{ + mpool->strokeOutline[idx].pts.clear(); + mpool->strokeOutline[idx].cntrs.clear(); + mpool->strokeOutline[idx].types.clear(); + mpool->strokeOutline[idx].closed.clear(); +} + + +SwOutline* mpoolReqDashOutline(SwMpool* mpool, unsigned idx) +{ + return &mpool->dashOutline[idx]; +} + + +void mpoolRetDashOutline(SwMpool* mpool, unsigned idx) +{ + mpool->dashOutline[idx].pts.clear(); + mpool->dashOutline[idx].cntrs.clear(); + mpool->dashOutline[idx].types.clear(); + mpool->dashOutline[idx].closed.clear(); +} + + +SwMpool* mpoolInit(uint32_t threads) +{ + auto allocSize = threads + 1; + + auto mpool = static_cast(calloc(sizeof(SwMpool), 1)); + mpool->outline = static_cast(calloc(1, sizeof(SwOutline) * allocSize)); + mpool->strokeOutline = static_cast(calloc(1, sizeof(SwOutline) * allocSize)); + mpool->dashOutline = static_cast(calloc(1, sizeof(SwOutline) * allocSize)); + mpool->allocSize = allocSize; + + return mpool; +} + + +bool mpoolClear(SwMpool* mpool) +{ + for (unsigned i = 0; i < mpool->allocSize; ++i) { + mpool->outline[i].pts.reset(); + mpool->outline[i].cntrs.reset(); + mpool->outline[i].types.reset(); + mpool->outline[i].closed.reset(); + + mpool->strokeOutline[i].pts.reset(); + mpool->strokeOutline[i].cntrs.reset(); + mpool->strokeOutline[i].types.reset(); + mpool->strokeOutline[i].closed.reset(); + + mpool->dashOutline[i].pts.reset(); + mpool->dashOutline[i].cntrs.reset(); + mpool->dashOutline[i].types.reset(); + mpool->dashOutline[i].closed.reset(); + } + + return true; +} + + +bool mpoolTerm(SwMpool* mpool) +{ + if (!mpool) return false; + + mpoolClear(mpool); + + free(mpool->outline); + free(mpool->strokeOutline); + free(mpool->dashOutline); + free(mpool); + + return true; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRaster.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRaster.cpp new file mode 100644 index 00000000000..4e0020ee524 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRaster.cpp @@ -0,0 +1,1961 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef _WIN32 + #include +#elif defined(__linux__) + #include +#else + #include +#endif + +#include "tvgMath.h" +#include "tvgRender.h" +#include "tvgSwCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ +constexpr auto DOWN_SCALE_TOLERANCE = 0.5f; + +struct FillLinear +{ + void operator()(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask op, uint8_t a) + { + fillLinear(fill, dst, y, x, len, op, a); + } + + void operator()(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask op, uint8_t a) + { + fillLinear(fill, dst, y, x, len, cmp, op, a); + } + + void operator()(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a) + { + fillLinear(fill, dst, y, x, len, op, a); + } + + void operator()(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity) + { + fillLinear(fill, dst, y, x, len, cmp, alpha, csize, opacity); + } + + void operator()(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a) + { + fillLinear(fill, dst, y, x, len, op, op2, a); + } + +}; + +struct FillRadial +{ + void operator()(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask op, uint8_t a) + { + fillRadial(fill, dst, y, x, len, op, a); + } + + void operator()(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask op, uint8_t a) + { + fillRadial(fill, dst, y, x, len, cmp, op, a); + } + + void operator()(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a) + { + fillRadial(fill, dst, y, x, len, op, a); + } + + void operator()(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity) + { + fillRadial(fill, dst, y, x, len, cmp, alpha, csize, opacity); + } + + void operator()(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a) + { + fillRadial(fill, dst, y, x, len, op, op2, a); + } +}; + + +static inline uint8_t _alpha(uint8_t* a) +{ + return *a; +} + + +static inline uint8_t _ialpha(uint8_t* a) +{ + return ~(*a); +} + + +static inline uint8_t _abgrLuma(uint8_t* c) +{ + auto v = *(uint32_t*)c; + return ((((v&0xff)*54) + (((v>>8)&0xff)*183) + (((v>>16)&0xff)*19))) >> 8; //0.2125*R + 0.7154*G + 0.0721*B +} + + +static inline uint8_t _argbLuma(uint8_t* c) +{ + auto v = *(uint32_t*)c; + return ((((v&0xff)*19) + (((v>>8)&0xff)*183) + (((v>>16)&0xff)*54))) >> 8; //0.0721*B + 0.7154*G + 0.2125*R +} + + +static inline uint8_t _abgrInvLuma(uint8_t* c) +{ + return ~_abgrLuma(c); +} + + +static inline uint8_t _argbInvLuma(uint8_t* c) +{ + return ~_argbLuma(c); +} + + +static inline uint32_t _abgrJoin(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (a << 24 | b << 16 | g << 8 | r); +} + + +static inline uint32_t _argbJoin(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (a << 24 | r << 16 | g << 8 | b); +} + +static inline bool _blending(const SwSurface* surface) +{ + return (surface->blender) ? true : false; +} + + +/* OPTIMIZE_ME: Probably, we can separate masking(8bits) / composition(32bits) + This would help to enhance the performance by avoiding the unnecessary matting from the composition */ +static inline bool _compositing(const SwSurface* surface) +{ + if (!surface->compositor || (int)surface->compositor->method <= (int)CompositeMethod::ClipPath) return false; + return true; +} + + +static inline bool _matting(const SwSurface* surface) +{ + if ((int)surface->compositor->method < (int)CompositeMethod::AddMask) return true; + else return false; +} + +static inline uint8_t _opMaskNone(uint8_t s, TVG_UNUSED uint8_t d, TVG_UNUSED uint8_t a) +{ + return s; +} + +static inline uint8_t _opMaskAdd(uint8_t s, uint8_t d, uint8_t a) +{ + return s + MULTIPLY(d, a); +} + + +static inline uint8_t _opMaskSubtract(uint8_t s, uint8_t d, TVG_UNUSED uint8_t a) +{ + return MULTIPLY(s, 255 - d); +} + + +static inline uint8_t _opMaskIntersect(uint8_t s, uint8_t d, TVG_UNUSED uint8_t a) +{ + return MULTIPLY(s, d); +} + + +static inline uint8_t _opMaskDifference(uint8_t s, uint8_t d, uint8_t a) +{ + return MULTIPLY(s, 255 - d) + MULTIPLY(d, a); +} + + +static inline bool _direct(CompositeMethod method) +{ + //subtract & Intersect allows the direct composition + if (method == CompositeMethod::SubtractMask || method == CompositeMethod::IntersectMask) return true; + return false; +} + + +static inline SwMask _getMaskOp(CompositeMethod method) +{ + switch (method) { + case CompositeMethod::AddMask: return _opMaskAdd; + case CompositeMethod::SubtractMask: return _opMaskSubtract; + case CompositeMethod::DifferenceMask: return _opMaskDifference; + case CompositeMethod::IntersectMask: return _opMaskIntersect; + default: return nullptr; + } +} + + +static bool _compositeMaskImage(SwSurface* surface, const SwImage* image, const SwBBox& region) +{ + auto dbuffer = &surface->buf8[region.min.y * surface->stride + region.min.x]; + auto sbuffer = image->buf8 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto src = sbuffer; + for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) { + *dst = *src + MULTIPLY(*dst, ~*src); + } + dbuffer += surface->stride; + sbuffer += image->stride; + } + return true; +} + + +#include "tvgSwRasterTexmap.h" +#include "tvgSwRasterC.h" +#include "tvgSwRasterAvx.h" +#include "tvgSwRasterNeon.h" + + +static inline uint32_t _sampleSize(float scale) +{ + auto sampleSize = static_cast(0.5f / scale); + if (sampleSize == 0) sampleSize = 1; + return sampleSize; +} + + +//Bilinear Interpolation +//OPTIMIZE_ME: Skip the function pointer access +static uint32_t _interpUpScaler(const uint32_t *img, TVG_UNUSED uint32_t stride, uint32_t w, uint32_t h, float sx, float sy, TVG_UNUSED int32_t miny, TVG_UNUSED int32_t maxy, TVG_UNUSED int32_t n) +{ + auto rx = (size_t)(sx); + auto ry = (size_t)(sy); + auto rx2 = rx + 1; + if (rx2 >= w) rx2 = w - 1; + auto ry2 = ry + 1; + if (ry2 >= h) ry2 = h - 1; + + auto dx = (sx > 0.0f) ? static_cast((sx - rx) * 255.0f) : 0; + auto dy = (sy > 0.0f) ? static_cast((sy - ry) * 255.0f) : 0; + + auto c1 = img[rx + ry * w]; + auto c2 = img[rx2 + ry * w]; + auto c3 = img[rx + ry2 * w]; + auto c4 = img[rx2 + ry2 * w]; + + return INTERPOLATE(INTERPOLATE(c4, c3, dx), INTERPOLATE(c2, c1, dx), dy); +} + + +//2n x 2n Mean Kernel +//OPTIMIZE_ME: Skip the function pointer access +static uint32_t _interpDownScaler(const uint32_t *img, uint32_t stride, uint32_t w, uint32_t h, float sx, TVG_UNUSED float sy, int32_t miny, int32_t maxy, int32_t n) +{ + size_t c[4] = {0, 0, 0, 0}; + + int32_t minx = (int32_t)sx - n; + if (minx < 0) minx = 0; + + int32_t maxx = (int32_t)sx + n; + if (maxx >= (int32_t)w) maxx = w; + + int32_t inc = (n / 2) + 1; + n = 0; + + auto src = img + minx + miny * stride; + + for (auto y = miny; y < maxy; y += inc) { + auto p = src; + for (auto x = minx; x < maxx; x += inc, p += inc) { + c[0] += A(*p); + c[1] += C1(*p); + c[2] += C2(*p); + c[3] += C3(*p); + ++n; + } + src += (stride * inc); + } + + c[0] /= n; + c[1] /= n; + c[2] /= n; + c[3] /= n; + + return (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]; +} + + +/************************************************************************/ +/* Rect */ +/************************************************************************/ + +static bool _rasterCompositeMaskedRect(SwSurface* surface, const SwBBox& region, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + auto w = static_cast(region.max.x - region.min.x); + auto h = static_cast(region.max.y - region.min.y); + auto cstride = surface->compositor->image.stride; + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); //compositor buffer + auto ialpha = 255 - a; + + for (uint32_t y = 0; y < h; ++y) { + auto cmp = cbuffer; + for (uint32_t x = 0; x < w; ++x, ++cmp) { + *cmp = maskOp(a, *cmp, ialpha); + } + cbuffer += cstride; + } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); +} + + +static bool _rasterDirectMaskedRect(SwSurface* surface, const SwBBox& region, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + auto w = static_cast(region.max.x - region.min.x); + auto h = static_cast(region.max.y - region.min.y); + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * surface->compositor->image.stride + region.min.x); //compositor buffer + auto dbuffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); //destination buffer + + for (uint32_t y = 0; y < h; ++y) { + auto cmp = cbuffer; + auto dst = dbuffer; + for (uint32_t x = 0; x < w; ++x, ++cmp, ++dst) { + auto tmp = maskOp(a, *cmp, 0); //not use alpha. + *dst = tmp + MULTIPLY(*dst, ~tmp); + } + cbuffer += surface->compositor->image.stride; + dbuffer += surface->stride; + } + return true; +} + + +static bool _rasterMaskedRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + //8bit masking channels composition + if (surface->channelSize != sizeof(uint8_t)) return false; + + TVGLOG("SW_ENGINE", "Masked(%d) Rect [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); + + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) return _rasterDirectMaskedRect(surface, region, maskOp, r, g, b, a); + else return _rasterCompositeMaskedRect(surface, region, maskOp, r, g, b, a); + return false; +} + + +static bool _rasterMattedRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + auto w = static_cast(region.max.x - region.min.x); + auto h = static_cast(region.max.y - region.min.y); + auto csize = surface->compositor->image.channelSize; + auto cbuffer = surface->compositor->image.buf8 + ((region.min.y * surface->compositor->image.stride + region.min.x) * csize); //compositor buffer + auto alpha = surface->alpha(surface->compositor->method); + + TVGLOG("SW_ENGINE", "Matted(%d) Rect [Region: %lu %lu %u %u]", (int)surface->compositor->method, region.min.x, region.min.y, w, h); + + //32bits channels + if (surface->channelSize == sizeof(uint32_t)) { + auto color = surface->join(r, g, b, a); + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + auto cmp = &cbuffer[y * surface->compositor->image.stride * csize]; + for (uint32_t x = 0; x < w; ++x, ++dst, cmp += csize) { + *dst = INTERPOLATE(color, *dst, alpha(cmp)); + } + } + //8bits grayscale + } else if (surface->channelSize == sizeof(uint8_t)) { + auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x; + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + auto cmp = &cbuffer[y * surface->compositor->image.stride * csize]; + for (uint32_t x = 0; x < w; ++x, ++dst, cmp += csize) { + *dst = INTERPOLATE8(a, *dst, alpha(cmp)); + } + } + } + return true; +} + + +static bool _rasterBlendingRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (surface->channelSize != sizeof(uint32_t)) return false; + + auto w = static_cast(region.max.x - region.min.x); + auto h = static_cast(region.max.y - region.min.y); + auto color = surface->join(r, g, b, a); + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + auto ialpha = 255 - a; + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + for (uint32_t x = 0; x < w; ++x, ++dst) { + *dst = surface->blender(color, *dst, ialpha); + } + } + return true; +} + + +static bool _rasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ +#if defined(THORVG_AVX_VECTOR_SUPPORT) + return avxRasterTranslucentRect(surface, region, r, g, b, a); +#elif defined(THORVG_NEON_VECTOR_SUPPORT) + return neonRasterTranslucentRect(surface, region, r, g, b, a); +#else + return cRasterTranslucentRect(surface, region, r, g, b, a); +#endif +} + + +static bool _rasterSolidRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b) +{ + auto w = static_cast(region.max.x - region.min.x); + auto h = static_cast(region.max.y - region.min.y); + + //32bits channels + if (surface->channelSize == sizeof(uint32_t)) { + auto color = surface->join(r, g, b, 255); + auto buffer = surface->buf32 + (region.min.y * surface->stride); + for (uint32_t y = 0; y < h; ++y) { + rasterPixel32(buffer + y * surface->stride, color, region.min.x, w); + } + return true; + } + //8bits grayscale + if (surface->channelSize == sizeof(uint8_t)) { + for (uint32_t y = 0; y < h; ++y) { + rasterGrayscale8(surface->buf8, 255, (y + region.min.y) * surface->stride + region.min.x, w); + } + return true; + } + return false; +} + + +static bool _rasterRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (_compositing(surface)) { + if (_matting(surface)) return _rasterMattedRect(surface, region, r, g, b, a); + else return _rasterMaskedRect(surface, region, r, g, b, a); + } else if (_blending(surface)) { + return _rasterBlendingRect(surface, region, r, g, b, a); + } else { + if (a == 255) return _rasterSolidRect(surface, region, r, g, b); + else return _rasterTranslucentRect(surface, region, r, g, b, a); + } + return false; +} + + +/************************************************************************/ +/* Rle */ +/************************************************************************/ + +static bool _rasterCompositeMaskedRle(SwSurface* surface, SwRleData* rle, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + auto span = rle->spans; + auto cbuffer = surface->compositor->image.buf8; + auto cstride = surface->compositor->image.stride; + uint8_t src; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto cmp = &cbuffer[span->y * cstride + span->x]; + if (span->coverage == 255) src = a; + else src = MULTIPLY(a, span->coverage); + auto ialpha = 255 - src; + for (auto x = 0; x < span->len; ++x, ++cmp) { + *cmp = maskOp(src, *cmp, ialpha); + } + } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); +} + + +static bool _rasterDirectMaskedRle(SwSurface* surface, SwRleData* rle, SwMask maskOp, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + auto span = rle->spans; + auto cbuffer = surface->compositor->image.buf8; + auto cstride = surface->compositor->image.stride; + uint8_t src; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto cmp = &cbuffer[span->y * cstride + span->x]; + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + if (span->coverage == 255) src = a; + else src = MULTIPLY(a, span->coverage); + for (auto x = 0; x < span->len; ++x, ++cmp, ++dst) { + auto tmp = maskOp(src, *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); + } + } + return true; +} + + +static bool _rasterMaskedRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + TVGLOG("SW_ENGINE", "Masked(%d) Rle", (int)surface->compositor->method); + + //8bit masking channels composition + if (surface->channelSize != sizeof(uint8_t)) return false; + + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) return _rasterDirectMaskedRle(surface, rle, maskOp, r, g, b, a); + else return _rasterCompositeMaskedRle(surface, rle, maskOp, r, g, b, a); + return false; +} + + +static bool _rasterMattedRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + TVGLOG("SW_ENGINE", "Matted(%d) Rle", (int)surface->compositor->method); + + auto span = rle->spans; + auto cbuffer = surface->compositor->image.buf8; + auto csize = surface->compositor->image.channelSize; + auto alpha = surface->alpha(surface->compositor->method); + + //32bit channels + if (surface->channelSize == sizeof(uint32_t)) { + uint32_t src; + auto color = surface->join(r, g, b, a); + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + auto cmp = &cbuffer[(span->y * surface->compositor->image.stride + span->x) * csize]; + if (span->coverage == 255) src = color; + else src = ALPHA_BLEND(color, span->coverage); + for (uint32_t x = 0; x < span->len; ++x, ++dst, cmp += csize) { + auto tmp = ALPHA_BLEND(src, alpha(cmp)); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } + } + return true; + } + //8bit grayscale + if (surface->channelSize == sizeof(uint8_t)) { + uint8_t src; + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + auto cmp = &cbuffer[(span->y * surface->compositor->image.stride + span->x) * csize]; + if (span->coverage == 255) src = a; + else src = MULTIPLY(a, span->coverage); + for (uint32_t x = 0; x < span->len; ++x, ++dst, cmp += csize) { + *dst = INTERPOLATE8(src, *dst, alpha(cmp)); + } + } + return true; + } + return false; +} + + +static bool _rasterBlendingRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (surface->channelSize != sizeof(uint32_t)) return false; + + auto span = rle->spans; + auto color = surface->join(r, g, b, a); + auto ialpha = 255 - a; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + if (span->coverage == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + *dst = surface->blender(color, *dst, ialpha); + } + } else { + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + auto tmp = surface->blender(color, *dst, ialpha); + *dst = INTERPOLATE(tmp, *dst, span->coverage); + } + } + } + return true; +} + + +static bool _rasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ +#if defined(THORVG_AVX_VECTOR_SUPPORT) + return avxRasterTranslucentRle(surface, rle, r, g, b, a); +#elif defined(THORVG_NEON_VECTOR_SUPPORT) + return neonRasterTranslucentRle(surface, rle, r, g, b, a); +#else + return cRasterTranslucentRle(surface, rle, r, g, b, a); +#endif +} + + +static bool _rasterSolidRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b) +{ + auto span = rle->spans; + + //32bit channels + if (surface->channelSize == sizeof(uint32_t)) { + auto color = surface->join(r, g, b, 255); + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + if (span->coverage == 255) { + rasterPixel32(surface->buf32 + span->y * surface->stride, color, span->x, span->len); + } else { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + auto src = ALPHA_BLEND(color, span->coverage); + auto ialpha = 255 - span->coverage; + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + *dst = src + ALPHA_BLEND(*dst, ialpha); + } + } + } + //8bit grayscale + } else if (surface->channelSize == sizeof(uint8_t)) { + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + if (span->coverage == 255) { + rasterGrayscale8(surface->buf8, span->coverage, span->y * surface->stride + span->x, span->len); + } else { + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + auto ialpha = 255 - span->coverage; + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + *dst = span->coverage + MULTIPLY(*dst, ialpha); + } + } + } + } + return true; +} + + +static bool _rasterRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (!rle) return false; + + if (_compositing(surface)) { + if (_matting(surface)) return _rasterMattedRle(surface, rle, r, g, b, a); + else return _rasterMaskedRle(surface, rle, r, g, b, a); + } else if (_blending(surface)) { + return _rasterBlendingRle(surface, rle, r, g, b, a); + } else { + if (a == 255) return _rasterSolidRle(surface, rle, r, g, b); + else return _rasterTranslucentRle(surface, rle, r, g, b, a); + } + return false; +} + + +/************************************************************************/ +/* RLE Scaled Image */ +/************************************************************************/ + +#define SCALED_IMAGE_RANGE_Y(y) \ + auto sy = (y) * itransform->e22 + itransform->e23 - 0.49f; \ + if (sy <= -0.5f || (uint32_t)(sy + 0.5f) >= image->h) continue; \ + if (scaleMethod == _interpDownScaler) { \ + auto my = (int32_t)round(sy); \ + miny = my - (int32_t)sampleSize; \ + if (miny < 0) miny = 0; \ + maxy = my + (int32_t)sampleSize; \ + if (maxy >= (int32_t)image->h) maxy = (int32_t)image->h; \ + } + +#define SCALED_IMAGE_RANGE_X \ + auto sx = (x) * itransform->e11 + itransform->e13 - 0.49f; \ + if (sx <= -0.5f || (uint32_t)(sx + 0.5f) >= image->w) continue; \ + + +#if 0 //Enable it when GRAYSCALE image is supported +static bool _rasterCompositeScaledMaskedRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) +{ + auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; + auto sampleSize = _sampleSize(image->scale); + auto span = image->rle->spans; + int32_t miny = 0, maxy = 0; + + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + SCALED_IMAGE_RANGE_Y(span->y) + auto cmp = &surface->compositor->image.buf8[span->y * surface->compositor->image.stride + span->x]; + auto a = MULTIPLY(span->coverage, opacity); + for (uint32_t x = static_cast(span->x); x < static_cast(span->x) + span->len; ++x, ++cmp) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + if (a < 255) src = MULTIPLY(src, a); + *cmp = maskOp(src, *cmp, ~src); + } + } + return true; +} + + +static bool _rasterDirectScaledMaskedRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) +{ + auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; + auto sampleSize = _sampleSize(image->scale); + auto span = image->rle->spans; + int32_t miny = 0, maxy = 0; + + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + SCALED_IMAGE_RANGE_Y(span->y) + auto cmp = &surface->compositor->image.buf8[span->y * surface->compositor->image.stride + span->x]; + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + auto a = MULTIPLY(span->coverage, opacity); + for (uint32_t x = static_cast(span->x); x < static_cast(span->x) + span->len; ++x, ++cmp, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + if (a < 255) src = MULTIPLY(src, a); + src = maskOp(src, *cmp, 0); //not use alpha + *dst = src + MULTIPLY(*dst, ~src); + } + } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); +} +#endif + +static bool _rasterScaledMaskedRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) +{ +#if 0 //Enable it when GRAYSCALE image is supported + TVGLOG("SW_ENGINE", "Scaled Masked(%d) Rle Image", (int)surface->compositor->method); + + //8bit masking channels composition + if (surface->channelSize != sizeof(uint8_t)) return false; + + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) return _rasterDirectScaledMaskedRleImage(surface, image, itransform, region, maskOp, opacity); + else return _rasterCompositeScaledMaskedRleImage(surface, image, itransform, region, maskOp, opacity); +#endif + return false; +} + + +static bool _rasterScaledMattedRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) +{ + TVGLOG("SW_ENGINE", "Scaled Matted(%d) Rle Image", (int)surface->compositor->method); + + auto span = image->rle->spans; + auto csize = surface->compositor->image.channelSize; + auto alpha = surface->alpha(surface->compositor->method); + auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; + auto sampleSize = _sampleSize(image->scale); + int32_t miny = 0, maxy = 0; + + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + SCALED_IMAGE_RANGE_Y(span->y) + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + auto cmp = &surface->compositor->image.buf8[(span->y * surface->compositor->image.stride + span->x) * csize]; + auto a = MULTIPLY(span->coverage, opacity); + for (uint32_t x = static_cast(span->x); x < static_cast(span->x) + span->len; ++x, ++dst, cmp += csize) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + src = ALPHA_BLEND(src, (a == 255) ? alpha(cmp) : MULTIPLY(alpha(cmp), a)); + *dst = src + ALPHA_BLEND(*dst, IA(src)); + } + } + + return true; +} + + +static bool _rasterScaledBlendingRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) +{ + auto span = image->rle->spans; + auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; + auto sampleSize = _sampleSize(image->scale); + int32_t miny = 0, maxy = 0; + + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + SCALED_IMAGE_RANGE_Y(span->y) + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + auto alpha = MULTIPLY(span->coverage, opacity); + if (alpha == 255) { + for (uint32_t x = static_cast(span->x); x < static_cast(span->x) + span->len; ++x, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + auto tmp = surface->blender(src, *dst, 255); + *dst = INTERPOLATE(tmp, *dst, A(src)); + } + } else { + for (uint32_t x = static_cast(span->x); x < static_cast(span->x) + span->len; ++x, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + if (opacity < 255) src = ALPHA_BLEND(src, opacity); + auto tmp = surface->blender(src, *dst, 255); + *dst = INTERPOLATE(tmp, *dst, MULTIPLY(span->coverage, A(src))); + } + } + } + return true; +} + + +static bool _rasterScaledRleImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) +{ + auto span = image->rle->spans; + auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; + auto sampleSize = _sampleSize(image->scale); + int32_t miny = 0, maxy = 0; + + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + SCALED_IMAGE_RANGE_Y(span->y) + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + auto alpha = MULTIPLY(span->coverage, opacity); + for (uint32_t x = static_cast(span->x); x < static_cast(span->x) + span->len; ++x, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + if (alpha < 255) src = ALPHA_BLEND(src, alpha); + *dst = src + ALPHA_BLEND(*dst, IA(src)); + } + } + return true; +} + + +static bool _scaledRleImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity) +{ + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported scaled rle image!"); + return false; + } + + Matrix itransform; + + if (transform) { + if (!mathInverse(transform, &itransform)) return false; + } else mathIdentity(&itransform); + + if (_compositing(surface)) { + if (_matting(surface)) return _rasterScaledMattedRleImage(surface, image, &itransform, region, opacity); + else return _rasterScaledMaskedRleImage(surface, image, &itransform, region, opacity); + } else if (_blending(surface)) { + return _rasterScaledBlendingRleImage(surface, image, &itransform, region, opacity); + } else { + return _rasterScaledRleImage(surface, image, &itransform, region, opacity); + } + return false; +} + + +/************************************************************************/ +/* RLE Direct Image */ +/************************************************************************/ + +#if 0 //Enable it when GRAYSCALE image is supported +static bool _rasterCompositeDirectMaskedRleImage(SwSurface* surface, const SwImage* image, SwMask maskOp, uint8_t opacity) +{ + auto span = image->rle->spans; + auto cbuffer = surface->compositor->image.buf8; + auto ctride = surface->compositor->image.stride; + + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + auto src = image->buf8 + (span->y + image->oy) * image->stride + (span->x + image->ox); + auto cmp = &cbuffer[span->y * ctride + span->x]; + auto alpha = MULTIPLY(span->coverage, opacity); + if (alpha == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp) { + *cmp = maskOp(*src, *cmp, ~*src); + } + } else { + for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp) { + auto tmp = MULTIPLY(*src, alpha); + *cmp = maskOp(*src, *cmp, ~tmp); + } + } + } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); +} + + +static bool _rasterDirectDirectMaskedRleImage(SwSurface* surface, const SwImage* image, SwMask maskOp, uint8_t opacity) +{ + auto span = image->rle->spans; + auto cbuffer = surface->compositor->image.buf8; + auto ctride = surface->compositor->image.stride; + + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + auto src = image->buf8 + (span->y + image->oy) * image->stride + (span->x + image->ox); + auto cmp = &cbuffer[span->y * ctride + span->x]; + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + auto alpha = MULTIPLY(span->coverage, opacity); + if (alpha == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp, ++dst) { + auto tmp = maskOp(*src, *cmp, 0); //not use alpha + *dst = INTERPOLATE8(tmp, *dst, (255 - tmp)); + } + } else { + for (uint32_t x = 0; x < span->len; ++x, ++src, ++cmp, ++dst) { + auto tmp = maskOp(MULTIPLY(*src, alpha), *cmp, 0); //not use alpha + *dst = INTERPOLATE8(tmp, *dst, (255 - tmp)); + } + } + } + return true; +} +#endif + +static bool _rasterDirectMaskedRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) +{ +#if 0 //Enable it when GRAYSCALE image is supported + TVGLOG("SW_ENGINE", "Direct Masked(%d) Rle Image", (int)surface->compositor->method); + + //8bit masking channels composition + if (surface->channelSize != sizeof(uint8_t)) return false; + + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) _rasterDirectDirectMaskedRleImage(surface, image, maskOp, opacity); + else return _rasterCompositeDirectMaskedRleImage(surface, image, maskOp, opacity); +#endif + return false; +} + + +static bool _rasterDirectMattedRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) +{ + TVGLOG("SW_ENGINE", "Direct Matted(%d) Rle Image", (int)surface->compositor->method); + + auto span = image->rle->spans; + auto csize = surface->compositor->image.channelSize; + auto cbuffer = surface->compositor->image.buf8; + auto alpha = surface->alpha(surface->compositor->method); + + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + auto cmp = &cbuffer[(span->y * surface->compositor->image.stride + span->x) * csize]; + auto img = image->buf32 + (span->y + image->oy) * image->stride + (span->x + image->ox); + auto a = MULTIPLY(span->coverage, opacity); + if (a == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img, cmp += csize) { + auto tmp = ALPHA_BLEND(*img, alpha(cmp)); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } + } else { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img, cmp += csize) { + auto tmp = ALPHA_BLEND(*img, MULTIPLY(a, alpha(cmp))); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } + } + } + return true; +} + + +static bool _rasterDirectBlendingRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) +{ + auto span = image->rle->spans; + + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + auto img = image->buf32 + (span->y + image->oy) * image->stride + (span->x + image->ox); + auto alpha = MULTIPLY(span->coverage, opacity); + if (alpha == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { + *dst = surface->blender(*img, *dst, IA(*img)); + } + } else if (opacity == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { + auto tmp = surface->blender(*img, *dst, 255); + *dst = INTERPOLATE(tmp, *dst, MULTIPLY(span->coverage, A(*img))); + } + } else { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { + auto src = ALPHA_BLEND(*img, opacity); + auto tmp = surface->blender(src, *dst, IA(src)); + *dst = INTERPOLATE(tmp, *dst, MULTIPLY(span->coverage, A(src))); + } + } + } + return true; +} + + +static bool _rasterDirectRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) +{ + auto span = image->rle->spans; + + for (uint32_t i = 0; i < image->rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + auto img = image->buf32 + (span->y + image->oy) * image->stride + (span->x + image->ox); + auto alpha = MULTIPLY(span->coverage, opacity); + if (alpha == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { + *dst = *img + ALPHA_BLEND(*dst, IA(*img)); + } + } else { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { + auto src = ALPHA_BLEND(*img, alpha); + *dst = src + ALPHA_BLEND(*dst, IA(src)); + } + } + } + return true; +} + + +static bool _directRleImage(SwSurface* surface, const SwImage* image, uint8_t opacity) +{ + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale rle image!"); + return false; + } + + if (_compositing(surface)) { + if (_matting(surface)) return _rasterDirectMattedRleImage(surface, image, opacity); + else return _rasterDirectMaskedRleImage(surface, image, opacity); + } else if (_blending(surface)) { + return _rasterDirectBlendingRleImage(surface, image, opacity); + } else { + return _rasterDirectRleImage(surface, image, opacity); + } + return false; +} + + +/************************************************************************/ +/*Scaled Image */ +/************************************************************************/ + +#if 0 //Enable it when GRAYSCALE image is supported +static bool _rasterCompositeScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) +{ + auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; + auto sampleSize = _sampleSize(image->scale); + auto cstride = surface->compositor->image.stride; + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); + int32_t miny = 0, maxy = 0; + + for (auto y = region.min.y; y < region.max.y; ++y) { + SCALED_IMAGE_RANGE_Y(y) + auto cmp = cbuffer; + for (auto x = region.min.x; x < region.max.x; ++x, ++cmp) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + if (opacity < 255) src = MULTIPLY(src, opacity); + *cmp = maskOp(src, *cmp, ~src); + } + cbuffer += cstride; + } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); +} + + +static bool _rasterDirectScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, SwMask maskOp, uint8_t opacity) +{ + auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; + auto sampleSize = _sampleSize(image->scale); + auto cstride = surface->compositor->image.stride; + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); + auto dbuffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); + int32_t miny = 0, maxy = 0; + + for (auto y = region.min.y; y < region.max.y; ++y) { + SCALED_IMAGE_RANGE_Y(y) + auto cmp = cbuffer; + auto dst = dbuffer; + for (auto x = region.min.x; x < region.max.x; ++x, ++cmp, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf8, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + if (opacity < 255) src = MULTIPLY(src, opacity); + src = maskOp(src, *cmp, 0); //not use alpha + *dst = src + MULTIPLY(*dst, ~src); + } + cbuffer += cstride; + dbuffer += surface->stride; + } + return true; +} +#endif + +static bool _rasterScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) +{ +#if 0 //Enable it when GRAYSCALE image is supported + TVGLOG("SW_ENGINE", "Scaled Masked(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); + + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) return _rasterDirectScaledMaskedImage(surface, image, itransform, region, maskOp, opacity); + else return _rasterCompositeScaledMaskedImage(surface, image, itransform, region, maskOp, opacity); +#endif + return false; +} + + +static bool _rasterScaledMattedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) +{ + auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); + auto csize = surface->compositor->image.channelSize; + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * surface->compositor->image.stride + region.min.x) * csize; + auto alpha = surface->alpha(surface->compositor->method); + + TVGLOG("SW_ENGINE", "Scaled Matted(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); + + auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; + auto sampleSize = _sampleSize(image->scale); + int32_t miny = 0, maxy = 0; + + for (auto y = region.min.y; y < region.max.y; ++y) { + SCALED_IMAGE_RANGE_Y(y) + auto dst = dbuffer; + auto cmp = cbuffer; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst, cmp += csize) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + auto tmp = ALPHA_BLEND(src, opacity == 255 ? alpha(cmp) : MULTIPLY(opacity, alpha(cmp))); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } + dbuffer += surface->stride; + cbuffer += surface->compositor->image.stride * csize; + } + return true; +} + + +static bool _rasterScaledBlendingImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) +{ + auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); + auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; + auto sampleSize = _sampleSize(image->scale); + int32_t miny = 0, maxy = 0; + + for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride) { + SCALED_IMAGE_RANGE_Y(y) + auto dst = dbuffer; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + if (opacity < 255) ALPHA_BLEND(src, opacity); + auto tmp = surface->blender(src, *dst, 255); + *dst = INTERPOLATE(tmp, *dst, A(src)); + } + } + return true; +} + + +static bool _rasterScaledImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) +{ + auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); + auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; + auto sampleSize = _sampleSize(image->scale); + int32_t miny = 0, maxy = 0; + + for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride) { + SCALED_IMAGE_RANGE_Y(y) + auto dst = dbuffer; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + if (opacity < 255) src = ALPHA_BLEND(src, opacity); + *dst = src + ALPHA_BLEND(*dst, IA(src)); + } + } + return true; +} + + +static bool _scaledImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity) +{ + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon mesh!"); + return false; + } + + Matrix itransform; + + if (transform) { + if (!mathInverse(transform, &itransform)) return false; + } else mathIdentity(&itransform); + + if (_compositing(surface)) { + if (_matting(surface)) return _rasterScaledMattedImage(surface, image, &itransform, region, opacity); + else return _rasterScaledMaskedImage(surface, image, &itransform, region, opacity); + } else if (_blending(surface)) { + return _rasterScaledBlendingImage(surface, image, &itransform, region, opacity); + } else { + return _rasterScaledImage(surface, image, &itransform, region, opacity); + } + return false; +} + + +/************************************************************************/ +/* Direct Image */ +/************************************************************************/ + +#if 0 //Enable it when GRAYSCALE image is supported +static bool _rasterCompositeDirectMaskedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, SwMask maskOp, uint8_t opacity) +{ + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto cstride = surface->compositor->image.stride; + + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); //compositor buffer + auto sbuffer = image->buf8 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); + + for (uint32_t y = 0; y < h; ++y) { + auto cmp = cbuffer; + auto src = sbuffer; + if (opacity == 255) { + for (uint32_t x = 0; x < w; ++x, ++src, ++cmp) { + *cmp = maskOp(*src, *cmp, ~*src); + } + } else { + for (uint32_t x = 0; x < w; ++x, ++src, ++cmp) { + auto tmp = MULTIPLY(*src, opacity); + *cmp = maskOp(tmp, *cmp, ~tmp); + } + } + cbuffer += cstride; + sbuffer += image->stride; + } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); +} + + +static bool _rasterDirectDirectMaskedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, SwMask maskOp, uint8_t opacity) +{ + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto cstride = surface->compositor->image.stride; + + auto cbuffer = surface->compositor->image.buf32 + (region.min.y * cstride + region.min.x); //compositor buffer + auto dbuffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); //destination buffer + auto sbuffer = image->buf8 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); + + for (uint32_t y = 0; y < h; ++y) { + auto cmp = cbuffer; + auto dst = dbuffer; + auto src = sbuffer; + if (opacity == 255) { + for (uint32_t x = 0; x < w; ++x, ++src, ++cmp, ++dst) { + auto tmp = maskOp(*src, *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); + } + } else { + for (uint32_t x = 0; x < w; ++x, ++src, ++cmp, ++dst) { + auto tmp = maskOp(MULTIPLY(*src, opacity), *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); + } + } + cbuffer += cstride; + dbuffer += surface->stride; + sbuffer += image->stride; + } + return true; +} +#endif + +static bool _rasterDirectMaskedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) +{ + TVGERR("SW_ENGINE", "Not Supported: Direct Masked(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); + +#if 0 //Enable it when GRAYSCALE image is supported + auto maskOp = _getMaskOp(surface->compositor->method); + if (_direct(surface->compositor->method)) return _rasterDirectDirectMaskedImage(surface, image, region, maskOp, opacity); + else return _rasterCompositeDirectMaskedImage(surface, image, region, maskOp, opacity); +#endif + return false; +} + + +static bool _rasterDirectMattedImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) +{ + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto csize = surface->compositor->image.channelSize; + auto alpha = surface->alpha(surface->compositor->method); + auto sbuffer = image->buf32 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * surface->compositor->image.stride + region.min.x) * csize; //compositor buffer + + TVGLOG("SW_ENGINE", "Direct Matted(%d) Image [Region: %lu %lu %u %u]", (int)surface->compositor->method, region.min.x, region.min.y, w, h); + + //32 bits + if (surface->channelSize == sizeof(uint32_t)) { + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + for (uint32_t y = 0; y < h; ++y) { + auto dst = buffer; + auto cmp = cbuffer; + auto src = sbuffer; + if (opacity == 255) { + for (uint32_t x = 0; x < w; ++x, ++dst, ++src, cmp += csize) { + auto tmp = ALPHA_BLEND(*src, alpha(cmp)); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } + } else { + for (uint32_t x = 0; x < w; ++x, ++dst, ++src, cmp += csize) { + auto tmp = ALPHA_BLEND(*src, MULTIPLY(opacity, alpha(cmp))); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } + } + buffer += surface->stride; + cbuffer += surface->compositor->image.stride * csize; + sbuffer += image->stride; + } + //8 bits + } else if (surface->channelSize == sizeof(uint8_t)) { + auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x; + for (uint32_t y = 0; y < h; ++y) { + auto dst = buffer; + auto cmp = cbuffer; + auto src = sbuffer; + if (opacity == 255) { + for (uint32_t x = 0; x < w; ++x, ++dst, ++src, cmp += csize) { + *dst = MULTIPLY(A(*src), alpha(cmp)); + } + } else { + for (uint32_t x = 0; x < w; ++x, ++dst, ++src, cmp += csize) { + *dst = MULTIPLY(A(*src), MULTIPLY(opacity, alpha(cmp))); + } + } + buffer += surface->stride; + cbuffer += surface->compositor->image.stride * csize; + sbuffer += image->stride; + } + } + return true; +} + + +static bool _rasterDirectBlendingImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) +{ + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale image!"); + return false; + } + + auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x]; + auto sbuffer = image->buf32 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto src = sbuffer; + if (opacity == 255) { + for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) { + auto tmp = surface->blender(*src, *dst, 255); + *dst = INTERPOLATE(tmp, *dst, A(*src)); + } + } else { + for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { + auto tmp = ALPHA_BLEND(*src, opacity); + auto tmp2 = surface->blender(tmp, *dst, 255); + *dst = INTERPOLATE(tmp2, *dst, A(tmp)); + } + } + dbuffer += surface->stride; + sbuffer += image->stride; + } + return true; +} + + +static bool _rasterDirectImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) +{ + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale image!"); + return false; + } + + auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x]; + auto sbuffer = image->buf32 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox); + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto src = sbuffer; + if (opacity == 255) { + for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) { + *dst = *src + ALPHA_BLEND(*dst, IA(*src)); + } + } else { + for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { + auto tmp = ALPHA_BLEND(*src, opacity); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } + } + dbuffer += surface->stride; + sbuffer += image->stride; + } + return true; +} + + +//Blenders for the following scenarios: [Composition / Non-Composition] * [Opaque / Translucent] +static bool _directImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity) +{ + if (_compositing(surface)) { + if (_matting(surface)) return _rasterDirectMattedImage(surface, image, region, opacity); + else return _rasterDirectMaskedImage(surface, image, region, opacity); + } else if (_blending(surface)) { + return _rasterDirectBlendingImage(surface, image, region, opacity); + } else { + return _rasterDirectImage(surface, image, region, opacity); + } + return false; +} + + +//Blenders for the following scenarios: [RLE / Whole] * [Direct / Scaled / Transformed] +static bool _rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity) +{ + //RLE Image + if (image->rle) { + if (image->direct) return _directRleImage(surface, image, opacity); + else if (image->scaled) return _scaledRleImage(surface, image, transform, region, opacity); + else return _rasterTexmapPolygon(surface, image, transform, nullptr, opacity); + //Whole Image + } else { + if (image->direct) return _directImage(surface, image, region, opacity); + else if (image->scaled) return _scaledImage(surface, image, transform, region, opacity); + else return _rasterTexmapPolygon(surface, image, transform, ®ion, opacity); + } +} + + +/************************************************************************/ +/* Rect Gradient */ +/************************************************************************/ + +template +static bool _rasterCompositeGradientMaskedRect(SwSurface* surface, const SwBBox& region, const SwFill* fill, SwMask maskOp) +{ + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto cstride = surface->compositor->image.stride; + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); + + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, cbuffer, region.min.y + y, region.min.x, w, maskOp, 255); + cbuffer += surface->stride; + } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); +} + + +template +static bool _rasterDirectGradientMaskedRect(SwSurface* surface, const SwBBox& region, const SwFill* fill, SwMask maskOp) +{ + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto cstride = surface->compositor->image.stride; + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * cstride + region.min.x); + auto dbuffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); + + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, dbuffer, region.min.y + y, region.min.x, w, cbuffer, maskOp, 255); + cbuffer += cstride; + dbuffer += surface->stride; + } + return true; +} + + +template +static bool _rasterGradientMaskedRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + auto method = surface->compositor->method; + + TVGLOG("SW_ENGINE", "Masked(%d) Gradient [Region: %lu %lu %lu %lu]", (int)method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); + + auto maskOp = _getMaskOp(method); + + if (_direct(method)) return _rasterDirectGradientMaskedRect(surface, region, fill, maskOp); + else return _rasterCompositeGradientMaskedRect(surface, region, fill, maskOp); + + return false; +} + + +template +static bool _rasterGradientMattedRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto csize = surface->compositor->image.channelSize; + auto cbuffer = surface->compositor->image.buf8 + (region.min.y * surface->compositor->image.stride + region.min.x) * csize; + auto alpha = surface->alpha(surface->compositor->method); + + TVGLOG("SW_ENGINE", "Matted(%d) Gradient [Region: %lu %lu %u %u]", (int)surface->compositor->method, region.min.x, region.min.y, w, h); + + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, buffer, region.min.y + y, region.min.x, w, cbuffer, alpha, csize, 255); + buffer += surface->stride; + cbuffer += surface->stride * csize; + } + return true; +} + + +template +static bool _rasterBlendingGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + auto w = static_cast(region.max.x - region.min.x); + auto h = static_cast(region.max.y - region.min.y); + + if (fill->translucent) { + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, buffer + y * surface->stride, region.min.y + y, region.min.x, w, opBlendPreNormal, surface->blender, 255); + } + } else { + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, buffer + y * surface->stride, region.min.y + y, region.min.x, w, opBlendSrcOver, surface->blender, 255); + } + } + return true; +} + +template +static bool _rasterTranslucentGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, buffer, region.min.y + y, region.min.x, w, opBlendPreNormal, 255); + buffer += surface->stride; + } + return true; +} + + +template +static bool _rasterSolidGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + auto w = static_cast(region.max.x - region.min.x); + auto h = static_cast(region.max.y - region.min.y); + + for (uint32_t y = 0; y < h; ++y) { + fillMethod()(fill, buffer + y * surface->stride, region.min.y + y, region.min.x, w, opBlendSrcOver, 255); + } + return true; +} + + +static bool _rasterLinearGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (fill->linear.len < FLT_EPSILON) return false; + + if (_compositing(surface)) { + if (_matting(surface)) return _rasterGradientMattedRect(surface, region, fill); + else return _rasterGradientMaskedRect(surface, region, fill); + } else if (_blending(surface)) { + return _rasterBlendingGradientRect(surface, region, fill); + } else { + if (fill->translucent) return _rasterTranslucentGradientRect(surface, region, fill); + else _rasterSolidGradientRect(surface, region, fill); + } + return false; +} + + +static bool _rasterRadialGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (_compositing(surface)) { + if (_matting(surface)) return _rasterGradientMattedRect(surface, region, fill); + else return _rasterGradientMaskedRect(surface, region, fill); + } else if (_blending(surface)) { + return _rasterBlendingGradientRect(surface, region, fill); + } else { + if (fill->translucent) return _rasterTranslucentGradientRect(surface, region, fill); + else _rasterSolidGradientRect(surface, region, fill); + } + return false; +} + + +/************************************************************************/ +/* Rle Gradient */ +/************************************************************************/ + +template +static bool _rasterCompositeGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill, SwMask maskOp) +{ + auto span = rle->spans; + auto cstride = surface->compositor->image.stride; + auto cbuffer = surface->compositor->image.buf8; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto cmp = &cbuffer[span->y * cstride + span->x]; + fillMethod()(fill, cmp, span->y, span->x, span->len, maskOp, span->coverage); + } + return _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); +} + + +template +static bool _rasterDirectGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill, SwMask maskOp) +{ + auto span = rle->spans; + auto cstride = surface->compositor->image.stride; + auto cbuffer = surface->compositor->image.buf8; + auto dbuffer = surface->buf8; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto cmp = &cbuffer[span->y * cstride + span->x]; + auto dst = &dbuffer[span->y * surface->stride + span->x]; + fillMethod()(fill, dst, span->y, span->x, span->len, cmp, maskOp, span->coverage); + } + return true; +} + + +template +static bool _rasterGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + auto method = surface->compositor->method; + + TVGLOG("SW_ENGINE", "Masked(%d) Rle Linear Gradient", (int)method); + + auto maskOp = _getMaskOp(method); + + if (_direct(method)) return _rasterDirectGradientMaskedRle(surface, rle, fill, maskOp); + else return _rasterCompositeGradientMaskedRle(surface, rle, fill, maskOp); + return false; +} + + +template +static bool _rasterGradientMattedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + TVGLOG("SW_ENGINE", "Matted(%d) Rle Linear Gradient", (int)surface->compositor->method); + + auto span = rle->spans; + auto csize = surface->compositor->image.channelSize; + auto cbuffer = surface->compositor->image.buf8; + auto alpha = surface->alpha(surface->compositor->method); + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + auto cmp = &cbuffer[(span->y * surface->compositor->image.stride + span->x) * csize]; + fillMethod()(fill, dst, span->y, span->x, span->len, cmp, alpha, csize, span->coverage); + } + return true; +} + + +template +static bool _rasterBlendingGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + fillMethod()(fill, dst, span->y, span->x, span->len, opBlendPreNormal, surface->blender, span->coverage); + } + return true; +} + + +template +static bool _rasterTranslucentGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + auto span = rle->spans; + + //32 bits + if (surface->channelSize == sizeof(uint32_t)) { + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + if (span->coverage == 255) fillMethod()(fill, dst, span->y, span->x, span->len, opBlendPreNormal, 255); + else fillMethod()(fill, dst, span->y, span->x, span->len, opBlendNormal, span->coverage); + } + //8 bits + } else if (surface->channelSize == sizeof(uint8_t)) { + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskAdd, 255); + } + } + return true; +} + + +template +static bool _rasterSolidGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + auto span = rle->spans; + + //32 bits + if (surface->channelSize == sizeof(uint32_t)) { + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + if (span->coverage == 255) fillMethod()(fill, dst, span->y, span->x, span->len, opBlendSrcOver, 255); + else fillMethod()(fill, dst, span->y, span->x, span->len, opBlendInterp, span->coverage); + } + //8 bits + } else if (surface->channelSize == sizeof(uint8_t)) { + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + if (span->coverage == 255) fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskNone, 255); + else fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskAdd, span->coverage); + } + } + + return true; +} + + +static bool _rasterLinearGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (!rle) return false; + + if (_compositing(surface)) { + if (_matting(surface)) return _rasterGradientMattedRle(surface, rle, fill); + else return _rasterGradientMaskedRle(surface, rle, fill); + } else if (_blending(surface)) { + return _rasterBlendingGradientRle(surface, rle, fill); + } else { + if (fill->translucent) return _rasterTranslucentGradientRle(surface, rle, fill); + else return _rasterSolidGradientRle(surface, rle, fill); + } + return false; +} + + +static bool _rasterRadialGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (!rle) return false; + + if (_compositing(surface)) { + if (_matting(surface)) return _rasterGradientMattedRle(surface, rle, fill); + else return _rasterGradientMaskedRle(surface, rle, fill); + } else if (_blending(surface)) { + _rasterBlendingGradientRle(surface, rle, fill); + } else { + if (fill->translucent) _rasterTranslucentGradientRle(surface, rle, fill); + else return _rasterSolidGradientRle(surface, rle, fill); + } + return false; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len) +{ +#if defined(THORVG_AVX_VECTOR_SUPPORT) + avxRasterGrayscale8(dst, val, offset, len); +#elif defined(THORVG_NEON_VECTOR_SUPPORT) + neonRasterGrayscale8(dst, val, offset, len); +#else + cRasterPixels(dst, val, offset, len); +#endif +} + + +void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len) +{ +#if defined(THORVG_AVX_VECTOR_SUPPORT) + avxRasterPixel32(dst, val, offset, len); +#elif defined(THORVG_NEON_VECTOR_SUPPORT) + neonRasterPixel32(dst, val, offset, len); +#else + cRasterPixels(dst, val, offset, len); +#endif +} + + +bool rasterCompositor(SwSurface* surface) +{ + //See CompositeMethod, Alpha:3, InvAlpha:4, Luma:5, InvLuma:6 + surface->alphas[0] = _alpha; + surface->alphas[1] = _ialpha; + + if (surface->cs == ColorSpace::ABGR8888 || surface->cs == ColorSpace::ABGR8888S) { + surface->join = _abgrJoin; + surface->alphas[2] = _abgrLuma; + surface->alphas[3] = _abgrInvLuma; + } else if (surface->cs == ColorSpace::ARGB8888 || surface->cs == ColorSpace::ARGB8888S) { + surface->join = _argbJoin; + surface->alphas[2] = _argbLuma; + surface->alphas[3] = _argbInvLuma; + } else { + TVGERR("SW_ENGINE", "Unsupported Colorspace(%d) is expected!", surface->cs); + return false; + } + return true; +} + + +bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h) +{ + if (!surface || !surface->buf32 || surface->stride == 0 || surface->w == 0 || surface->h == 0) return false; + + //32 bits + if (surface->channelSize == sizeof(uint32_t)) { + //full clear + if (w == surface->stride) { + rasterPixel32(surface->buf32, 0x00000000, surface->stride * y, w * h); + //partial clear + } else { + for (uint32_t i = 0; i < h; i++) { + rasterPixel32(surface->buf32, 0x00000000, (surface->stride * y + x) + (surface->stride * i), w); + } + } + //8 bits + } else if (surface->channelSize == sizeof(uint8_t)) { + //full clear + if (w == surface->stride) { + rasterGrayscale8(surface->buf8, 0x00, surface->stride * y, w * h); + //partial clear + } else { + for (uint32_t i = 0; i < h; i++) { + rasterGrayscale8(surface->buf8, 0x00, (surface->stride * y + x) + (surface->stride * i), w); + } + } + } + return true; +} + + +void rasterUnpremultiply(Surface* surface) +{ + if (surface->channelSize != sizeof(uint32_t)) return; + + TVGLOG("SW_ENGINE", "Unpremultiply [Size: %d x %d]", surface->w, surface->h); + + //OPTIMIZE_ME: +SIMD + for (uint32_t y = 0; y < surface->h; y++) { + auto buffer = surface->buf32 + surface->stride * y; + for (uint32_t x = 0; x < surface->w; ++x) { + uint8_t a = buffer[x] >> 24; + if (a == 255) { + continue; + } else if (a == 0) { + buffer[x] = 0x00ffffff; + } else { + uint16_t r = ((buffer[x] >> 8) & 0xff00) / a; + uint16_t g = ((buffer[x]) & 0xff00) / a; + uint16_t b = ((buffer[x] << 8) & 0xff00) / a; + if (r > 0xff) r = 0xff; + if (g > 0xff) g = 0xff; + if (b > 0xff) b = 0xff; + buffer[x] = (a << 24) | (r << 16) | (g << 8) | (b); + } + } + } + surface->premultiplied = false; +} + + +void rasterPremultiply(Surface* surface) +{ + ScopedLock lock(surface->key); + if (surface->premultiplied || (surface->channelSize != sizeof(uint32_t))) return; + surface->premultiplied = true; + + TVGLOG("SW_ENGINE", "Premultiply [Size: %d x %d]", surface->w, surface->h); + + //OPTIMIZE_ME: +SIMD + auto buffer = surface->buf32; + for (uint32_t y = 0; y < surface->h; ++y, buffer += surface->stride) { + auto dst = buffer; + for (uint32_t x = 0; x < surface->w; ++x, ++dst) { + auto c = *dst; + auto a = (c >> 24); + *dst = (c & 0xff000000) + ((((c >> 8) & 0xff) * a) & 0xff00) + ((((c & 0x00ff00ff) * a) >> 8) & 0x00ff00ff); + } + } +} + + +bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id) +{ + if (!shape->fill) return false; + + if (shape->fastTrack) { + if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRect(surface, shape->bbox, shape->fill); + else if (id == TVG_CLASS_ID_RADIAL)return _rasterRadialGradientRect(surface, shape->bbox, shape->fill); + } else { + if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->rle, shape->fill); + else if (id == TVG_CLASS_ID_RADIAL) return _rasterRadialGradientRle(surface, shape->rle, shape->fill); + } + return false; +} + + +bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id) +{ + if (!shape->stroke || !shape->stroke->fill || !shape->strokeRle) return false; + + if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->strokeRle, shape->stroke->fill); + else if (id == TVG_CLASS_ID_RADIAL) return _rasterRadialGradientRle(surface, shape->strokeRle, shape->stroke->fill); + + return false; +} + + +bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (a < 255) { + r = MULTIPLY(r, a); + g = MULTIPLY(g, a); + b = MULTIPLY(b, a); + } + if (shape->fastTrack) return _rasterRect(surface, shape->bbox, r, g, b, a); + else return _rasterRle(surface, shape->rle, r, g, b, a); +} + + +bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (a < 255) { + r = MULTIPLY(r, a); + g = MULTIPLY(g, a); + b = MULTIPLY(b, a); + } + + return _rasterRle(surface, shape->strokeRle, r, g, b, a); +} + + +bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& bbox, uint8_t opacity) +{ + //Outside of the viewport, skip the rendering + if (bbox.max.x < 0 || bbox.max.y < 0 || bbox.min.x >= static_cast(surface->w) || bbox.min.y >= static_cast(surface->h)) return true; + + if (mesh && mesh->triangleCnt > 0) return _rasterTexmapPolygonMesh(surface, image, mesh, transform, &bbox, opacity); + else return _rasterImage(surface, image, transform, bbox, opacity); +} + + +bool rasterConvertCS(Surface* surface, ColorSpace to) +{ + ScopedLock lock(surface->key); + if (surface->cs == to) return true; + + //TOOD: Support SIMD accelerations + auto from = surface->cs; + + if (((from == ColorSpace::ABGR8888) || (from == ColorSpace::ABGR8888S)) && ((to == ColorSpace::ARGB8888) || (to == ColorSpace::ARGB8888S))) { + surface->cs = to; + return cRasterABGRtoARGB(surface); + } + if (((from == ColorSpace::ARGB8888) || (from == ColorSpace::ARGB8888S)) && ((to == ColorSpace::ABGR8888) || (to == ColorSpace::ABGR8888S))) { + surface->cs = to; + return cRasterARGBtoABGR(surface); + } + return false; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterAvx.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterAvx.h new file mode 100644 index 00000000000..cbaec28fa3b --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterAvx.h @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef THORVG_AVX_VECTOR_SUPPORT + +#include + +#define N_32BITS_IN_128REG 4 +#define N_32BITS_IN_256REG 8 + +static inline __m128i ALPHA_BLEND(__m128i c, __m128i a) +{ + //1. set the masks for the A/G and R/B channels + auto AG = _mm_set1_epi32(0xff00ff00); + auto RB = _mm_set1_epi32(0x00ff00ff); + + //2. mask the alpha vector - originally quartet [a, a, a, a] + auto aAG = _mm_and_si128(a, AG); + auto aRB = _mm_and_si128(a, RB); + + //3. calculate the alpha blending of the 2nd and 4th channel + //- mask the color vector + //- multiply it by the masked alpha vector + //- add the correction to compensate bit shifting used instead of dividing by 255 + //- shift bits - corresponding to division by 256 + auto even = _mm_and_si128(c, RB); + even = _mm_mullo_epi16(even, aRB); + even =_mm_add_epi16(even, RB); + even = _mm_srli_epi16(even, 8); + + //4. calculate the alpha blending of the 1st and 3rd channel: + //- mask the color vector + //- multiply it by the corresponding masked alpha vector and store the high bits of the result + //- add the correction to compensate division by 256 instead of by 255 (next step) + //- remove the low 8 bits to mimic the division by 256 + auto odd = _mm_and_si128(c, AG); + odd = _mm_mulhi_epu16(odd, aAG); + odd = _mm_add_epi16(odd, RB); + odd = _mm_and_si128(odd, AG); + + //5. the final result + return _mm_or_si128(odd, even); +} + + +static void avxRasterGrayscale8(uint8_t* dst, uint8_t val, uint32_t offset, int32_t len) +{ + dst += offset; + + __m256i vecVal = _mm256_set1_epi8(val); + + int32_t i = 0; + for (; i <= len - 32; i += 32) { + _mm256_storeu_si256((__m256i*)(dst + i), vecVal); + } + + for (; i < len; ++i) { + dst[i] = val; + } +} + + +static void avxRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len) +{ + //1. calculate how many iterations we need to cover the length + uint32_t iterations = len / N_32BITS_IN_256REG; + uint32_t avxFilled = iterations * N_32BITS_IN_256REG; + + //2. set the beginning of the array + dst += offset; + + //3. fill the octets + for (uint32_t i = 0; i < iterations; ++i, dst += N_32BITS_IN_256REG) { + _mm256_storeu_si256((__m256i*)dst, _mm256_set1_epi32(val)); + } + + //4. fill leftovers (in the first step we have to set the pointer to the place where the avx job is done) + int32_t leftovers = len - avxFilled; + while (leftovers--) *dst++ = val; +} + + +static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (surface->channelSize != sizeof(uint32_t)) { + TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize); + return false; + } + + auto color = surface->join(r, g, b, a); + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + uint32_t ialpha = 255 - a; + + auto avxColor = _mm_set1_epi32(color); + auto avxIalpha = _mm_set1_epi8(ialpha); + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + + //1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required) + auto notAligned = ((uintptr_t)dst & 0xf) / 4; + if (notAligned) { + notAligned = (N_32BITS_IN_128REG - notAligned > w ? w : N_32BITS_IN_128REG - notAligned); + for (uint32_t x = 0; x < notAligned; ++x, ++dst) { + *dst = color + ALPHA_BLEND(*dst, ialpha); + } + } + + //2. fill the aligned memory - N_32BITS_IN_128REG pixels processed at once + uint32_t iterations = (w - notAligned) / N_32BITS_IN_128REG; + uint32_t avxFilled = iterations * N_32BITS_IN_128REG; + auto avxDst = (__m128i*)dst; + for (uint32_t x = 0; x < iterations; ++x, ++avxDst) { + *avxDst = _mm_add_epi32(avxColor, ALPHA_BLEND(*avxDst, avxIalpha)); + } + + //3. fill the remaining pixels + int32_t leftovers = w - notAligned - avxFilled; + dst += avxFilled; + while (leftovers--) { + *dst = color + ALPHA_BLEND(*dst, ialpha); + dst++; + } + } + return true; +} + + +static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (surface->channelSize != sizeof(uint32_t)) { + TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize); + return false; + } + + auto color = surface->join(r, g, b, a); + auto span = rle->spans; + uint32_t src; + + for (uint32_t i = 0; i < rle->size; ++i) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + + if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage); + else src = color; + + auto ialpha = IA(src); + + //1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required) + auto notAligned = ((uintptr_t)dst & 0xf) / 4; + if (notAligned) { + notAligned = (N_32BITS_IN_128REG - notAligned > span->len ? span->len : N_32BITS_IN_128REG - notAligned); + for (uint32_t x = 0; x < notAligned; ++x, ++dst) { + *dst = src + ALPHA_BLEND(*dst, ialpha); + } + } + + //2. fill the aligned memory using avx - N_32BITS_IN_128REG pixels processed at once + //In order to avoid unneccessary avx variables declarations a check is made whether there are any iterations at all + uint32_t iterations = (span->len - notAligned) / N_32BITS_IN_128REG; + uint32_t avxFilled = 0; + if (iterations > 0) { + auto avxSrc = _mm_set1_epi32(src); + auto avxIalpha = _mm_set1_epi8(ialpha); + + avxFilled = iterations * N_32BITS_IN_128REG; + auto avxDst = (__m128i*)dst; + for (uint32_t x = 0; x < iterations; ++x, ++avxDst) { + *avxDst = _mm_add_epi32(avxSrc, ALPHA_BLEND(*avxDst, avxIalpha)); + } + } + + //3. fill the remaining pixels + int32_t leftovers = span->len - notAligned - avxFilled; + dst += avxFilled; + while (leftovers--) { + *dst = src + ALPHA_BLEND(*dst, ialpha); + dst++; + } + + ++span; + } + return true; +} + + +#endif diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterC.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterC.h new file mode 100644 index 00000000000..2b3f057ca2f --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterC.h @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +template +static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int32_t len) +{ + dst += offset; + + //fix the misaligned memory + auto alignOffset = (long long) dst % 8; + if (alignOffset > 0) { + if (sizeof(PIXEL_T) == 4) alignOffset /= 4; + else if (sizeof(PIXEL_T) == 1) alignOffset = 8 - alignOffset; + while (alignOffset > 0 && len > 0) { + *dst++ = val; + --len; + --alignOffset; + } + } + + //64bits faster clear + if ((sizeof(PIXEL_T) == 4)) { + auto val64 = (uint64_t(val) << 32) | uint64_t(val); + while (len > 1) { + *reinterpret_cast(dst) = val64; + len -= 2; + dst += 2; + } + } else if (sizeof(PIXEL_T) == 1) { + auto val32 = (uint32_t(val) << 24) | (uint32_t(val) << 16) | (uint32_t(val) << 8) | uint32_t(val); + auto val64 = (uint64_t(val32) << 32) | val32; + while (len > 7) { + *reinterpret_cast(dst) = val64; + len -= 8; + dst += 8; + } + } + + //leftovers + while (len--) *dst++ = val; +} + + +static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + auto span = rle->spans; + + //32bit channels + if (surface->channelSize == sizeof(uint32_t)) { + auto color = surface->join(r, g, b, a); + uint32_t src; + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage); + else src = color; + auto ialpha = IA(src); + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + *dst = src + ALPHA_BLEND(*dst, ialpha); + } + } + //8bit grayscale + } else if (surface->channelSize == sizeof(uint8_t)) { + uint8_t src; + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buf8[span->y * surface->stride + span->x]; + if (span->coverage < 255) src = MULTIPLY(span->coverage, a); + else src = a; + auto ialpha = ~a; + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + *dst = src + MULTIPLY(*dst, ialpha); + } + } + } + return true; +} + + +static bool inline cRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + //32bits channels + if (surface->channelSize == sizeof(uint32_t)) { + auto color = surface->join(r, g, b, a); + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + auto ialpha = 255 - a; + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + for (uint32_t x = 0; x < w; ++x, ++dst) { + *dst = color + ALPHA_BLEND(*dst, ialpha); + } + } + //8bit grayscale + } else if (surface->channelSize == sizeof(uint8_t)) { + auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x; + auto ialpha = ~a; + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + for (uint32_t x = 0; x < w; ++x, ++dst) { + *dst = a + MULTIPLY(*dst, ialpha); + } + } + } + return true; +} + + +static bool inline cRasterABGRtoARGB(Surface* surface) +{ + TVGLOG("SW_ENGINE", "Convert ColorSpace ABGR - ARGB [Size: %d x %d]", surface->w, surface->h); + + //64bits faster converting + if (surface->w % 2 == 0) { + auto buffer = reinterpret_cast(surface->buf32); + for (uint32_t y = 0; y < surface->h; ++y, buffer += surface->stride / 2) { + auto dst = buffer; + for (uint32_t x = 0; x < surface->w / 2; ++x, ++dst) { + auto c = *dst; + //flip Blue, Red channels + *dst = (c & 0xff000000ff000000) + ((c & 0x00ff000000ff0000) >> 16) + (c & 0x0000ff000000ff00) + ((c & 0x000000ff000000ff) << 16); + } + } + //default converting + } else { + auto buffer = surface->buf32; + for (uint32_t y = 0; y < surface->h; ++y, buffer += surface->stride) { + auto dst = buffer; + for (uint32_t x = 0; x < surface->w; ++x, ++dst) { + auto c = *dst; + //flip Blue, Red channels + *dst = (c & 0xff000000) + ((c & 0x00ff0000) >> 16) + (c & 0x0000ff00) + ((c & 0x000000ff) << 16); + } + } + } + return true; +} + + +static bool inline cRasterARGBtoABGR(Surface* surface) +{ + //exactly same with ABGRtoARGB + return cRasterABGRtoARGB(surface); +} \ No newline at end of file diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterNeon.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterNeon.h new file mode 100644 index 00000000000..1ea6cd96cfe --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterNeon.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef THORVG_NEON_VECTOR_SUPPORT + +#include + +//TODO : need to support windows ARM + +#if defined(__ARM_64BIT_STATE) || defined(_M_ARM64) +#define TVG_AARCH64 1 +#else +#define TVG_AARCH64 0 +#endif + + +static inline uint8x8_t ALPHA_BLEND(uint8x8_t c, uint8x8_t a) +{ + uint16x8_t t = vmull_u8(c, a); + return vshrn_n_u16(t, 8); +} + + +static void neonRasterGrayscale8(uint8_t* dst, uint8_t val, uint32_t offset, int32_t len) +{ + dst += offset; + + int32_t i = 0; + const uint8x16_t valVec = vdupq_n_u8(val); +#if TVG_AARCH64 + uint8x16x4_t valQuad = {valVec, valVec, valVec, valVec}; + for (; i <= len - 16 * 4; i += 16 * 4) { + vst1q_u8_x4(dst + i, valQuad); + } +#else + for (; i <= len - 16; i += 16) { + vst1q_u8(dst + i, valVec); + } +#endif + for (; i < len; i++) { + dst[i] = val; + } +} + + +static void neonRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len) +{ + dst += offset; + + uint32x4_t vectorVal = vdupq_n_u32(val); + +#if TVG_AARCH64 + uint32_t iterations = len / 16; + uint32_t neonFilled = iterations * 16; + uint32x4x4_t valQuad = {vectorVal, vectorVal, vectorVal, vectorVal}; + for (uint32_t i = 0; i < iterations; ++i) { + vst4q_u32(dst, valQuad); + dst += 16; + } +#else + uint32_t iterations = len / 4; + uint32_t neonFilled = iterations * 4; + for (uint32_t i = 0; i < iterations; ++i) { + vst1q_u32(dst, vectorVal); + dst += 4; + } +#endif + int32_t leftovers = len - neonFilled; + while (leftovers--) *dst++ = val; +} + + +static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (surface->channelSize != sizeof(uint32_t)) { + TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize); + return false; + } + + auto color = surface->join(r, g, b, a); + auto span = rle->spans; + uint32_t src; + uint8x8_t *vDst = nullptr; + uint16_t align; + + for (uint32_t i = 0; i < rle->size; ++i) { + if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage); + else src = color; + + auto dst = &surface->buf32[span->y * surface->stride + span->x]; + auto ialpha = IA(src); + + if ((((uintptr_t) dst) & 0x7) != 0) { + //fill not aligned byte + *dst = src + ALPHA_BLEND(*dst, ialpha); + vDst = (uint8x8_t*)(dst + 1); + align = 1; + } else { + vDst = (uint8x8_t*) dst; + align = 0; + } + + uint8x8_t vSrc = (uint8x8_t) vdup_n_u32(src); + uint8x8_t vIalpha = vdup_n_u8((uint8_t) ialpha); + + for (uint32_t x = 0; x < (span->len - align) / 2; ++x) + vDst[x] = vadd_u8(vSrc, ALPHA_BLEND(vDst[x], vIalpha)); + + auto leftovers = (span->len - align) % 2; + if (leftovers > 0) dst[span->len - 1] = src + ALPHA_BLEND(dst[span->len - 1], ialpha); + + ++span; + } + return true; +} + + +static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (surface->channelSize != sizeof(uint32_t)) { + TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize); + return false; + } + + auto color = surface->join(r, g, b, a); + auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto ialpha = 255 - a; + + auto vColor = vdup_n_u32(color); + auto vIalpha = vdup_n_u8((uint8_t) ialpha); + + uint8x8_t* vDst = nullptr; + uint32_t align; + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + + if ((((uintptr_t) dst) & 0x7) != 0) { + //fill not aligned byte + *dst = color + ALPHA_BLEND(*dst, ialpha); + vDst = (uint8x8_t*) (dst + 1); + align = 1; + } else { + vDst = (uint8x8_t*) dst; + align = 0; + } + + for (uint32_t x = 0; x < (w - align) / 2; ++x) + vDst[x] = vadd_u8((uint8x8_t)vColor, ALPHA_BLEND(vDst[x], vIalpha)); + + auto leftovers = (w - align) % 2; + if (leftovers > 0) dst[w - 1] = color + ALPHA_BLEND(dst[w - 1], ialpha); + } + return true; +} + +#endif diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterTexmap.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterTexmap.h new file mode 100644 index 00000000000..731f6984e3a --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRasterTexmap.h @@ -0,0 +1,1206 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +struct AALine +{ + int32_t x[2]; + int32_t coverage[2]; + int32_t length[2]; +}; + +struct AASpans +{ + AALine *lines; + int32_t yStart; + int32_t yEnd; +}; + +static inline void _swap(float& a, float& b, float& tmp) +{ + tmp = a; + a = b; + b = tmp; +} + + +//Careful! Shared resource, No support threading +static float dudx, dvdx; +static float dxdya, dxdyb, dudya, dvdya; +static float xa, xb, ua, va; + + +//Y Range exception handling +static bool _arrange(const SwImage* image, const SwBBox* region, int& yStart, int& yEnd) +{ + int32_t regionTop, regionBottom; + + if (region) { + regionTop = region->min.y; + regionBottom = region->max.y; + } else { + regionTop = image->rle->spans->y; + regionBottom = image->rle->spans[image->rle->size - 1].y; + } + + if (yStart >= regionBottom) return false; + + if (yStart < regionTop) yStart = regionTop; + if (yEnd > regionBottom) yEnd = regionBottom; + + return true; +} + + +static bool _rasterMaskedPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, uint8_t dirFlag = 0) +{ + return false; + +#if 0 //Enable it when GRAYSCALE image is supported + auto maskOp = _getMaskOp(surface->compositor->method); + auto direct = _direct(surface->compositor->method); + float _dudx = dudx, _dvdx = dvdx; + float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya; + float _xa = xa, _xb = xb, _ua = ua, _va = va; + auto sbuf = image->buf8; + int32_t sw = static_cast(image->stride); + int32_t sh = image->h; + int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay; + int32_t vv = 0, uu = 0; + int32_t minx = INT32_MAX, maxx = INT32_MIN; + float dx, u, v, iptr; + SwSpan* span = nullptr; //used only when rle based. + + if (!_arrange(image, region, yStart, yEnd)) return false; + + //Loop through all lines in the segment + uint32_t spanIdx = 0; + + if (region) { + minx = region->min.x; + maxx = region->max.x; + } else { + span = image->rle->spans; + while (span->y < yStart) { + ++span; + ++spanIdx; + } + } + + y = yStart; + + while (y < yEnd) { + x1 = (int32_t)_xa; + x2 = (int32_t)_xb; + + if (!region) { + minx = INT32_MAX; + maxx = INT32_MIN; + //one single row, could be consisted of multiple spans. + while (span->y == y && spanIdx < image->rle->size) { + if (minx > span->x) minx = span->x; + if (maxx < span->x + span->len) maxx = span->x + span->len; + ++span; + ++spanIdx; + } + } + if (x1 < minx) x1 = minx; + if (x2 > maxx) x2 = maxx; + + //Anti-Aliasing frames + ay = y - aaSpans->yStart; + if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1; + if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2; + + //Range allowed + if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) { + + //Perform subtexel pre-stepping on UV + dx = 1 - (_xa - x1); + u = _ua + dx * _dudx; + v = _va + dx * _dvdx; + + x = x1; + + auto cmp = &surface->compositor->image.buf8[y * surface->compositor->image.stride + x1]; + auto dst = &surface->buf8[y * surface->stride + x1]; + + if (opacity == 255) { + //Draw horizontal line + while (x++ < x2) { + uu = (int) u; + if (uu >= sw) continue; + vv = (int) v; + if (vv >= sh) continue; + + ar = (int)(255 * (1 - modff(u, &iptr))); + ab = (int)(255 * (1 - modff(v, &iptr))); + iru = uu + 1; + irv = vv + 1; + + px = *(sbuf + (vv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* right pixel */ + int px2 = *(sbuf + (vv * sw) + iru); + px = INTERPOLATE(px, px2, ar); + } + /* vertical interpolate */ + if (irv < sh) { + /* bottom pixel */ + int px2 = *(sbuf + (irv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* bottom right pixel */ + int px3 = *(sbuf + (irv * sw) + iru); + px2 = INTERPOLATE(px2, px3, ar); + } + px = INTERPOLATE(px, px2, ab); + } + if (direct) { + auto tmp = maskOp(px, *cmp, 0); //not use alpha + *dst = tmp + MULTIPLY(*dst, ~tmp); + ++dst; + } else { + *cmp = maskOp(px, *cmp, ~px); + } + ++cmp; + + //Step UV horizontally + u += _dudx; + v += _dvdx; + //range over? + if ((uint32_t)v >= image->h) break; + } + } else { + //Draw horizontal line + while (x++ < x2) { + uu = (int) u; + if (uu >= sw) continue; + vv = (int) v; + if (vv >= sh) continue; + + ar = (int)(255 * (1 - modff(u, &iptr))); + ab = (int)(255 * (1 - modff(v, &iptr))); + iru = uu + 1; + irv = vv + 1; + + px = *(sbuf + (vv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* right pixel */ + int px2 = *(sbuf + (vv * sw) + iru); + px = INTERPOLATE(px, px2, ar); + } + /* vertical interpolate */ + if (irv < sh) { + /* bottom pixel */ + int px2 = *(sbuf + (irv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* bottom right pixel */ + int px3 = *(sbuf + (irv * sw) + iru); + px2 = INTERPOLATE(px2, px3, ar); + } + px = INTERPOLATE(px, px2, ab); + } + + if (direct) { + auto tmp = maskOp(MULTIPLY(px, opacity), *cmp, 0); + *dst = tmp + MULTIPLY(*dst, ~tmp); + ++dst; + } else { + auto tmp = MULTIPLY(px, opacity); + *cmp = maskOp(tmp, *cmp, ~px); + } + ++cmp; + + //Step UV horizontally + u += _dudx; + v += _dvdx; + //range over? + if ((uint32_t)v >= image->h) break; + } + } + } + + //Step along both edges + _xa += _dxdya; + _xb += _dxdyb; + _ua += _dudya; + _va += _dvdya; + + if (!region && spanIdx >= image->rle->size) break; + + ++y; + } + xa = _xa; + xb = _xb; + ua = _ua; + va = _va; + + return true; +#endif +} + + +static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity) +{ + float _dudx = dudx, _dvdx = dvdx; + float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya; + float _xa = xa, _xb = xb, _ua = ua, _va = va; + auto sbuf = image->buf32; + auto dbuf = surface->buf32; + int32_t sw = static_cast(image->stride); + int32_t sh = image->h; + int32_t dw = surface->stride; + int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay; + int32_t vv = 0, uu = 0; + int32_t minx = INT32_MAX, maxx = INT32_MIN; + float dx, u, v, iptr; + uint32_t* buf; + SwSpan* span = nullptr; //used only when rle based. + + if (!_arrange(image, region, yStart, yEnd)) return; + + //Loop through all lines in the segment + uint32_t spanIdx = 0; + + if (region) { + minx = region->min.x; + maxx = region->max.x; + } else { + span = image->rle->spans; + while (span->y < yStart) { + ++span; + ++spanIdx; + } + } + + y = yStart; + + while (y < yEnd) { + x1 = (int32_t)_xa; + x2 = (int32_t)_xb; + + if (!region) { + minx = INT32_MAX; + maxx = INT32_MIN; + //one single row, could be consisted of multiple spans. + while (span->y == y && spanIdx < image->rle->size) { + if (minx > span->x) minx = span->x; + if (maxx < span->x + span->len) maxx = span->x + span->len; + ++span; + ++spanIdx; + } + } + if (x1 < minx) x1 = minx; + if (x2 > maxx) x2 = maxx; + + //Anti-Aliasing frames + ay = y - aaSpans->yStart; + if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1; + if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2; + + //Range allowed + if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) { + + //Perform subtexel pre-stepping on UV + dx = 1 - (_xa - x1); + u = _ua + dx * _dudx; + v = _va + dx * _dvdx; + + buf = dbuf + ((y * dw) + x1); + + x = x1; + + if (opacity == 255) { + //Draw horizontal line + while (x++ < x2) { + uu = (int) u; + if (uu >= sw) continue; + vv = (int) v; + if (vv >= sh) continue; + + ar = (int)(255 * (1 - modff(u, &iptr))); + ab = (int)(255 * (1 - modff(v, &iptr))); + iru = uu + 1; + irv = vv + 1; + + px = *(sbuf + (vv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* right pixel */ + int px2 = *(sbuf + (vv * sw) + iru); + px = INTERPOLATE(px, px2, ar); + } + /* vertical interpolate */ + if (irv < sh) { + /* bottom pixel */ + int px2 = *(sbuf + (irv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* bottom right pixel */ + int px3 = *(sbuf + (irv * sw) + iru); + px2 = INTERPOLATE(px2, px3, ar); + } + px = INTERPOLATE(px, px2, ab); + } + *buf = surface->blender(px, *buf, IA(px)); + ++buf; + + //Step UV horizontally + u += _dudx; + v += _dvdx; + //range over? + if ((uint32_t)v >= image->h) break; + } + } else { + //Draw horizontal line + while (x++ < x2) { + uu = (int) u; + if (uu >= sw) continue; + vv = (int) v; + if (vv >= sh) continue; + + ar = (int)(255 * (1 - modff(u, &iptr))); + ab = (int)(255 * (1 - modff(v, &iptr))); + iru = uu + 1; + irv = vv + 1; + + px = *(sbuf + (vv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* right pixel */ + int px2 = *(sbuf + (vv * sw) + iru); + px = INTERPOLATE(px, px2, ar); + } + /* vertical interpolate */ + if (irv < sh) { + /* bottom pixel */ + int px2 = *(sbuf + (irv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* bottom right pixel */ + int px3 = *(sbuf + (irv * sw) + iru); + px2 = INTERPOLATE(px2, px3, ar); + } + px = INTERPOLATE(px, px2, ab); + } + auto src = ALPHA_BLEND(px, opacity); + *buf = surface->blender(src, *buf, IA(src)); + ++buf; + + //Step UV horizontally + u += _dudx; + v += _dvdx; + //range over? + if ((uint32_t)v >= image->h) break; + } + } + } + + //Step along both edges + _xa += _dxdya; + _xb += _dxdyb; + _ua += _dudya; + _va += _dvdya; + + if (!region && spanIdx >= image->rle->size) break; + + ++y; + } + xa = _xa; + xb = _xb; + ua = _ua; + va = _va; +} + + +static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, bool matting) +{ + float _dudx = dudx, _dvdx = dvdx; + float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya; + float _xa = xa, _xb = xb, _ua = ua, _va = va; + auto sbuf = image->buf32; + auto dbuf = surface->buf32; + int32_t sw = static_cast(image->stride); + int32_t sh = image->h; + int32_t dw = surface->stride; + int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay; + int32_t vv = 0, uu = 0; + int32_t minx = INT32_MAX, maxx = INT32_MIN; + float dx, u, v, iptr; + uint32_t* buf; + SwSpan* span = nullptr; //used only when rle based. + + //for matting(composition) + auto csize = matting ? surface->compositor->image.channelSize: 0; + auto alpha = matting ? surface->alpha(surface->compositor->method) : nullptr; + uint8_t* cmp = nullptr; + + if (!_arrange(image, region, yStart, yEnd)) return; + + //Loop through all lines in the segment + uint32_t spanIdx = 0; + + if (region) { + minx = region->min.x; + maxx = region->max.x; + } else { + span = image->rle->spans; + while (span->y < yStart) { + ++span; + ++spanIdx; + } + } + + y = yStart; + + while (y < yEnd) { + x1 = (int32_t)_xa; + x2 = (int32_t)_xb; + + if (!region) { + minx = INT32_MAX; + maxx = INT32_MIN; + //one single row, could be consisted of multiple spans. + while (span->y == y && spanIdx < image->rle->size) { + if (minx > span->x) minx = span->x; + if (maxx < span->x + span->len) maxx = span->x + span->len; + ++span; + ++spanIdx; + } + } + if (x1 < minx) x1 = minx; + if (x2 > maxx) x2 = maxx; + + //Anti-Aliasing frames + ay = y - aaSpans->yStart; + if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1; + if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2; + + //Range allowed + if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) { + + //Perform subtexel pre-stepping on UV + dx = 1 - (_xa - x1); + u = _ua + dx * _dudx; + v = _va + dx * _dvdx; + + buf = dbuf + ((y * dw) + x1); + + x = x1; + + if (matting) cmp = &surface->compositor->image.buf8[(y * surface->compositor->image.stride + x1) * csize]; + + if (opacity == 255) { + //Draw horizontal line + while (x++ < x2) { + uu = (int) u; + if (uu >= sw) continue; + vv = (int) v; + if (vv >= sh) continue; + + ar = (int)(255.0f * (1.0f - modff(u, &iptr))); + ab = (int)(255.0f * (1.0f - modff(v, &iptr))); + iru = uu + 1; + irv = vv + 1; + + px = *(sbuf + (vv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* right pixel */ + int px2 = *(sbuf + (vv * sw) + iru); + px = INTERPOLATE(px, px2, ar); + } + /* vertical interpolate */ + if (irv < sh) { + /* bottom pixel */ + int px2 = *(sbuf + (irv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* bottom right pixel */ + int px3 = *(sbuf + (irv * sw) + iru); + px2 = INTERPOLATE(px2, px3, ar); + } + px = INTERPOLATE(px, px2, ab); + } + uint32_t src; + if (matting) { + src = ALPHA_BLEND(px, alpha(cmp)); + cmp += csize; + } else { + src = px; + } + *buf = src + ALPHA_BLEND(*buf, IA(src)); + ++buf; + + //Step UV horizontally + u += _dudx; + v += _dvdx; + //range over? + if ((uint32_t)v >= image->h) break; + } + } else { + //Draw horizontal line + while (x++ < x2) { + uu = (int) u; + vv = (int) v; + + ar = (int)(255.0f * (1.0f - modff(u, &iptr))); + ab = (int)(255.0f * (1.0f - modff(v, &iptr))); + iru = uu + 1; + irv = vv + 1; + + if (vv >= sh) continue; + + px = *(sbuf + (vv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* right pixel */ + int px2 = *(sbuf + (vv * sw) + iru); + px = INTERPOLATE(px, px2, ar); + } + /* vertical interpolate */ + if (irv < sh) { + /* bottom pixel */ + int px2 = *(sbuf + (irv * sw) + uu); + + /* horizontal interpolate */ + if (iru < sw) { + /* bottom right pixel */ + int px3 = *(sbuf + (irv * sw) + iru); + px2 = INTERPOLATE(px2, px3, ar); + } + px = INTERPOLATE(px, px2, ab); + } + uint32_t src; + if (matting) { + src = ALPHA_BLEND(px, MULTIPLY(opacity, alpha(cmp))); + cmp += csize; + } else { + src = ALPHA_BLEND(px, opacity); + } + *buf = src + ALPHA_BLEND(*buf, IA(src)); + ++buf; + + //Step UV horizontally + u += _dudx; + v += _dvdx; + //range over? + if ((uint32_t)v >= image->h) break; + } + } + } + + //Step along both edges + _xa += _dxdya; + _xb += _dxdyb; + _ua += _dudya; + _va += _dvdya; + + if (!region && spanIdx >= image->rle->size) break; + + ++y; + } + xa = _xa; + xb = _xb; + ua = _ua; + va = _va; +} + + +/* This mapping algorithm is based on Mikael Kalms's. */ +static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const SwBBox* region, Polygon& polygon, AASpans* aaSpans, uint8_t opacity) +{ + float x[3] = {polygon.vertex[0].pt.x, polygon.vertex[1].pt.x, polygon.vertex[2].pt.x}; + float y[3] = {polygon.vertex[0].pt.y, polygon.vertex[1].pt.y, polygon.vertex[2].pt.y}; + float u[3] = {polygon.vertex[0].uv.x, polygon.vertex[1].uv.x, polygon.vertex[2].uv.x}; + float v[3] = {polygon.vertex[0].uv.y, polygon.vertex[1].uv.y, polygon.vertex[2].uv.y}; + + float off_y; + float dxdy[3] = {0.0f, 0.0f, 0.0f}; + float tmp; + + auto upper = false; + + //Sort the vertices in ascending Y order + if (y[0] > y[1]) { + _swap(x[0], x[1], tmp); + _swap(y[0], y[1], tmp); + _swap(u[0], u[1], tmp); + _swap(v[0], v[1], tmp); + } + if (y[0] > y[2]) { + _swap(x[0], x[2], tmp); + _swap(y[0], y[2], tmp); + _swap(u[0], u[2], tmp); + _swap(v[0], v[2], tmp); + } + if (y[1] > y[2]) { + _swap(x[1], x[2], tmp); + _swap(y[1], y[2], tmp); + _swap(u[1], u[2], tmp); + _swap(v[1], v[2], tmp); + } + + //Y indexes + int yi[3] = {(int)y[0], (int)y[1], (int)y[2]}; + + //Skip drawing if it's too thin to cover any pixels at all. + if ((yi[0] == yi[1] && yi[0] == yi[2]) || ((int) x[0] == (int) x[1] && (int) x[0] == (int) x[2])) return; + + //Calculate horizontal and vertical increments for UV axes (these calcs are certainly not optimal, although they're stable (handles any dy being 0) + auto denom = ((x[2] - x[0]) * (y[1] - y[0]) - (x[1] - x[0]) * (y[2] - y[0])); + + //Skip poly if it's an infinitely thin line + if (mathZero(denom)) return; + + denom = 1 / denom; //Reciprocal for speeding up + dudx = ((u[2] - u[0]) * (y[1] - y[0]) - (u[1] - u[0]) * (y[2] - y[0])) * denom; + dvdx = ((v[2] - v[0]) * (y[1] - y[0]) - (v[1] - v[0]) * (y[2] - y[0])) * denom; + auto dudy = ((u[1] - u[0]) * (x[2] - x[0]) - (u[2] - u[0]) * (x[1] - x[0])) * denom; + auto dvdy = ((v[1] - v[0]) * (x[2] - x[0]) - (v[2] - v[0]) * (x[1] - x[0])) * denom; + + //Calculate X-slopes along the edges + if (y[1] > y[0]) dxdy[0] = (x[1] - x[0]) / (y[1] - y[0]); + if (y[2] > y[0]) dxdy[1] = (x[2] - x[0]) / (y[2] - y[0]); + if (y[2] > y[1]) dxdy[2] = (x[2] - x[1]) / (y[2] - y[1]); + + //Determine which side of the polygon the longer edge is on + auto side = (dxdy[1] > dxdy[0]) ? true : false; + + if (mathEqual(y[0], y[1])) side = x[0] > x[1]; + if (mathEqual(y[1], y[2])) side = x[2] > x[1]; + + auto regionTop = region ? region->min.y : image->rle->spans->y; //Normal Image or Rle Image? + auto compositing = _compositing(surface); //Composition required + auto blending = _blending(surface); //Blending required + + //Longer edge is on the left side + if (!side) { + //Calculate slopes along left edge + dxdya = dxdy[1]; + dudya = dxdya * dudx + dudy; + dvdya = dxdya * dvdx + dvdy; + + //Perform subpixel pre-stepping along left edge + auto dy = 1.0f - (y[0] - yi[0]); + xa = x[0] + dy * dxdya; + ua = u[0] + dy * dudya; + va = v[0] + dy * dvdya; + + //Draw upper segment if possibly visible + if (yi[0] < yi[1]) { + off_y = y[0] < regionTop ? (regionTop - y[0]) : 0; + xa += (off_y * dxdya); + ua += (off_y * dudya); + va += (off_y * dvdya); + + // Set right edge X-slope and perform subpixel pre-stepping + dxdyb = dxdy[0]; + xb = x[0] + dy * dxdyb + (off_y * dxdyb); + + if (compositing) { + if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, true); + else _rasterMaskedPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, 1); + } else if (blending) { + _rasterBlendingPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity); + } else { + _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, false); + } + upper = true; + } + //Draw lower segment if possibly visible + if (yi[1] < yi[2]) { + off_y = y[1] < regionTop ? (regionTop - y[1]) : 0; + if (!upper) { + xa += (off_y * dxdya); + ua += (off_y * dudya); + va += (off_y * dvdya); + } + // Set right edge X-slope and perform subpixel pre-stepping + dxdyb = dxdy[2]; + xb = x[1] + (1 - (y[1] - yi[1])) * dxdyb + (off_y * dxdyb); + if (compositing) { + if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, true); + else _rasterMaskedPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, 2); + } else if (blending) { + _rasterBlendingPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity); + } else { + _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, false); + } + } + //Longer edge is on the right side + } else { + //Set right edge X-slope and perform subpixel pre-stepping + dxdyb = dxdy[1]; + auto dy = 1.0f - (y[0] - yi[0]); + xb = x[0] + dy * dxdyb; + + //Draw upper segment if possibly visible + if (yi[0] < yi[1]) { + off_y = y[0] < regionTop ? (regionTop - y[0]) : 0; + xb += (off_y *dxdyb); + + // Set slopes along left edge and perform subpixel pre-stepping + dxdya = dxdy[0]; + dudya = dxdya * dudx + dudy; + dvdya = dxdya * dvdx + dvdy; + + xa = x[0] + dy * dxdya + (off_y * dxdya); + ua = u[0] + dy * dudya + (off_y * dudya); + va = v[0] + dy * dvdya + (off_y * dvdya); + + if (compositing) { + if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, true); + else _rasterMaskedPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, 3); + } else if (blending) { + _rasterBlendingPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity); + } else { + _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, false); + } + upper = true; + } + //Draw lower segment if possibly visible + if (yi[1] < yi[2]) { + off_y = y[1] < regionTop ? (regionTop - y[1]) : 0; + if (!upper) xb += (off_y *dxdyb); + + // Set slopes along left edge and perform subpixel pre-stepping + dxdya = dxdy[2]; + dudya = dxdya * dudx + dudy; + dvdya = dxdya * dvdx + dvdy; + dy = 1 - (y[1] - yi[1]); + xa = x[1] + dy * dxdya + (off_y * dxdya); + ua = u[1] + dy * dudya + (off_y * dudya); + va = v[1] + dy * dvdya + (off_y * dvdya); + + if (compositing) { + if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, true); + else _rasterMaskedPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, 4); + } else if (blending) { + _rasterBlendingPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity); + } else { + _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, false); + } + } + } +} + + +static AASpans* _AASpans(float ymin, float ymax, const SwImage* image, const SwBBox* region) +{ + auto yStart = static_cast(ymin); + auto yEnd = static_cast(ymax); + + if (!_arrange(image, region, yStart, yEnd)) return nullptr; + + auto aaSpans = static_cast(malloc(sizeof(AASpans))); + aaSpans->yStart = yStart; + aaSpans->yEnd = yEnd; + + //Initialize X range + auto height = yEnd - yStart; + + aaSpans->lines = static_cast(calloc(height, sizeof(AALine))); + + for (int32_t i = 0; i < height; i++) { + aaSpans->lines[i].x[0] = INT32_MAX; + aaSpans->lines[i].x[1] = INT32_MIN; + } + return aaSpans; +} + + +static void _calcIrregularCoverage(AALine* lines, int32_t eidx, int32_t y, int32_t diagonal, int32_t edgeDist, bool reverse) +{ + if (eidx == 1) reverse = !reverse; + int32_t coverage = (255 / (diagonal + 2)); + int32_t tmp; + for (int32_t ry = 0; ry < (diagonal + 2); ry++) { + tmp = y - ry - edgeDist; + if (tmp < 0) return; + lines[tmp].length[eidx] = 1; + if (reverse) lines[tmp].coverage[eidx] = 255 - (coverage * ry); + else lines[tmp].coverage[eidx] = (coverage * ry); + } +} + + +static void _calcVertCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t rewind, bool reverse) +{ + if (eidx == 1) reverse = !reverse; + int32_t coverage = (255 / (rewind + 1)); + int32_t tmp; + for (int ry = 1; ry < (rewind + 1); ry++) { + tmp = y - ry; + if (tmp < 0) return; + lines[tmp].length[eidx] = 1; + if (reverse) lines[tmp].coverage[eidx] = (255 - (coverage * ry)); + else lines[tmp].coverage[eidx] = (coverage * ry); + } +} + + +static void _calcHorizCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t x, int32_t x2) +{ + if (lines[y].length[eidx] < abs(x - x2)) { + lines[y].length[eidx] = abs(x - x2); + lines[y].coverage[eidx] = (255 / (lines[y].length[eidx] + 1)); + } +} + + +/* + * This Anti-Aliasing mechanism is originated from Hermet Park's idea. + * To understand this AA logic, you can refer this page: + * www.hermet.pe.kr/122 (hermetpark@gmail.com) +*/ +static void _calcAAEdge(AASpans *aaSpans, int32_t eidx) +{ +//Previous edge direction: +#define DirOutHor 0x0011 +#define DirOutVer 0x0001 +#define DirInHor 0x0010 +#define DirInVer 0x0000 +#define DirNone 0x1000 + +#define PUSH_VERTEX() \ + do { \ + pEdge.x = lines[y].x[eidx]; \ + pEdge.y = y; \ + ptx[0] = tx[0]; \ + ptx[1] = tx[1]; \ + } while (0) + + int32_t y = 0; + SwPoint pEdge = {-1, -1}; //previous edge point + SwPoint edgeDiff = {0, 0}; //temporary used for point distance + + /* store bigger to tx[0] between prev and current edge's x positions. */ + int32_t tx[2] = {0, 0}; + /* back up prev tx values */ + int32_t ptx[2] = {0, 0}; + int32_t diagonal = 0; //straight diagonal pixels count + + auto yStart = aaSpans->yStart; + auto yEnd = aaSpans->yEnd; + auto lines = aaSpans->lines; + + int32_t prevDir = DirNone; + int32_t curDir = DirNone; + + yEnd -= yStart; + + //Start Edge + if (y < yEnd) { + pEdge.x = lines[y].x[eidx]; + pEdge.y = y; + } + + //Calculates AA Edges + for (y++; y < yEnd; y++) { + //Ready tx + if (eidx == 0) { + tx[0] = pEdge.x; + tx[1] = lines[y].x[0]; + } else { + tx[0] = lines[y].x[1]; + tx[1] = pEdge.x; + } + edgeDiff.x = (tx[0] - tx[1]); + edgeDiff.y = (y - pEdge.y); + + //Confirm current edge direction + if (edgeDiff.x > 0) { + if (edgeDiff.y == 1) curDir = DirOutHor; + else curDir = DirOutVer; + } else if (edgeDiff.x < 0) { + if (edgeDiff.y == 1) curDir = DirInHor; + else curDir = DirInVer; + } else curDir = DirNone; + + //straight diagonal increase + if ((curDir == prevDir) && (y < yEnd)) { + if ((abs(edgeDiff.x) == 1) && (edgeDiff.y == 1)) { + ++diagonal; + PUSH_VERTEX(); + continue; + } + } + + switch (curDir) { + case DirOutHor: { + _calcHorizCoverage(lines, eidx, y, tx[0], tx[1]); + if (diagonal > 0) { + _calcIrregularCoverage(lines, eidx, y, diagonal, 0, true); + diagonal = 0; + } + /* Increment direction is changed: Outside Vertical -> Outside Horizontal */ + if (prevDir == DirOutVer) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]); + + //Trick, but fine-tunning! + if (y == 1) _calcHorizCoverage(lines, eidx, pEdge.y, tx[0], tx[1]); + PUSH_VERTEX(); + } + break; + case DirOutVer: { + _calcVertCoverage(lines, eidx, y, edgeDiff.y, true); + if (diagonal > 0) { + _calcIrregularCoverage(lines, eidx, y, diagonal, edgeDiff.y, false); + diagonal = 0; + } + /* Increment direction is changed: Outside Horizontal -> Outside Vertical */ + if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]); + PUSH_VERTEX(); + } + break; + case DirInHor: { + _calcHorizCoverage(lines, eidx, (y - 1), tx[0], tx[1]); + if (diagonal > 0) { + _calcIrregularCoverage(lines, eidx, y, diagonal, 0, false); + diagonal = 0; + } + /* Increment direction is changed: Outside Horizontal -> Inside Horizontal */ + if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]); + PUSH_VERTEX(); + } + break; + case DirInVer: { + _calcVertCoverage(lines, eidx, y, edgeDiff.y, false); + if (prevDir == DirOutHor) edgeDiff.y -= 1; //Weird, fine tuning????????????????????? + if (diagonal > 0) { + _calcIrregularCoverage(lines, eidx, y, diagonal, edgeDiff.y, true); + diagonal = 0; + } + /* Increment direction is changed: Outside Horizontal -> Inside Vertical */ + if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]); + PUSH_VERTEX(); + } + break; + } + if (curDir != DirNone) prevDir = curDir; + } + + //leftovers...? + if ((edgeDiff.y == 1) && (edgeDiff.x != 0)) { + if (y >= yEnd) y = (yEnd - 1); + _calcHorizCoverage(lines, eidx, y - 1, ptx[0], ptx[1]); + _calcHorizCoverage(lines, eidx, y, tx[0], tx[1]); + } else { + ++y; + if (y > yEnd) y = yEnd; + _calcVertCoverage(lines, eidx, y, (edgeDiff.y + 1), (prevDir & 0x00000001)); + } +} + + +static bool _apply(SwSurface* surface, AASpans* aaSpans) +{ + auto y = aaSpans->yStart; + uint32_t pixel; + uint32_t* dst; + int32_t pos; + + //left side + _calcAAEdge(aaSpans, 0); + //right side + _calcAAEdge(aaSpans, 1); + + while (y < aaSpans->yEnd) { + auto line = &aaSpans->lines[y - aaSpans->yStart]; + auto width = line->x[1] - line->x[0]; + if (width > 0) { + auto offset = y * surface->stride; + + //Left edge + dst = surface->buf32 + (offset + line->x[0]); + if (line->x[0] > 1) pixel = *(dst - 1); + else pixel = *dst; + + pos = 1; + while (pos <= line->length[0]) { + *dst = INTERPOLATE(*dst, pixel, line->coverage[0] * pos); + ++dst; + ++pos; + } + + //Right edge + dst = surface->buf32 + (offset + line->x[1] - 1); + if (line->x[1] < (int32_t)(surface->w - 1)) pixel = *(dst + 1); + else pixel = *dst; + + pos = width; + while ((int32_t)(width - line->length[1]) < pos) { + *dst = INTERPOLATE(*dst, pixel, 255 - (line->coverage[1] * (line->length[1] - (width - pos)))); + --dst; + --pos; + } + } + y++; + } + + free(aaSpans->lines); + free(aaSpans); + + return true; +} + + +/* + 2 triangles constructs 1 mesh. + below figure illustrates vert[4] index info. + If you need better quality, please divide a mesh by more number of triangles. + + 0 -- 1 + | / | + | / | + 3 -- 2 +*/ +static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox* region, uint8_t opacity) +{ + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon!"); + return false; + } + + //Exceptions: No dedicated drawing area? + if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false; + + /* Prepare vertices. + shift XY coordinates to match the sub-pixeling technique. */ + Vertex vertices[4]; + vertices[0] = {{0.0f, 0.0f}, {0.0f, 0.0f}}; + vertices[1] = {{float(image->w), 0.0f}, {float(image->w), 0.0f}}; + vertices[2] = {{float(image->w), float(image->h)}, {float(image->w), float(image->h)}}; + vertices[3] = {{0.0f, float(image->h)}, {0.0f, float(image->h)}}; + + float ys = FLT_MAX, ye = -1.0f; + for (int i = 0; i < 4; i++) { + if (transform) mathMultiply(&vertices[i].pt, transform); + if (vertices[i].pt.y < ys) ys = vertices[i].pt.y; + if (vertices[i].pt.y > ye) ye = vertices[i].pt.y; + } + + auto aaSpans = _AASpans(ys, ye, image, region); + if (!aaSpans) return true; + + Polygon polygon; + + //Draw the first polygon + polygon.vertex[0] = vertices[0]; + polygon.vertex[1] = vertices[1]; + polygon.vertex[2] = vertices[3]; + + _rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity); + + //Draw the second polygon + polygon.vertex[0] = vertices[1]; + polygon.vertex[1] = vertices[2]; + polygon.vertex[2] = vertices[3]; + + _rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity); + +#if 0 + if (_compositing(surface) && _masking(surface) && !_direct(surface->compositor->method)) { + _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); + } +#endif + return _apply(surface, aaSpans); +} + + +/* + Provide any number of triangles to draw a mesh using the supplied image. + Indexes are not used, so each triangle (Polygon) vertex has to be defined, even if they copy the previous one. + Example: + + 0 -- 1 0 -- 1 0 + | / | --> | / / | + | / | | / / | + 2 -- 3 2 1 -- 2 + + Should provide two Polygons, one for each triangle. + // TODO: region? +*/ +static bool _rasterTexmapPolygonMesh(SwSurface* surface, const SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox* region, uint8_t opacity) +{ + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon mesh!"); + return false; + } + + //Exceptions: No dedicated drawing area? + if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false; + + // Step polygons once to transform + auto transformedTris = (Polygon*)malloc(sizeof(Polygon) * mesh->triangleCnt); + float ys = FLT_MAX, ye = -1.0f; + for (uint32_t i = 0; i < mesh->triangleCnt; i++) { + transformedTris[i] = mesh->triangles[i]; + mathMultiply(&transformedTris[i].vertex[0].pt, transform); + mathMultiply(&transformedTris[i].vertex[1].pt, transform); + mathMultiply(&transformedTris[i].vertex[2].pt, transform); + + if (transformedTris[i].vertex[0].pt.y < ys) ys = transformedTris[i].vertex[0].pt.y; + else if (transformedTris[i].vertex[0].pt.y > ye) ye = transformedTris[i].vertex[0].pt.y; + if (transformedTris[i].vertex[1].pt.y < ys) ys = transformedTris[i].vertex[1].pt.y; + else if (transformedTris[i].vertex[1].pt.y > ye) ye = transformedTris[i].vertex[1].pt.y; + if (transformedTris[i].vertex[2].pt.y < ys) ys = transformedTris[i].vertex[2].pt.y; + else if (transformedTris[i].vertex[2].pt.y > ye) ye = transformedTris[i].vertex[2].pt.y; + + // Convert normalized UV coordinates to image coordinates + transformedTris[i].vertex[0].uv.x *= (float)image->w; + transformedTris[i].vertex[0].uv.y *= (float)image->h; + transformedTris[i].vertex[1].uv.x *= (float)image->w; + transformedTris[i].vertex[1].uv.y *= (float)image->h; + transformedTris[i].vertex[2].uv.x *= (float)image->w; + transformedTris[i].vertex[2].uv.y *= (float)image->h; + } + + // Get AA spans and step polygons again to draw + if (auto aaSpans = _AASpans(ys, ye, image, region)) { + for (uint32_t i = 0; i < mesh->triangleCnt; i++) { + _rasterPolygonImage(surface, image, region, transformedTris[i], aaSpans, opacity); + } +#if 0 + if (_compositing(surface) && _masking(surface) && !_direct(surface->compositor->method)) { + _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox); + } +#endif + _apply(surface, aaSpans); + } + free(transformedTris); + return true; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRenderer.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRenderer.cpp new file mode 100644 index 00000000000..d8a16fc374d --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRenderer.cpp @@ -0,0 +1,852 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" +#include "tvgSwCommon.h" +#include "tvgTaskScheduler.h" +#include "tvgSwRenderer.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ +static int32_t initEngineCnt = false; +static int32_t rendererCnt = 0; +static SwMpool* globalMpool = nullptr; +static uint32_t threadsCnt = 0; + +struct SwTask : Task +{ + SwSurface* surface = nullptr; + SwMpool* mpool = nullptr; + SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region + Matrix* transform = nullptr; + Array clips; + RenderUpdateFlag flags = RenderUpdateFlag::None; + uint8_t opacity; + bool pushed = false; //Pushed into task list? + bool disposed = false; //Disposed task? + + RenderRegion bounds() + { + //Can we skip the synchronization? + done(); + + RenderRegion region; + + //Range over? + region.x = bbox.min.x > 0 ? bbox.min.x : 0; + region.y = bbox.min.y > 0 ? bbox.min.y : 0; + region.w = bbox.max.x - region.x; + region.h = bbox.max.y - region.y; + if (region.w < 0) region.w = 0; + if (region.h < 0) region.h = 0; + + return region; + } + + virtual void dispose() = 0; + virtual bool clip(SwRleData* target) = 0; + virtual SwRleData* rle() = 0; + + virtual ~SwTask() + { + free(transform); + } +}; + + +struct SwShapeTask : SwTask +{ + SwShape shape; + const RenderShape* rshape = nullptr; + bool cmpStroking = false; + bool clipper = false; + + /* We assume that if the stroke width is greater than 2, + the shape's outline beneath the stroke could be adequately covered by the stroke drawing. + Therefore, antialiasing is disabled under this condition. + Additionally, the stroke style should not be dashed. */ + bool antialiasing(float strokeWidth) + { + return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst; + } + + float validStrokeWidth() + { + if (!rshape->stroke) return 0.0f; + + auto width = rshape->stroke->width; + if (mathZero(width)) return 0.0f; + + if (!rshape->stroke->fill && (MULTIPLY(rshape->stroke->color[3], opacity) == 0)) return 0.0f; + if (mathZero(rshape->stroke->trim.begin - rshape->stroke->trim.end)) return 0.0f; + + if (transform) return (width * sqrt(transform->e11 * transform->e11 + transform->e12 * transform->e12)); + else return width; + } + + + bool clip(SwRleData* target) override + { + if (shape.fastTrack) rleClipRect(target, &bbox); + else if (shape.rle) rleClipPath(target, shape.rle); + else return false; + + return true; + } + + SwRleData* rle() override + { + if (!shape.rle && shape.fastTrack) { + shape.rle = rleRender(&shape.bbox); + } + return shape.rle; + } + + void run(unsigned tid) override + { + if (opacity == 0 && !clipper) return; //Invisible + + auto strokeWidth = validStrokeWidth(); + bool visibleFill = false; + auto clipRegion = bbox; + + //This checks also for the case, if the invisible shape turned to visible by alpha. + auto prepareShape = false; + if (!shapePrepared(&shape) && (flags & RenderUpdateFlag::Color)) prepareShape = true; + + //Shape + if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform) || prepareShape) { + uint8_t alpha = 0; + rshape->fillColor(nullptr, nullptr, nullptr, &alpha); + alpha = MULTIPLY(alpha, opacity); + visibleFill = (alpha > 0 || rshape->fill); + if (visibleFill || clipper) { + shapeReset(&shape); + if (!shapePrepare(&shape, rshape, transform, clipRegion, bbox, mpool, tid, clips.count > 0 ? true : false)) { + visibleFill = false; + } + } + } + //Fill + if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) { + if (visibleFill || clipper) { + if (!shapeGenRle(&shape, rshape, antialiasing(strokeWidth))) goto err; + } + if (auto fill = rshape->fill) { + auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false; + if (ctable) shapeResetFill(&shape); + if (!shapeGenFillColors(&shape, fill, transform, surface, opacity, ctable)) goto err; + } else { + shapeDelFill(&shape); + } + } + //Stroke + if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { + if (strokeWidth > 0.0f) { + shapeResetStroke(&shape, rshape, transform); + if (!shapeGenStrokeRle(&shape, rshape, transform, clipRegion, bbox, mpool, tid)) goto err; + + if (auto fill = rshape->strokeFill()) { + auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false; + if (ctable) shapeResetStrokeFill(&shape); + if (!shapeGenStrokeFillColors(&shape, fill, transform, surface, opacity, ctable)) goto err; + } else { + shapeDelStrokeFill(&shape); + } + } else { + shapeDelStroke(&shape); + } + } + + //Clear current task memorypool here if the clippers would use the same memory pool + shapeDelOutline(&shape, mpool, tid); + + //Clip Path + for (auto clip = clips.begin(); clip < clips.end(); ++clip) { + auto clipper = static_cast(*clip); + //Clip shape rle + if (shape.rle && !clipper->clip(shape.rle)) goto err; + //Clip stroke rle + if (shape.strokeRle && !clipper->clip(shape.strokeRle)) goto err; + } + return; + + err: + shapeReset(&shape); + shapeDelOutline(&shape, mpool, tid); + } + + void dispose() override + { + shapeFree(&shape); + } +}; + + +struct SwSceneTask : SwTask +{ + Array scene; //list of paints render data (SwTask) + SwRleData* sceneRle = nullptr; + + bool clip(SwRleData* target) override + { + //Only one shape + if (scene.count == 1) { + return static_cast(*scene.data)->clip(target); + } + + //More than one shapes + if (sceneRle) rleClipPath(target, sceneRle); + else TVGLOG("SW_ENGINE", "No clippers in a scene?"); + + return true; + } + + SwRleData* rle() override + { + return sceneRle; + } + + void run(unsigned tid) override + { + //TODO: Skip the run if the scene hans't changed. + if (!sceneRle) sceneRle = static_cast(calloc(1, sizeof(SwRleData))); + else rleReset(sceneRle); + + //Merge shapes if it has more than one shapes + if (scene.count > 1) { + //Merge first two clippers + auto clipper1 = static_cast(*scene.data); + auto clipper2 = static_cast(*(scene.data + 1)); + + rleMerge(sceneRle, clipper1->rle(), clipper2->rle()); + + //Unify the remained clippers + for (auto rd = scene.begin() + 2; rd < scene.end(); ++rd) { + auto clipper = static_cast(*rd); + rleMerge(sceneRle, sceneRle, clipper->rle()); + } + } + } + + void dispose() override + { + rleFree(sceneRle); + } +}; + + +struct SwImageTask : SwTask +{ + SwImage image; + Surface* source; //Image source + const RenderMesh* mesh = nullptr; //Should be valid ptr in action + + bool clip(SwRleData* target) override + { + TVGERR("SW_ENGINE", "Image is used as ClipPath?"); + return true; + } + + SwRleData* rle() override + { + TVGERR("SW_ENGINE", "Image is used as Scene ClipPath?"); + return nullptr; + } + + void run(unsigned tid) override + { + auto clipRegion = bbox; + + //Convert colorspace if it's not aligned. + rasterConvertCS(source, surface->cs); + rasterPremultiply(source); + + image.data = source->data; + image.w = source->w; + image.h = source->h; + image.stride = source->stride; + image.channelSize = source->channelSize; + + //Invisible shape turned to visible by alpha. + if ((flags & (RenderUpdateFlag::Image | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) && (opacity > 0)) { + imageReset(&image); + if (!image.data || image.w == 0 || image.h == 0) goto end; + + if (!imagePrepare(&image, mesh, transform, clipRegion, bbox, mpool, tid)) goto end; + + // TODO: How do we clip the triangle mesh? Only clip non-meshed images for now + if (mesh->triangleCnt == 0 && clips.count > 0) { + if (!imageGenRle(&image, bbox, false)) goto end; + if (image.rle) { + //Clear current task memorypool here if the clippers would use the same memory pool + imageDelOutline(&image, mpool, tid); + for (auto clip = clips.begin(); clip < clips.end(); ++clip) { + auto clipper = static_cast(*clip); + if (!clipper->clip(image.rle)) goto err; + } + return; + } + } + } + goto end; + err: + rleReset(image.rle); + end: + imageDelOutline(&image, mpool, tid); + } + + void dispose() override + { + imageFree(&image); + } +}; + + +static void _termEngine() +{ + if (rendererCnt > 0) return; + + mpoolTerm(globalMpool); + globalMpool = nullptr; +} + + +static void _renderFill(SwShapeTask* task, SwSurface* surface, uint8_t opacity) +{ + uint8_t r, g, b, a; + if (auto fill = task->rshape->fill) { + rasterGradientShape(surface, &task->shape, fill->identifier()); + } else { + task->rshape->fillColor(&r, &g, &b, &a); + a = MULTIPLY(opacity, a); + if (a > 0) rasterShape(surface, &task->shape, r, g, b, a); + } +} + +static void _renderStroke(SwShapeTask* task, SwSurface* surface, uint8_t opacity) +{ + uint8_t r, g, b, a; + if (auto strokeFill = task->rshape->strokeFill()) { + rasterGradientStroke(surface, &task->shape, strokeFill->identifier()); + } else { + if (task->rshape->strokeFill(&r, &g, &b, &a)) { + a = MULTIPLY(opacity, a); + if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a); + } + } +} + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwRenderer::~SwRenderer() +{ + clearCompositors(); + + delete(surface); + + if (!sharedMpool) mpoolTerm(mpool); + + --rendererCnt; + + if (rendererCnt == 0 && initEngineCnt == 0) _termEngine(); +} + + +bool SwRenderer::clear() +{ + if (surface) return rasterClear(surface, 0, 0, surface->w, surface->h); + return false; +} + + +bool SwRenderer::sync() +{ + for (auto task = tasks.begin(); task < tasks.end(); ++task) { + if ((*task)->disposed) { + delete(*task); + } else { + (*task)->done(); + (*task)->pushed = false; + } + } + tasks.clear(); + + if (!sharedMpool) mpoolClear(mpool); + + return true; +} + + +RenderRegion SwRenderer::viewport() +{ + return vport; +} + + +bool SwRenderer::viewport(const RenderRegion& vp) +{ + vport = vp; + return true; +} + + +bool SwRenderer::target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs) +{ + if (!data || stride == 0 || w == 0 || h == 0 || w > stride) return false; + + clearCompositors(); + + if (!surface) surface = new SwSurface; + + surface->data = data; + surface->stride = stride; + surface->w = w; + surface->h = h; + surface->cs = cs; + surface->channelSize = CHANNEL_SIZE(cs); + surface->premultiplied = true; + + vport.x = vport.y = 0; + vport.w = surface->w; + vport.h = surface->h; + + return rasterCompositor(surface); +} + + +bool SwRenderer::preRender() +{ + if (surface) { + vport.x = vport.y = 0; + vport.w = surface->w; + vport.h = surface->h; + } + + return true; +} + + +void SwRenderer::clearCompositors() +{ + //Free Composite Caches + for (auto comp = compositors.begin(); comp < compositors.end(); ++comp) { + free((*comp)->compositor->image.data); + delete((*comp)->compositor); + delete(*comp); + } + compositors.reset(); +} + + +bool SwRenderer::postRender() +{ + //Unmultiply alpha if needed + if (surface->cs == ColorSpace::ABGR8888S || surface->cs == ColorSpace::ARGB8888S) { + rasterUnpremultiply(surface); + } + + for (auto task = tasks.begin(); task < tasks.end(); ++task) { + if ((*task)->disposed) delete(*task); + else (*task)->pushed = false; + } + tasks.clear(); + + return true; +} + + +bool SwRenderer::renderImage(RenderData data) +{ + auto task = static_cast(data); + task->done(); + + if (task->opacity == 0) return true; + + return rasterImage(surface, &task->image, task->mesh, task->transform, task->bbox, task->opacity); +} + + +bool SwRenderer::renderShape(RenderData data) +{ + auto task = static_cast(data); + if (!task) return false; + + task->done(); + + if (task->opacity == 0) return true; + + //Main raster stage + if (task->rshape->stroke && task->rshape->stroke->strokeFirst) { + _renderStroke(task, surface, task->opacity); + _renderFill(task, surface, task->opacity); + } else { + _renderFill(task, surface, task->opacity); + _renderStroke(task, surface, task->opacity); + } + + return true; +} + + +bool SwRenderer::blend(BlendMethod method) +{ + if (surface->blendMethod == method) return true; + surface->blendMethod = method; + + switch (method) { + case BlendMethod::Add: + surface->blender = opBlendAdd; + break; + case BlendMethod::Screen: + surface->blender = opBlendScreen; + break; + case BlendMethod::Multiply: + surface->blender = opBlendMultiply; + break; + case BlendMethod::Overlay: + surface->blender = opBlendOverlay; + break; + case BlendMethod::Difference: + surface->blender = opBlendDifference; + break; + case BlendMethod::Exclusion: + surface->blender = opBlendExclusion; + break; + case BlendMethod::SrcOver: + surface->blender = opBlendSrcOver; + break; + case BlendMethod::Darken: + surface->blender = opBlendDarken; + break; + case BlendMethod::Lighten: + surface->blender = opBlendLighten; + break; + case BlendMethod::ColorDodge: + surface->blender = opBlendColorDodge; + break; + case BlendMethod::ColorBurn: + surface->blender = opBlendColorBurn; + break; + case BlendMethod::HardLight: + surface->blender = opBlendHardLight; + break; + case BlendMethod::SoftLight: + surface->blender = opBlendSoftLight; + break; + default: + surface->blender = nullptr; + break; + } + return false; +} + + +RenderRegion SwRenderer::region(RenderData data) +{ + return static_cast(data)->bounds(); +} + + +bool SwRenderer::beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) +{ + if (!cmp) return false; + auto p = static_cast(cmp); + + p->method = method; + p->opacity = opacity; + + //Current Context? + if (p->method != CompositeMethod::None) { + surface = p->recoverSfc; + surface->compositor = p; + } + + return true; +} + + +bool SwRenderer::mempool(bool shared) +{ + if (shared == sharedMpool) return true; + + if (shared) { + if (!sharedMpool) { + if (!mpoolTerm(mpool)) return false; + mpool = globalMpool; + } + } else { + if (sharedMpool) mpool = mpoolInit(threadsCnt); + } + + sharedMpool = shared; + + if (mpool) return true; + return false; +} + + +Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs) +{ + auto x = region.x; + auto y = region.y; + auto w = region.w; + auto h = region.h; + auto sw = static_cast(surface->w); + auto sh = static_cast(surface->h); + + //Out of boundary + if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr; + + SwSurface* cmp = nullptr; + + auto reqChannelSize = CHANNEL_SIZE(cs); + + //Use cached data + for (auto p = compositors.begin(); p < compositors.end(); ++p) { + if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == reqChannelSize) { + cmp = *p; + break; + } + } + + //New Composition + if (!cmp) { + //Inherits attributes from main surface + cmp = new SwSurface(surface); + cmp->compositor = new SwCompositor; + + //TODO: We can optimize compositor surface size from (surface->stride x surface->h) to Parameter(w x h) + cmp->compositor->image.data = (pixel_t*)malloc(reqChannelSize * surface->stride * surface->h); + cmp->channelSize = cmp->compositor->image.channelSize = reqChannelSize; + + compositors.push(cmp); + } + + //Boundary Check + if (x + w > sw) w = (sw - x); + if (y + h > sh) h = (sh - y); + + cmp->compositor->recoverSfc = surface; + cmp->compositor->recoverCmp = surface->compositor; + cmp->compositor->valid = false; + cmp->compositor->bbox.min.x = x; + cmp->compositor->bbox.min.y = y; + cmp->compositor->bbox.max.x = x + w; + cmp->compositor->bbox.max.y = y + h; + cmp->compositor->image.stride = surface->stride; + cmp->compositor->image.w = surface->w; + cmp->compositor->image.h = surface->h; + cmp->compositor->image.direct = true; + + cmp->data = cmp->compositor->image.data; + cmp->w = cmp->compositor->image.w; + cmp->h = cmp->compositor->image.h; + + rasterClear(cmp, x, y, w, h); + + //Switch render target + surface = cmp; + + return cmp->compositor; +} + + +bool SwRenderer::endComposite(Compositor* cmp) +{ + if (!cmp) return false; + + auto p = static_cast(cmp); + p->valid = true; + + //Recover Context + surface = p->recoverSfc; + surface->compositor = p->recoverCmp; + + //Default is alpha blending + if (p->method == CompositeMethod::None) { + return rasterImage(surface, &p->image, nullptr, nullptr, p->bbox, p->opacity); + } + + return true; +} + + +ColorSpace SwRenderer::colorSpace() +{ + if (surface) return surface->cs; + else return ColorSpace::Unsupported; +} + + +void SwRenderer::dispose(RenderData data) +{ + auto task = static_cast(data); + if (!task) return; + task->done(); + task->dispose(); + + if (task->pushed) task->disposed = true; + else delete(task); +} + + +void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, const Array& clips, uint8_t opacity, RenderUpdateFlag flags) +{ + if (!surface) return task; + if (flags == RenderUpdateFlag::None) return task; + + //Finish previous task if it has duplicated request. + task->done(); + + //TODO: Failed threading them. It would be better if it's possible. + //See: https://github.com/thorvg/thorvg/issues/1409 + //Guarantee composition targets get ready. + for (auto clip = clips.begin(); clip < clips.end(); ++clip) { + static_cast(*clip)->done(); + } + + task->clips = clips; + + if (transform) { + if (!task->transform) task->transform = static_cast(malloc(sizeof(Matrix))); + *task->transform = transform->m; + } else { + if (task->transform) free(task->transform); + task->transform = nullptr; + } + + //zero size? + if (task->transform) { + if (task->transform->e11 == 0.0f && task->transform->e12 == 0.0f) return task; //zero width + if (task->transform->e21 == 0.0f && task->transform->e22 == 0.0f) return task; //zero height + } + + task->opacity = opacity; + task->surface = surface; + task->mpool = mpool; + task->flags = flags; + task->bbox.min.x = mathMax(static_cast(0), static_cast(vport.x)); + task->bbox.min.y = mathMax(static_cast(0), static_cast(vport.y)); + task->bbox.max.x = mathMin(static_cast(surface->w), static_cast(vport.x + vport.w)); + task->bbox.max.y = mathMin(static_cast(surface->h), static_cast(vport.y + vport.h)); + + if (!task->pushed) { + task->pushed = true; + tasks.push(task); + } + + TaskScheduler::request(task); + + return task; +} + + +RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) +{ + //prepare task + auto task = static_cast(data); + if (!task) task = new SwImageTask; + task->source = surface; + task->mesh = mesh; + return prepareCommon(task, transform, clips, opacity, flags); +} + + +RenderData SwRenderer::prepare(const Array& scene, RenderData data, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) +{ + //prepare task + auto task = static_cast(data); + if (!task) task = new SwSceneTask; + task->scene = scene; + + //TODO: Failed threading them. It would be better if it's possible. + //See: https://github.com/thorvg/thorvg/issues/1409 + //Guarantee composition targets get ready. + for (auto task = scene.begin(); task < scene.end(); ++task) { + static_cast(*task)->done(); + } + return prepareCommon(task, transform, clips, opacity, flags); +} + + +RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) +{ + //prepare task + auto task = static_cast(data); + if (!task) { + task = new SwShapeTask; + task->rshape = &rshape; + } + task->clipper = clipper; + + return prepareCommon(task, transform, clips, opacity, flags); +} + + +SwRenderer::SwRenderer():mpool(globalMpool) +{ +} + + +bool SwRenderer::init(uint32_t threads) +{ + if ((initEngineCnt++) > 0) return true; + + threadsCnt = threads; + + //Share the memory pool among the renderer + globalMpool = mpoolInit(threads); + if (!globalMpool) { + --initEngineCnt; + return false; + } + + return true; +} + + +int32_t SwRenderer::init() +{ + return initEngineCnt; +} + + +bool SwRenderer::term() +{ + if ((--initEngineCnt) > 0) return true; + + initEngineCnt = 0; + + _termEngine(); + + return true; +} + +SwRenderer* SwRenderer::gen() +{ + ++rendererCnt; + return new SwRenderer(); +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRenderer.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRenderer.h new file mode 100644 index 00000000000..02359e4a391 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRenderer.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_SW_RENDERER_H_ +#define _TVG_SW_RENDERER_H_ + +#include "tvgRender.h" + +struct SwSurface; +struct SwTask; +struct SwCompositor; +struct SwMpool; + +namespace tvg +{ + +class SwRenderer : public RenderMethod +{ +public: + RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override; + RenderData prepare(const Array& scene, RenderData data, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) override; + RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) override; + bool preRender() override; + bool renderShape(RenderData data) override; + bool renderImage(RenderData data) override; + bool postRender() override; + void dispose(RenderData data) override; + RenderRegion region(RenderData data) override; + RenderRegion viewport() override; + bool viewport(const RenderRegion& vp) override; + bool blend(BlendMethod method) override; + ColorSpace colorSpace() override; + + bool clear() override; + bool sync() override; + bool target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs); + bool mempool(bool shared); + + Compositor* target(const RenderRegion& region, ColorSpace cs) override; + bool beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) override; + bool endComposite(Compositor* cmp) override; + void clearCompositors(); + + static SwRenderer* gen(); + static bool init(uint32_t threads); + static int32_t init(); + static bool term(); + +private: + SwSurface* surface = nullptr; //active surface + Array tasks; //async task list + Array compositors; //render targets cache list + SwMpool* mpool; //private memory pool + RenderRegion vport; //viewport + bool sharedMpool = true; //memory-pool behavior policy + + SwRenderer(); + ~SwRenderer(); + + RenderData prepareCommon(SwTask* task, const RenderTransform* transform, const Array& clips, uint8_t opacity, RenderUpdateFlag flags); +}; + +} + +#endif /* _TVG_SW_RENDERER_H_ */ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRle.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRle.cpp new file mode 100644 index 00000000000..3af7e1b50cc --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwRle.cpp @@ -0,0 +1,1128 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * The FreeType Project LICENSE + * ---------------------------- + + * 2006-Jan-27 + + * Copyright 1996-2002, 2006 by + * David Turner, Robert Wilhelm, and Werner Lemberg + + + + * Introduction + * ============ + + * The FreeType Project is distributed in several archive packages; + * some of them may contain, in addition to the FreeType font engine, + * various tools and contributions which rely on, or relate to, the + * FreeType Project. + + * This license applies to all files found in such packages, and + * which do not fall under their own explicit license. The license + * affects thus the FreeType font engine, the test programs, + * documentation and makefiles, at the very least. + + * This license was inspired by the BSD, Artistic, and IJG + * (Independent JPEG Group) licenses, which all encourage inclusion + * and use of free software in commercial and freeware products + * alike. As a consequence, its main points are that: + + * o We don't promise that this software works. However, we will be + * interested in any kind of bug reports. (`as is' distribution) + + * o You can use this software for whatever you want, in parts or + * full form, without having to pay us. (`royalty-free' usage) + + * o You may not pretend that you wrote this software. If you use + * it, or only parts of it, in a program, you must acknowledge + * somewhere in your documentation that you have used the + * FreeType code. (`credits') + + * We specifically permit and encourage the inclusion of this + * software, with or without modifications, in commercial products. + * We disclaim all warranties covering The FreeType Project and + * assume no liability related to The FreeType Project. + + + * Finally, many people asked us for a preferred form for a + * credit/disclaimer to use in compliance with this license. We thus + * encourage you to use the following text: + + * """ + * Portions of this software are copyright � The FreeType + * Project (www.freetype.org). All rights reserved. + * """ + + * Please replace with the value from the FreeType version you + * actually use. + +* Legal Terms +* =========== + +* 0. Definitions +* -------------- + +* Throughout this license, the terms `package', `FreeType Project', +* and `FreeType archive' refer to the set of files originally +* distributed by the authors (David Turner, Robert Wilhelm, and +* Werner Lemberg) as the `FreeType Project', be they named as alpha, +* beta or final release. + +* `You' refers to the licensee, or person using the project, where +* `using' is a generic term including compiling the project's source +* code as well as linking it to form a `program' or `executable'. +* This program is referred to as `a program using the FreeType +* engine'. + +* This license applies to all files distributed in the original +* FreeType Project, including all source code, binaries and +* documentation, unless otherwise stated in the file in its +* original, unmodified form as distributed in the original archive. +* If you are unsure whether or not a particular file is covered by +* this license, you must contact us to verify this. + +* The FreeType Project is copyright (C) 1996-2000 by David Turner, +* Robert Wilhelm, and Werner Lemberg. All rights reserved except as +* specified below. + +* 1. No Warranty +* -------------- + +* THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY +* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +* PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS +* BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO +* USE, OF THE FREETYPE PROJECT. + +* 2. Redistribution +* ----------------- + +* This license grants a worldwide, royalty-free, perpetual and +* irrevocable right and license to use, execute, perform, compile, +* display, copy, create derivative works of, distribute and +* sublicense the FreeType Project (in both source and object code +* forms) and derivative works thereof for any purpose; and to +* authorize others to exercise some or all of the rights granted +* herein, subject to the following conditions: + +* o Redistribution of source code must retain this license file +* (`FTL.TXT') unaltered; any additions, deletions or changes to +* the original files must be clearly indicated in accompanying +* documentation. The copyright notices of the unaltered, +* original files must be preserved in all copies of source +* files. + +* o Redistribution in binary form must provide a disclaimer that +* states that the software is based in part of the work of the +* FreeType Team, in the distribution documentation. We also +* encourage you to put an URL to the FreeType web page in your +* documentation, though this isn't mandatory. + +* These conditions apply to any software derived from or based on +* the FreeType Project, not just the unmodified files. If you use +* our work, you must acknowledge us. However, no fee need be paid +* to us. + +* 3. Advertising +* -------------- + +* Neither the FreeType authors and contributors nor you shall use +* the name of the other for commercial, advertising, or promotional +* purposes without specific prior written permission. + +* We suggest, but do not require, that you use one or more of the +* following phrases to refer to this software in your documentation +* or advertising materials: `FreeType Project', `FreeType Engine', +* `FreeType library', or `FreeType Distribution'. + +* As you have not signed this license, you are not required to +* accept it. However, as the FreeType Project is copyrighted +* material, only this license, or another one contracted with the +* authors, grants you the right to use, distribute, and modify it. +* Therefore, by using, distributing, or modifying the FreeType +* Project, you indicate that you understand and accept all the terms +* of this license. + +* 4. Contacts +* ----------- + +* There are two mailing lists related to FreeType: + +* o freetype@nongnu.org + +* Discusses general use and applications of FreeType, as well as +* future and wanted additions to the library and distribution. +* If you are looking for support, start in this list if you +* haven't found anything to help you in the documentation. + +* o freetype-devel@nongnu.org + +* Discusses bugs, as well as engine internals, design issues, +* specific licenses, porting, etc. + +* Our home page can be found at + +* http://www.freetype.org +*/ + +#include +#include +#include +#include "tvgSwCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +constexpr auto MAX_SPANS = 256; +constexpr auto PIXEL_BITS = 8; //must be at least 6 bits! +constexpr auto ONE_PIXEL = (1L << PIXEL_BITS); + +using Area = long; + +struct Band +{ + SwCoord min, max; +}; + +struct Cell +{ + SwCoord x; + SwCoord cover; + Area area; + Cell *next; +}; + +struct RleWorker +{ + SwRleData* rle; + + SwPoint cellPos; + SwPoint cellMin; + SwPoint cellMax; + SwCoord cellXCnt; + SwCoord cellYCnt; + + Area area; + SwCoord cover; + + Cell* cells; + ptrdiff_t maxCells; + ptrdiff_t cellsCnt; + + SwPoint pos; + + SwPoint bezStack[32 * 3 + 1]; + int levStack[32]; + + SwOutline* outline; + + SwSpan spans[MAX_SPANS]; + int spansCnt; + int ySpan; + + int bandSize; + int bandShoot; + + jmp_buf jmpBuf; + + void* buffer; + long bufferSize; + + Cell** yCells; + SwCoord yCnt; + + bool invalid; + bool antiAlias; +}; + + +static inline SwPoint UPSCALE(const SwPoint& pt) +{ + return {SwCoord(((unsigned long) pt.x) << (PIXEL_BITS - 6)), SwCoord(((unsigned long) pt.y) << (PIXEL_BITS - 6))}; +} + + +static inline SwPoint TRUNC(const SwPoint& pt) +{ + return {pt.x >> PIXEL_BITS, pt.y >> PIXEL_BITS}; +} + + +static inline SwCoord TRUNC(const SwCoord x) +{ + return x >> PIXEL_BITS; +} + + +static inline SwPoint SUBPIXELS(const SwPoint& pt) +{ + return {SwCoord(((unsigned long) pt.x) << PIXEL_BITS), SwCoord(((unsigned long) pt.y) << PIXEL_BITS)}; +} + + +static inline SwCoord SUBPIXELS(const SwCoord x) +{ + return SwCoord(((unsigned long) x) << PIXEL_BITS); +} + +/* + * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' + * algorithm. We use alpha = 1, beta = 3/8, giving us results with a + * largest error less than 7% compared to the exact value. + */ +static inline SwCoord HYPOT(SwPoint pt) +{ + if (pt.x < 0) pt.x = -pt.x; + if (pt.y < 0) pt.y = -pt.y; + return ((pt.x > pt.y) ? (pt.x + (3 * pt.y >> 3)) : (pt.y + (3 * pt.x >> 3))); +} + +static void _genSpan(SwRleData* rle, const SwSpan* spans, uint32_t count) +{ + auto newSize = rle->size + count; + + /* allocate enough memory for new spans */ + /* alloc is required to prevent free and reallocation */ + /* when the rle needs to be regenerated because of attribute change. */ + if (rle->alloc < newSize) { + rle->alloc = (newSize * 2); + //OPTIMIZE: use mempool! + rle->spans = static_cast(realloc(rle->spans, rle->alloc * sizeof(SwSpan))); + } + + //copy the new spans to the allocated memory + SwSpan* lastSpan = rle->spans + rle->size; + memcpy(lastSpan, spans, count * sizeof(SwSpan)); + + rle->size = newSize; +} + + +static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoord acount) +{ + x += rw.cellMin.x; + y += rw.cellMin.y; + + //Clip Y range + if (y < rw.cellMin.y || y >= rw.cellMax.y) return; + + /* compute the coverage line's coverage, depending on the outline fill rule */ + /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ + auto coverage = static_cast(area >> (PIXEL_BITS * 2 + 1 - 8)); //range 0 - 255 + + if (coverage < 0) coverage = -coverage; + + if (rw.outline->fillRule == FillRule::EvenOdd) { + coverage &= 511; + if (coverage > 255) coverage = 511 - coverage; + } else { + //normal non-zero winding rule + if (coverage > 255) coverage = 255; + } + + //span has ushort coordinates. check limit overflow + if (x >= SHRT_MAX) { + TVGERR("SW_ENGINE", "X-coordiante overflow!"); + x = SHRT_MAX; + } + if (y >= SHRT_MAX) { + TVGERR("SW_ENGINE", "Y Coordiante overflow!"); + y = SHRT_MAX; + } + + if (coverage > 0) { + if (!rw.antiAlias) coverage = 255; + auto count = rw.spansCnt; + auto span = rw.spans + count - 1; + + //see whether we can add this span to the current list + if ((count > 0) && (rw.ySpan == y) && + (span->x + span->len == x) && (span->coverage == coverage)) { + + //Clip x range + SwCoord xOver = 0; + if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x); + if (x < rw.cellMin.x) xOver -= (rw.cellMin.x - x); + + //span->len += (acount + xOver) - 1; + span->len += (acount + xOver); + return; + } + + if (count >= MAX_SPANS) { + _genSpan(rw.rle, rw.spans, count); + rw.spansCnt = 0; + rw.ySpan = 0; + span = rw.spans; + } else { + ++span; + } + + //Clip x range + SwCoord xOver = 0; + if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x); + if (x < rw.cellMin.x) { + xOver -= (rw.cellMin.x - x); + x = rw.cellMin.x; + } + + //Nothing to draw + if (acount + xOver <= 0) return; + + //add a span to the current list + span->x = x; + span->y = y; + span->len = (acount + xOver); + span->coverage = coverage; + ++rw.spansCnt; + rw.ySpan = y; + } +} + + +static void _sweep(RleWorker& rw) +{ + if (rw.cellsCnt == 0) return; + + rw.spansCnt = 0; + rw.ySpan = 0; + + for (int y = 0; y < rw.yCnt; ++y) { + auto cover = 0; + auto x = 0; + auto cell = rw.yCells[y]; + + while (cell) { + if (cell->x > x && cover != 0) _horizLine(rw, x, y, cover * (ONE_PIXEL * 2), cell->x - x); + cover += cell->cover; + auto area = cover * (ONE_PIXEL * 2) - cell->area; + if (area != 0 && cell->x >= 0) _horizLine(rw, cell->x, y, area, 1); + x = cell->x + 1; + cell = cell->next; + } + + if (cover != 0) _horizLine(rw, x, y, cover * (ONE_PIXEL * 2), rw.cellXCnt - x); + } + + if (rw.spansCnt > 0) _genSpan(rw.rle, rw.spans, rw.spansCnt); +} + + +static Cell* _findCell(RleWorker& rw) +{ + auto x = rw.cellPos.x; + if (x > rw.cellXCnt) x = rw.cellXCnt; + + auto pcell = &rw.yCells[rw.cellPos.y]; + + while(true) { + Cell* cell = *pcell; + if (!cell || cell->x > x) break; + if (cell->x == x) return cell; + pcell = &cell->next; + } + + if (rw.cellsCnt >= rw.maxCells) longjmp(rw.jmpBuf, 1); + + auto cell = rw.cells + rw.cellsCnt++; + cell->x = x; + cell->area = 0; + cell->cover = 0; + cell->next = *pcell; + *pcell = cell; + + return cell; +} + + +static void _recordCell(RleWorker& rw) +{ + if (rw.area | rw.cover) { + auto cell = _findCell(rw); + cell->area += rw.area; + cell->cover += rw.cover; + } +} + + +static void _setCell(RleWorker& rw, SwPoint pos) +{ + /* Move the cell pointer to a new position. We set the `invalid' */ + /* flag to indicate that the cell isn't part of those we're interested */ + /* in during the render phase. This means that: */ + /* */ + /* . the new vertical position must be within min_ey..max_ey-1. */ + /* . the new horizontal position must be strictly less than max_ex */ + /* */ + /* Note that if a cell is to the left of the clipping region, it is */ + /* actually set to the (min_ex-1) horizontal position. */ + + /* All cells that are on the left of the clipping region go to the + min_ex - 1 horizontal position. */ + pos.x -= rw.cellMin.x; + pos.y -= rw.cellMin.y; + + if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x; + + //Are we moving to a different cell? + if (pos != rw.cellPos) { + //Record the current one if it is valid + if (!rw.invalid) _recordCell(rw); + } + + rw.area = 0; + rw.cover = 0; + rw.cellPos = pos; + rw.invalid = ((unsigned)pos.y >= (unsigned)rw.cellYCnt || pos.x >= rw.cellXCnt); +} + + +static void _startCell(RleWorker& rw, SwPoint pos) +{ + if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x; + if (pos.x < rw.cellMin.x) pos.x = rw.cellMin.x; + + rw.area = 0; + rw.cover = 0; + rw.cellPos = pos - rw.cellMin; + rw.invalid = false; + + _setCell(rw, pos); +} + + +static void _moveTo(RleWorker& rw, const SwPoint& to) +{ + //record current cell, if any */ + if (!rw.invalid) _recordCell(rw); + + //start to a new position + _startCell(rw, TRUNC(to)); + + rw.pos = to; +} + + +static void _lineTo(RleWorker& rw, const SwPoint& to) +{ +#define SW_UDIV(a, b) \ + static_cast(((unsigned long)(a) * (unsigned long)(b)) >> \ + (sizeof(long) * CHAR_BIT - PIXEL_BITS)) + + auto e1 = TRUNC(rw.pos); + auto e2 = TRUNC(to); + + //vertical clipping + if ((e1.y >= rw.cellMax.y && e2.y >= rw.cellMax.y) || (e1.y < rw.cellMin.y && e2.y < rw.cellMin.y)) { + rw.pos = to; + return; + } + + auto diff = to - rw.pos; + auto f1 = rw.pos - SUBPIXELS(e1); + SwPoint f2; + + //inside one cell + if (e1 == e2) { + ; + //any horizontal line + } else if (diff.y == 0) { + e1.x = e2.x; + _setCell(rw, e1); + } else if (diff.x == 0) { + //vertical line up + if (diff.y > 0) { + do { + f2.y = ONE_PIXEL; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * f1.x * 2; + f1.y = 0; + ++e1.y; + _setCell(rw, e1); + } while(e1.y != e2.y); + //vertical line down + } else { + do { + f2.y = 0; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * f1.x * 2; + f1.y = ONE_PIXEL; + --e1.y; + _setCell(rw, e1); + } while(e1.y != e2.y); + } + //any other line + } else { + Area prod = diff.x * f1.y - diff.y * f1.x; + + /* These macros speed up repetitive divisions by replacing them + with multiplications and right shifts. */ + auto dx_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.x); + auto dy_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.y); + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ + do { + auto px = diff.x * ONE_PIXEL; + auto py = diff.y * ONE_PIXEL; + + //left + if (prod <= 0 && prod - px > 0) { + f2 = {0, SW_UDIV(-prod, -dx_r)}; + prod -= py; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {ONE_PIXEL, f2.y}; + --e1.x; + //up + } else if (prod - px <= 0 && prod - px + py > 0) { + prod -= px; + f2 = {SW_UDIV(-prod, dy_r), ONE_PIXEL}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {f2.x, 0}; + ++e1.y; + //right + } else if (prod - px + py <= 0 && prod + py >= 0) { + prod += py; + f2 = {ONE_PIXEL, SW_UDIV(prod, dx_r)}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {0, f2.y}; + ++e1.x; + //down + } else { + f2 = {SW_UDIV(prod, -dy_r), 0}; + prod += px; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {f2.x, ONE_PIXEL}; + --e1.y; + } + + _setCell(rw, e1); + + } while(e1 != e2); + } + + f2 = {to.x - SUBPIXELS(e2.x), to.y - SUBPIXELS(e2.y)}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + rw.pos = to; +} + + +static void _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) +{ + auto arc = rw.bezStack; + arc[0] = to; + arc[1] = ctrl2; + arc[2] = ctrl1; + arc[3] = rw.pos; + + //Short-cut the arc that crosses the current band + auto min = arc[0].y; + auto max = arc[0].y; + + SwCoord y; + for (auto i = 1; i < 4; ++i) { + y = arc[i].y; + if (y < min) min = y; + if (y > max) max = y; + } + + if (TRUNC(min) >= rw.cellMax.y || TRUNC(max) < rw.cellMin.y) goto draw; + + /* Decide whether to split or draw. See `Rapid Termination */ + /* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */ + /* F. Hain, at */ + /* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf */ + while (true) { + { + //diff is the P0 - P3 chord vector + auto diff = arc[3] - arc[0]; + auto L = HYPOT(diff); + + //avoid possible arithmetic overflow below by splitting + if (L > SHRT_MAX) goto split; + + //max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1) + auto sLimit = L * (ONE_PIXEL / 6); + + auto diff1 = arc[1] - arc[0]; + auto s = diff.y * diff1.x - diff.x * diff1.y; + if (s < 0) s = -s; + if (s > sLimit) goto split; + + //s is L * the perpendicular distance from P2 to the line P0 - P3 + auto diff2 = arc[2] - arc[0]; + s = diff.y * diff2.x - diff.x * diff2.y; + if (s < 0) s = -s; + if (s > sLimit) goto split; + + /* Split super curvy segments where the off points are so far + from the chord that the angles P0-P1-P3 or P0-P2-P3 become + acute as detected by appropriate dot products */ + if (diff1.x * (diff1.x - diff.x) + diff1.y * (diff1.y - diff.y) > 0 || + diff2.x * (diff2.x - diff.x) + diff2.y * (diff2.y - diff.y) > 0) + goto split; + + //no reason to split + goto draw; + } + split: + mathSplitCubic(arc); + arc += 3; + continue; + + draw: + _lineTo(rw, arc[0]); + if (arc == rw.bezStack) return; + arc -= 3; + } +} + + +static void _decomposeOutline(RleWorker& rw) +{ + auto outline = rw.outline; + auto first = 0; //index of first point in contour + + for (auto cntr = outline->cntrs.begin(); cntr < outline->cntrs.end(); ++cntr) { + auto last = *cntr; + auto limit = outline->pts.data + last; + auto start = UPSCALE(outline->pts[first]); + auto pt = outline->pts.data + first; + auto types = outline->types.data + first; + + _moveTo(rw, UPSCALE(outline->pts[first])); + + while (pt < limit) { + ++pt; + ++types; + + //emit a single line_to + if (types[0] == SW_CURVE_TYPE_POINT) { + _lineTo(rw, UPSCALE(*pt)); + //types cubic + } else { + pt += 2; + types += 2; + + if (pt <= limit) { + _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(pt[0])); + continue; + } + _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), start); + goto close; + } + } + _lineTo(rw, start); + close: + first = last + 1; + } +} + + +static int _genRle(RleWorker& rw) +{ + if (setjmp(rw.jmpBuf) == 0) { + _decomposeOutline(rw); + if (!rw.invalid) _recordCell(rw); + return 0; + } + return -1; //lack of cell memory +} + + +static SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *target, SwSpan *outSpans, uint32_t outSpansCnt) +{ + auto out = outSpans; + auto spans = target->spans; + auto end = target->spans + target->size; + auto clipSpans = clip->spans; + auto clipEnd = clip->spans + clip->size; + + while (spans < end && clipSpans < clipEnd) { + //align y cooridnates. + if (clipSpans->y > spans->y) { + ++spans; + continue; + } + if (spans->y > clipSpans->y) { + ++clipSpans; + continue; + } + + //Try clipping with all clip spans which have a same y coordinate. + auto temp = clipSpans; + while(temp < clipEnd && outSpansCnt > 0 && temp->y == clipSpans->y) { + auto sx1 = spans->x; + auto sx2 = sx1 + spans->len; + auto cx1 = temp->x; + auto cx2 = cx1 + temp->len; + + //The span must be left(x1) to right(x2) direction. Not intersected. + if (cx2 < sx1 || sx2 < cx1) { + ++temp; + continue; + } + + //clip span region. + auto x = sx1 > cx1 ? sx1 : cx1; + auto len = (sx2 < cx2 ? sx2 : cx2) - x; + if (len > 0) { + out->x = x; + out->y = temp->y; + out->len = len; + out->coverage = (uint8_t)(((spans->coverage * temp->coverage) + 0xff) >> 8); + ++out; + --outSpansCnt; + } + ++temp; + } + ++spans; + } + return out; +} + + +static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRle, SwSpan *outSpans, uint32_t outSpansCnt) +{ + auto out = outSpans; + auto spans = targetRle->spans; + auto end = targetRle->spans + targetRle->size; + auto minx = static_cast(bbox->min.x); + auto miny = static_cast(bbox->min.y); + auto maxx = minx + static_cast(bbox->max.x - bbox->min.x) - 1; + auto maxy = miny + static_cast(bbox->max.y - bbox->min.y) - 1; + + while (outSpansCnt > 0 && spans < end) { + if (spans->y > maxy) { + spans = end; + break; + } + if (spans->y < miny || spans->x > maxx || spans->x + spans->len <= minx) { + ++spans; + continue; + } + if (spans->x < minx) { + out->len = (spans->len - (minx - spans->x)) < (maxx - minx + 1) ? (spans->len - (minx - spans->x)) : (maxx - minx + 1); + out->x = minx; + } + else { + out->x = spans->x; + out->len = spans->len < (maxx - spans->x + 1) ? spans->len : (maxx - spans->x + 1); + } + if (out->len > 0) { + out->y = spans->y; + out->coverage = spans->coverage; + ++out; + --outSpansCnt; + } + ++spans; + } + return out; +} + + +static SwSpan* _mergeSpansRegion(const SwRleData *clip1, const SwRleData *clip2, SwSpan *outSpans) +{ + auto out = outSpans; + auto spans1 = clip1->spans; + auto end1 = clip1->spans + clip1->size; + auto spans2 = clip2->spans; + auto end2 = clip2->spans + clip2->size; + + //list two spans up in y order + //TODO: Remove duplicated regions? + while (spans1 < end1 && spans2 < end2) { + while (spans1 < end1 && spans1->y <= spans2->y) { + *out = *spans1; + ++spans1; + ++out; + } + if (spans1 >= end1) break; + while (spans2 < end2 && spans2->y <= spans1->y) { + *out = *spans2; + ++spans2; + ++out; + } + } + + //Leftovers + while (spans1 < end1) { + *out = *spans1; + ++spans1; + ++out; + } + while (spans2 < end2) { + *out = *spans2; + ++spans2; + ++out; + } + + return out; +} + + +void _replaceClipSpan(SwRleData *rle, SwSpan* clippedSpans, uint32_t size) +{ + free(rle->spans); + rle->spans = clippedSpans; + rle->size = rle->alloc = size; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias) +{ + constexpr auto RENDER_POOL_SIZE = 16384L; + constexpr auto BAND_SIZE = 40; + + //TODO: We can preserve several static workers in advance + RleWorker rw; + Cell buffer[RENDER_POOL_SIZE / sizeof(Cell)]; + + //Init Cells + rw.buffer = buffer; + rw.bufferSize = sizeof(buffer); + rw.yCells = reinterpret_cast(buffer); + rw.cells = nullptr; + rw.maxCells = 0; + rw.cellsCnt = 0; + rw.area = 0; + rw.cover = 0; + rw.invalid = true; + rw.cellMin = renderRegion.min; + rw.cellMax = renderRegion.max; + rw.cellXCnt = rw.cellMax.x - rw.cellMin.x; + rw.cellYCnt = rw.cellMax.y - rw.cellMin.y; + rw.ySpan = 0; + rw.outline = const_cast(outline); + rw.bandSize = rw.bufferSize / (sizeof(Cell) * 8); //bandSize: 64 + rw.bandShoot = 0; + rw.antiAlias = antiAlias; + + if (!rle) rw.rle = reinterpret_cast(calloc(1, sizeof(SwRleData))); + else rw.rle = rle; + + //Generate RLE + Band bands[BAND_SIZE]; + Band* band; + + /* set up vertical bands */ + auto bandCnt = static_cast((rw.cellMax.y - rw.cellMin.y) / rw.bandSize); + if (bandCnt == 0) bandCnt = 1; + else if (bandCnt >= BAND_SIZE) bandCnt = (BAND_SIZE - 1); + + auto min = rw.cellMin.y; + auto yMax = rw.cellMax.y; + SwCoord max; + int ret; + + for (int n = 0; n < bandCnt; ++n, min = max) { + max = min + rw.bandSize; + if (n == bandCnt -1 || max > yMax) max = yMax; + + bands[0].min = min; + bands[0].max = max; + band = bands; + + while (band >= bands) { + rw.yCells = static_cast(rw.buffer); + rw.yCnt = band->max - band->min; + + int cellStart = sizeof(Cell*) * (int)rw.yCnt; + int cellMod = cellStart % sizeof(Cell); + + if (cellMod > 0) cellStart += sizeof(Cell) - cellMod; + + auto cellEnd = rw.bufferSize; + cellEnd -= cellEnd % sizeof(Cell); + + auto cellsMax = reinterpret_cast((char*)rw.buffer + cellEnd); + rw.cells = reinterpret_cast((char*)rw.buffer + cellStart); + + if (rw.cells >= cellsMax) goto reduce_bands; + + rw.maxCells = cellsMax - rw.cells; + if (rw.maxCells < 2) goto reduce_bands; + + for (int y = 0; y < rw.yCnt; ++y) + rw.yCells[y] = nullptr; + + rw.cellsCnt = 0; + rw.invalid = true; + rw.cellMin.y = band->min; + rw.cellMax.y = band->max; + rw.cellYCnt = band->max - band->min; + + ret = _genRle(rw); + if (ret == 0) { + _sweep(rw); + --band; + continue; + } else if (ret == 1) { + goto error; + } + + reduce_bands: + /* render pool overflow: we will reduce the render band by half */ + auto bottom = band->min; + auto top = band->max; + auto middle = bottom + ((top - bottom) >> 1); + + /* This is too complex for a single scanline; there must + be some problems */ + if (middle == bottom) goto error; + + if (bottom - top >= rw.bandSize) ++rw.bandShoot; + + band[1].min = bottom; + band[1].max = middle; + band[0].min = middle; + band[0].max = top; + ++band; + } + } + + if (rw.bandShoot > 8 && rw.bandSize > 16) + rw.bandSize = (rw.bandSize >> 1); + + return rw.rle; + +error: + free(rw.rle); + rw.rle = nullptr; + return nullptr; +} + + +SwRleData* rleRender(const SwBBox* bbox) +{ + auto width = static_cast(bbox->max.x - bbox->min.x); + auto height = static_cast(bbox->max.y - bbox->min.y); + + auto rle = static_cast(malloc(sizeof(SwRleData))); + rle->spans = static_cast(malloc(sizeof(SwSpan) * height)); + rle->size = height; + rle->alloc = height; + + auto span = rle->spans; + for (uint16_t i = 0; i < height; ++i, ++span) { + span->x = bbox->min.x; + span->y = bbox->min.y + i; + span->len = width; + span->coverage = 255; + } + + return rle; +} + + +void rleReset(SwRleData* rle) +{ + if (!rle) return; + rle->size = 0; +} + + +void rleFree(SwRleData* rle) +{ + if (!rle) return; + if (rle->spans) free(rle->spans); + free(rle); +} + + +void rleMerge(SwRleData* rle, SwRleData* clip1, SwRleData* clip2) +{ + if (!rle || (!clip1 && !clip2)) return; + if (clip1 && clip1->size == 0 && clip2 && clip2->size == 0) return; + + TVGLOG("SW_ENGINE", "Unifying Rle!"); + + //clip1 is empty, just copy clip2 + if (!clip1 || clip1->size == 0) { + if (clip2) { + auto spans = static_cast(malloc(sizeof(SwSpan) * (clip2->size))); + memcpy(spans, clip2->spans, clip2->size); + _replaceClipSpan(rle, spans, clip2->size); + } else { + _replaceClipSpan(rle, nullptr, 0); + } + return; + } + + //clip2 is empty, just copy clip1 + if (!clip2 || clip2->size == 0) { + if (clip1) { + auto spans = static_cast(malloc(sizeof(SwSpan) * (clip1->size))); + memcpy(spans, clip1->spans, clip1->size); + _replaceClipSpan(rle, spans, clip1->size); + } else { + _replaceClipSpan(rle, nullptr, 0); + } + return; + } + + auto spanCnt = clip1->size + clip2->size; + auto spans = static_cast(malloc(sizeof(SwSpan) * spanCnt)); + auto spansEnd = _mergeSpansRegion(clip1, clip2, spans); + + _replaceClipSpan(rle, spans, spansEnd - spans); +} + + +void rleClipPath(SwRleData *rle, const SwRleData *clip) +{ + if (rle->size == 0 || clip->size == 0) return; + auto spanCnt = rle->size > clip->size ? rle->size : clip->size; + auto spans = static_cast(malloc(sizeof(SwSpan) * (spanCnt))); + auto spansEnd = _intersectSpansRegion(clip, rle, spans, spanCnt); + + _replaceClipSpan(rle, spans, spansEnd - spans); + + TVGLOG("SW_ENGINE", "Using ClipPath!"); +} + + +void rleClipRect(SwRleData *rle, const SwBBox* clip) +{ + if (rle->size == 0) return; + auto spans = static_cast(malloc(sizeof(SwSpan) * (rle->size))); + auto spansEnd = _intersectSpansRect(clip, rle, spans, rle->size); + + _replaceClipSpan(rle, spans, spansEnd - spans); + + TVGLOG("SW_ENGINE", "Using ClipRect!"); +} \ No newline at end of file diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwShape.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwShape.cpp new file mode 100644 index 00000000000..e38f13db256 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwShape.cpp @@ -0,0 +1,669 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgSwCommon.h" +#include "tvgMath.h" +#include "tvgLines.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static bool _outlineBegin(SwOutline& outline) +{ + //Make a contour if lineTo/curveTo without calling close or moveTo beforehand. + if (outline.pts.empty()) return false; + outline.cntrs.push(outline.pts.count - 1); + outline.closed.push(false); + outline.pts.push(outline.pts[outline.cntrs.last()]); + outline.types.push(SW_CURVE_TYPE_POINT); + return false; +} + + +static bool _outlineEnd(SwOutline& outline) +{ + if (outline.pts.empty()) return false; + outline.cntrs.push(outline.pts.count - 1); + outline.closed.push(false); + return false; +} + + +static bool _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform, bool closed = false) +{ + //make it a contour, if the last contour is not closed yet. + if (!closed) _outlineEnd(outline); + + outline.pts.push(mathTransform(to, transform)); + outline.types.push(SW_CURVE_TYPE_POINT); + return false; +} + + +static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix* transform) +{ + outline.pts.push(mathTransform(to, transform)); + outline.types.push(SW_CURVE_TYPE_POINT); +} + + +static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform) +{ + outline.pts.push(mathTransform(ctrl1, transform)); + outline.types.push(SW_CURVE_TYPE_CUBIC); + + outline.pts.push(mathTransform(ctrl2, transform)); + outline.types.push(SW_CURVE_TYPE_CUBIC); + + outline.pts.push(mathTransform(to, transform)); + outline.types.push(SW_CURVE_TYPE_POINT); +} + + +static bool _outlineClose(SwOutline& outline) +{ + uint32_t i; + if (outline.cntrs.count > 0) i = outline.cntrs.last() + 1; + else i = 0; + + //Make sure there is at least one point in the current path + if (outline.pts.count == i) return false; + + //Close the path + outline.pts.push(outline.pts[i]); + outline.cntrs.push(outline.pts.count - 1); + outline.types.push(SW_CURVE_TYPE_POINT); + outline.closed.push(true); + + return true; +} + + +static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* transform) +{ + Line cur = {dash.ptCur, *to}; + auto len = lineLength(cur.pt1, cur.pt2); + + if (mathZero(len)) { + _outlineMoveTo(*dash.outline, &dash.ptCur, transform); + //draw the current line fully + } else if (len < dash.curLen) { + dash.curLen -= len; + if (!dash.curOpGap) { + if (dash.move) { + _outlineMoveTo(*dash.outline, &dash.ptCur, transform); + dash.move = false; + } + _outlineLineTo(*dash.outline, to, transform); + } + //draw the current line partially + } else { + while (len - dash.curLen > 0.0001f) { + Line left, right; + if (dash.curLen > 0) { + len -= dash.curLen; + lineSplitAt(cur, dash.curLen, left, right); + if (!dash.curOpGap) { + if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLT_EPSILON) { + _outlineMoveTo(*dash.outline, &left.pt1, transform); + dash.move = false; + } + _outlineLineTo(*dash.outline, &left.pt2, transform); + } + } else { + right = cur; + } + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + cur = right; + dash.ptCur = cur.pt1; + dash.move = true; + } + //leftovers + dash.curLen -= len; + if (!dash.curOpGap) { + if (dash.move) { + _outlineMoveTo(*dash.outline, &cur.pt1, transform); + dash.move = false; + } + _outlineLineTo(*dash.outline, &cur.pt2, transform); + } + if (dash.curLen < 1 && TO_SWCOORD(len) > 1) { + //move to next dash + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + } + } + dash.ptCur = *to; +} + + +static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform) +{ + Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to}; + auto len = bezLength(cur); + + //draw the current line fully + if (mathZero(len)) { + _outlineMoveTo(*dash.outline, &dash.ptCur, transform); + } else if (len < dash.curLen) { + dash.curLen -= len; + if (!dash.curOpGap) { + if (dash.move) { + _outlineMoveTo(*dash.outline, &dash.ptCur, transform); + dash.move = false; + } + _outlineCubicTo(*dash.outline, ctrl1, ctrl2, to, transform); + } + //draw the current line partially + } else { + while ((len - dash.curLen) > 0.0001f) { + Bezier left, right; + if (dash.curLen > 0) { + len -= dash.curLen; + bezSplitAt(cur, dash.curLen, left, right); + if (!dash.curOpGap) { + if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLT_EPSILON) { + _outlineMoveTo(*dash.outline, &left.start, transform); + dash.move = false; + } + _outlineCubicTo(*dash.outline, &left.ctrl1, &left.ctrl2, &left.end, transform); + } + } else { + right = cur; + } + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + cur = right; + dash.ptCur = right.start; + dash.move = true; + } + //leftovers + dash.curLen -= len; + if (!dash.curOpGap) { + if (dash.move) { + _outlineMoveTo(*dash.outline, &cur.start, transform); + dash.move = false; + } + _outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform); + } + if (dash.curLen < 1 && TO_SWCOORD(len) > 1) { + //move to next dash + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + } + } + dash.ptCur = *to; +} + + +static void _dashClose(SwDashStroke& dash, const Matrix* transform) +{ + _dashLineTo(dash, &dash.ptStart, transform); +} + + +static void _dashMoveTo(SwDashStroke& dash, const Point* pts) +{ + dash.ptCur = *pts; + dash.ptStart = *pts; + dash.move = true; +} + + +static void _dashMoveTo(SwDashStroke& dash, uint32_t offIdx, float offset, const Point* pts) +{ + dash.curIdx = offIdx % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx] - offset; + dash.curOpGap = offIdx % 2; + dash.ptStart = dash.ptCur = *pts; + dash.move = true; +} + + +static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform, float length, SwMpool* mpool, unsigned tid) +{ + const PathCommand* cmds = rshape->path.cmds.data; + auto cmdCnt = rshape->path.cmds.count; + const Point* pts = rshape->path.pts.data; + auto ptsCnt = rshape->path.pts.count; + + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) return nullptr; + + SwDashStroke dash; + auto offset = 0.0f; + auto trimmed = false; + + dash.cnt = rshape->strokeDash((const float**)&dash.pattern, &offset); + + //dash by trimming. + if (length > 0.0f && dash.cnt == 0) { + auto begin = length * rshape->stroke->trim.begin; + auto end = length * rshape->stroke->trim.end; + + //TODO: mix trimming + dash style + + //default + if (end > begin) { + if (begin > 0.0f) dash.cnt += 4; + else dash.cnt += 2; + //looping + } else dash.cnt += 3; + + dash.pattern = (float*)malloc(sizeof(float) * dash.cnt); + + if (dash.cnt == 2) { + dash.pattern[0] = end - begin; + dash.pattern[1] = length - (end - begin); + } else if (dash.cnt == 3) { + dash.pattern[0] = end; + dash.pattern[1] = (begin - end); + dash.pattern[2] = length - begin; + } else { + dash.pattern[0] = 0; //zero dash to start with a space. + dash.pattern[1] = begin; + dash.pattern[2] = end - begin; + dash.pattern[3] = length - end; + } + + trimmed = true; + //just a dasy style. + } else { + if (dash.cnt == 0) return nullptr; + } + + //offset? + auto patternLength = 0.0f; + uint32_t offIdx = 0; + if (!mathZero(offset)) { + for (size_t i = 0; i < dash.cnt; ++i) patternLength += dash.pattern[i]; + bool isOdd = dash.cnt % 2; + if (isOdd) patternLength *= 2; + + offset = fmodf(offset, patternLength); + if (offset < 0) offset += patternLength; + + for (size_t i = 0; i < dash.cnt * (1 + (size_t)isOdd); ++i, ++offIdx) { + auto curPattern = dash.pattern[i % dash.cnt]; + if (offset < curPattern) break; + offset -= curPattern; + } + } + + dash.outline = mpoolReqDashOutline(mpool, tid); + + //must begin with moveTo + if (cmds[0] == PathCommand::MoveTo) { + _dashMoveTo(dash, offIdx, offset, pts); + cmds++; + pts++; + } + + while (--cmdCnt > 0) { + switch (*cmds) { + case PathCommand::Close: { + _dashClose(dash, transform); + break; + } + case PathCommand::MoveTo: { + if (rshape->stroke->trim.individual) _dashMoveTo(dash, pts); + else _dashMoveTo(dash, offIdx, offset, pts); + ++pts; + break; + } + case PathCommand::LineTo: { + _dashLineTo(dash, pts, transform); + ++pts; + break; + } + case PathCommand::CubicTo: { + _dashCubicTo(dash, pts, pts + 1, pts + 2, transform); + pts += 3; + break; + } + } + ++cmds; + } + + _outlineEnd(*dash.outline); + + if (trimmed) free(dash.pattern); + + return dash.outline; +} + + +static float _outlineLength(const RenderShape* rshape) +{ + const PathCommand* cmds = rshape->path.cmds.data; + auto cmdCnt = rshape->path.cmds.count; + const Point* pts = rshape->path.pts.data; + auto ptsCnt = rshape->path.pts.count; + + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) return 0.0f; + + const Point* close = nullptr; + auto length = 0.0f; + auto slength = -1.0f; + auto simultaneous = !rshape->stroke->trim.individual; + + //Compute the whole length + while (cmdCnt-- > 0) { + switch (*cmds) { + case PathCommand::Close: { + length += mathLength(pts - 1, close); + //retrieve the max length of the shape if the simultaneous mode. + if (simultaneous) { + if (slength < length) slength = length; + length = 0.0f; + } + break; + } + case PathCommand::MoveTo: { + close = pts; + ++pts; + break; + } + case PathCommand::LineTo: { + length += mathLength(pts - 1, pts); + ++pts; + break; + } + case PathCommand::CubicTo: { + length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)}); + pts += 3; + break; + } + } + ++cmds; + } + if (simultaneous && slength > length) return slength; + else return length; +} + + +static bool _axisAlignedRect(const SwOutline* outline) +{ + //Fast Track: axis-aligned rectangle? + if (outline->pts.count != 5) return false; + + auto pt1 = outline->pts.data + 0; + auto pt2 = outline->pts.data + 1; + auto pt3 = outline->pts.data + 2; + auto pt4 = outline->pts.data + 3; + + auto a = SwPoint{pt1->x, pt3->y}; + auto b = SwPoint{pt3->x, pt1->y}; + + if ((*pt2 == a && *pt4 == b) || (*pt2 == b && *pt4 == a)) return true; + + return false; +} + + +static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix* transform, SwMpool* mpool, unsigned tid, bool hasComposite) +{ + const PathCommand* cmds = rshape->path.cmds.data; + auto cmdCnt = rshape->path.cmds.count; + const Point* pts = rshape->path.pts.data; + auto ptsCnt = rshape->path.pts.count; + + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) return false; + + shape->outline = mpoolReqOutline(mpool, tid); + auto outline = shape->outline; + auto closed = false; + + //Generate Outlines + while (cmdCnt-- > 0) { + switch (*cmds) { + case PathCommand::Close: { + if (!closed) closed = _outlineClose(*outline); + break; + } + case PathCommand::MoveTo: { + closed = _outlineMoveTo(*outline, pts, transform, closed); + ++pts; + break; + } + case PathCommand::LineTo: { + if (closed) closed = _outlineBegin(*outline); + _outlineLineTo(*outline, pts, transform); + ++pts; + break; + } + case PathCommand::CubicTo: { + if (closed) closed = _outlineBegin(*outline); + _outlineCubicTo(*outline, pts, pts + 1, pts + 2, transform); + pts += 3; + break; + } + } + ++cmds; + } + + if (!closed) _outlineEnd(*outline); + + outline->fillRule = rshape->rule; + shape->outline = outline; + + shape->fastTrack = (!hasComposite && _axisAlignedRect(shape->outline)); + return true; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite) +{ + if (!_genOutline(shape, rshape, transform, mpool, tid, hasComposite)) return false; + if (!mathUpdateOutlineBBox(shape->outline, clipRegion, renderRegion, shape->fastTrack)) return false; + + //Keep it for Rasterization Region + shape->bbox = renderRegion; + + //Check valid region + if (renderRegion.max.x - renderRegion.min.x < 1 && renderRegion.max.y - renderRegion.min.y < 1) return false; + + //Check boundary + if (renderRegion.min.x >= clipRegion.max.x || renderRegion.min.y >= clipRegion.max.y || + renderRegion.max.x <= clipRegion.min.x || renderRegion.max.y <= clipRegion.min.y) return false; + + return true; +} + + +bool shapePrepared(const SwShape* shape) +{ + return shape->rle ? true : false; +} + + +bool shapeGenRle(SwShape* shape, TVG_UNUSED const RenderShape* rshape, bool antiAlias) +{ + //FIXME: Should we draw it? + //Case: Stroke Line + //if (shape.outline->opened) return true; + + //Case A: Fast Track Rectangle Drawing + if (shape->fastTrack) return true; + + //Case B: Normal Shape RLE Drawing + if ((shape->rle = rleRender(shape->rle, shape->outline, shape->bbox, antiAlias))) return true; + + return false; +} + + +void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid) +{ + mpoolRetOutline(mpool, tid); + shape->outline = nullptr; +} + + +void shapeReset(SwShape* shape) +{ + rleReset(shape->rle); + rleReset(shape->strokeRle); + shape->fastTrack = false; + shape->bbox.reset(); +} + + +void shapeFree(SwShape* shape) +{ + rleFree(shape->rle); + shape->rle = nullptr; + + shapeDelFill(shape); + + if (shape->stroke) { + rleFree(shape->strokeRle); + shape->strokeRle = nullptr; + strokeFree(shape->stroke); + shape->stroke = nullptr; + } +} + + +void shapeDelStroke(SwShape* shape) +{ + if (!shape->stroke) return; + rleFree(shape->strokeRle); + shape->strokeRle = nullptr; + strokeFree(shape->stroke); + shape->stroke = nullptr; +} + + +void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* transform) +{ + if (!shape->stroke) shape->stroke = static_cast(calloc(1, sizeof(SwStroke))); + auto stroke = shape->stroke; + if (!stroke) return; + + strokeReset(stroke, rshape, transform); + rleReset(shape->strokeRle); +} + + +bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid) +{ + SwOutline* shapeOutline = nullptr; + SwOutline* strokeOutline = nullptr; + auto dashStroking = false; + auto ret = true; + + auto length = rshape->strokeTrim() ? _outlineLength(rshape) : 0.0f; + + //Dash style (+trimming) + if (rshape->stroke->dashCnt > 0 || length > 0) { + shapeOutline = _genDashOutline(rshape, transform, length, mpool, tid); + if (!shapeOutline) return false; + dashStroking = true; + //Normal style + } else { + if (!shape->outline) { + if (!_genOutline(shape, rshape, transform, mpool, tid, false)) return false; + } + shapeOutline = shape->outline; + } + + if (!strokeParseOutline(shape->stroke, *shapeOutline)) { + ret = false; + goto clear; + } + + strokeOutline = strokeExportOutline(shape->stroke, mpool, tid); + + if (!mathUpdateOutlineBBox(strokeOutline, clipRegion, renderRegion, false)) { + ret = false; + goto clear; + } + + shape->strokeRle = rleRender(shape->strokeRle, strokeOutline, renderRegion, true); + +clear: + if (dashStroking) mpoolRetDashOutline(mpool, tid); + mpoolRetStrokeOutline(mpool, tid); + + return ret; +} + + +bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable) +{ + return fillGenColorTable(shape->fill, fill, transform, surface, opacity, ctable); +} + + +bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable) +{ + return fillGenColorTable(shape->stroke->fill, fill, transform, surface, opacity, ctable); +} + + +void shapeResetFill(SwShape* shape) +{ + if (!shape->fill) { + shape->fill = static_cast(calloc(1, sizeof(SwFill))); + if (!shape->fill) return; + } + fillReset(shape->fill); +} + + +void shapeResetStrokeFill(SwShape* shape) +{ + if (!shape->stroke->fill) { + shape->stroke->fill = static_cast(calloc(1, sizeof(SwFill))); + if (!shape->stroke->fill) return; + } + fillReset(shape->stroke->fill); +} + + +void shapeDelFill(SwShape* shape) +{ + if (!shape->fill) return; + fillFree(shape->fill); + shape->fill = nullptr; +} + + +void shapeDelStrokeFill(SwShape* shape) +{ + if (!shape->stroke->fill) return; + fillFree(shape->stroke->fill); + shape->stroke->fill = nullptr; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwStroke.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwStroke.cpp new file mode 100644 index 00000000000..9ec4bd78a50 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/sw_engine/tvgSwStroke.cpp @@ -0,0 +1,907 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "tvgSwCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static constexpr auto SW_STROKE_TAG_POINT = 1; +static constexpr auto SW_STROKE_TAG_CUBIC = 2; +static constexpr auto SW_STROKE_TAG_BEGIN = 4; +static constexpr auto SW_STROKE_TAG_END = 8; + +static inline SwFixed SIDE_TO_ROTATE(const int32_t s) +{ + return (SW_ANGLE_PI2 - static_cast(s) * SW_ANGLE_PI); +} + + +static inline void SCALE(const SwStroke& stroke, SwPoint& pt) +{ + pt.x = static_cast(pt.x * stroke.sx); + pt.y = static_cast(pt.y * stroke.sy); +} + + +static void _growBorder(SwStrokeBorder* border, uint32_t newPts) +{ + auto maxOld = border->maxPts; + auto maxNew = border->ptsCnt + newPts; + + if (maxNew <= maxOld) return; + + auto maxCur = maxOld; + + while (maxCur < maxNew) + maxCur += (maxCur >> 1) + 16; + //OPTIMIZE: use mempool! + border->pts = static_cast(realloc(border->pts, maxCur * sizeof(SwPoint))); + border->tags = static_cast(realloc(border->tags, maxCur * sizeof(uint8_t))); + border->maxPts = maxCur; +} + + +static void _borderClose(SwStrokeBorder* border, bool reverse) +{ + auto start = border->start; + auto count = border->ptsCnt; + + //Don't record empty paths! + if (count <= start + 1U) { + border->ptsCnt = start; + } else { + /* Copy the last point to the start of this sub-path, + since it contains the adjusted starting coordinates */ + border->ptsCnt = --count; + border->pts[start] = border->pts[count]; + + if (reverse) { + //reverse the points + auto pt1 = border->pts + start + 1; + auto pt2 = border->pts + count - 1; + + while (pt1 < pt2) { + auto tmp = *pt1; + *pt1 = *pt2; + *pt2 = tmp; + ++pt1; + --pt2; + } + + //reverse the tags + auto tag1 = border->tags + start + 1; + auto tag2 = border->tags + count - 1; + + while (tag1 < tag2) { + auto tmp = *tag1; + *tag1 = *tag2; + *tag2 = tmp; + ++tag1; + --tag2; + } + } + + border->tags[start] |= SW_STROKE_TAG_BEGIN; + border->tags[count - 1] |= SW_STROKE_TAG_END; + } + + border->start = -1; + border->movable = false; +} + + +static void _borderCubicTo(SwStrokeBorder* border, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) +{ + _growBorder(border, 3); + + auto pt = border->pts + border->ptsCnt; + auto tag = border->tags + border->ptsCnt; + + pt[0] = ctrl1; + pt[1] = ctrl2; + pt[2] = to; + + tag[0] = SW_STROKE_TAG_CUBIC; + tag[1] = SW_STROKE_TAG_CUBIC; + tag[2] = SW_STROKE_TAG_POINT; + + border->ptsCnt += 3; + border->movable = false; +} + + +static void _borderArcTo(SwStrokeBorder* border, const SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff, SwStroke& stroke) +{ + constexpr SwFixed ARC_CUBIC_ANGLE = SW_ANGLE_PI / 2; + SwPoint a = {static_cast(radius), 0}; + mathRotate(a, angleStart); + SCALE(stroke, a); + a += center; + + auto total = angleDiff; + auto angle = angleStart; + auto rotate = (angleDiff >= 0) ? SW_ANGLE_PI2 : -SW_ANGLE_PI2; + + while (total != 0) { + auto step = total; + if (step > ARC_CUBIC_ANGLE) step = ARC_CUBIC_ANGLE; + else if (step < -ARC_CUBIC_ANGLE) step = -ARC_CUBIC_ANGLE; + + auto next = angle + step; + auto theta = step; + if (theta < 0) theta = -theta; + + theta >>= 1; + + //compute end point + SwPoint b = {static_cast(radius), 0}; + mathRotate(b, next); + SCALE(stroke, b); + b += center; + + //compute first and second control points + auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3); + + SwPoint a2 = {static_cast(length), 0}; + mathRotate(a2, angle + rotate); + SCALE(stroke, a2); + a2 += a; + + SwPoint b2 = {static_cast(length), 0}; + mathRotate(b2, next - rotate); + SCALE(stroke, b2); + b2 += b; + + //add cubic arc + _borderCubicTo(border, a2, b2, b); + + //process the rest of the arc? + a = b; + total -= step; + angle = next; + } +} + + +static void _borderLineTo(SwStrokeBorder* border, const SwPoint& to, bool movable) +{ + if (border->movable) { + //move last point + border->pts[border->ptsCnt - 1] = to; + } else { + //don't add zero-length line_to + if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return; + + _growBorder(border, 1); + border->pts[border->ptsCnt] = to; + border->tags[border->ptsCnt] = SW_STROKE_TAG_POINT; + border->ptsCnt += 1; + } + + border->movable = movable; +} + + +static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to) +{ + //close current open path if any? + if (border->start >= 0) _borderClose(border, false); + + border->start = border->ptsCnt; + border->movable = false; + + _borderLineTo(border, to, false); +} + + +static void _arcTo(SwStroke& stroke, int32_t side) +{ + auto border = stroke.borders + side; + auto rotate = SIDE_TO_ROTATE(side); + auto total = mathDiff(stroke.angleIn, stroke.angleOut); + if (total == SW_ANGLE_PI) total = -rotate * 2; + + _borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total, stroke); + border->movable = false; +} + + +static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength) +{ + auto border = stroke.borders + side; + + if (stroke.join == StrokeJoin::Round) { + _arcTo(stroke, side); + } else { + //this is a mitered (pointed) or beveled (truncated) corner + auto rotate = SIDE_TO_ROTATE(side); + auto bevel = (stroke.join == StrokeJoin::Bevel) ? true : false; + SwFixed phi = 0; + SwFixed thcos = 0; + + if (!bevel) { + auto theta = mathDiff(stroke.angleIn, stroke.angleOut); + if (theta == SW_ANGLE_PI) { + theta = rotate; + phi = stroke.angleIn; + } else { + theta /= 2; + phi = stroke.angleIn + theta + rotate; + } + + thcos = mathCos(theta); + auto sigma = mathMultiply(stroke.miterlimit, thcos); + + //is miter limit exceeded? + if (sigma < 0x10000L) bevel = true; + } + + //this is a bevel (broken angle) + if (bevel) { + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, stroke.angleOut + rotate); + SCALE(stroke, delta); + delta += stroke.center; + border->movable = false; + _borderLineTo(border, delta, false); + //this is a miter (intersection) + } else { + auto length = mathDivide(stroke.width, thcos); + SwPoint delta = {static_cast(length), 0}; + mathRotate(delta, phi); + SCALE(stroke, delta); + delta += stroke.center; + _borderLineTo(border, delta, false); + + /* Now add and end point + Only needed if not lineto (lineLength is zero for curves) */ + if (lineLength == 0) { + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, stroke.angleOut + rotate); + SCALE(stroke, delta); + delta += stroke.center; + _borderLineTo(border, delta, false); + } + } + } +} + + +static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength) +{ + auto border = stroke.borders + side; + auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2; + SwPoint delta; + bool intersect = false; + + /* Only intersect borders if between two line_to's and both + lines are long enough (line length is zero for curves). */ + if (border->movable && lineLength > 0) { + //compute minimum required length of lines + SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta))); + if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true; + } + + auto rotate = SIDE_TO_ROTATE(side); + + if (!intersect) { + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, stroke.angleOut + rotate); + SCALE(stroke, delta); + delta += stroke.center; + border->movable = false; + } else { + //compute median angle + auto phi = stroke.angleIn + theta; + auto thcos = mathCos(theta); + delta = {static_cast(mathDivide(stroke.width, thcos)), 0}; + mathRotate(delta, phi + rotate); + SCALE(stroke, delta); + delta += stroke.center; + } + + _borderLineTo(border, delta, false); +} + + +void _processCorner(SwStroke& stroke, SwFixed lineLength) +{ + auto turn = mathDiff(stroke.angleIn, stroke.angleOut); + + //no specific corner processing is required if the turn is 0 + if (turn == 0) return; + + //when we turn to the right, the inside side is 0 + int32_t inside = 0; + + //otherwise, the inside is 1 + if (turn < 0) inside = 1; + + //process the inside + _inside(stroke, inside, lineLength); + + //process the outside + _outside(stroke, 1 - inside, lineLength); +} + + +void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength) +{ + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, startAngle + SW_ANGLE_PI2); + SCALE(stroke, delta); + + auto pt = stroke.center + delta; + auto border = stroke.borders; + _borderMoveTo(border, pt); + + pt = stroke.center - delta; + ++border; + _borderMoveTo(border, pt); + + /* Save angle, position and line length for last join + lineLength is zero for curves */ + stroke.subPathAngle = startAngle; + stroke.firstPt = false; + stroke.subPathLineLength = lineLength; +} + + +static void _lineTo(SwStroke& stroke, const SwPoint& to) +{ + auto delta = to - stroke.center; + + //a zero-length lineto is a no-op; avoid creating a spurious corner + if (delta.zero()) return; + + /* The lineLength is used to determine the intersection of strokes outlines. + The scale needs to be reverted since the stroke width has not been scaled. + An alternative option is to scale the width of the stroke properly by + calculating the mixture of the sx/sy rating on the stroke direction. */ + delta.x = static_cast(delta.x / stroke.sx); + delta.y = static_cast(delta.y / stroke.sy); + auto lineLength = mathLength(delta); + auto angle = mathAtan(delta); + + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle + SW_ANGLE_PI2); + SCALE(stroke, delta); + + //process corner if necessary + if (stroke.firstPt) { + /* This is the first segment of a subpath. We need to add a point to each border + at their respective starting point locations. */ + _firstSubPath(stroke, angle, lineLength); + } else { + //process the current corner + stroke.angleOut = angle; + _processCorner(stroke, lineLength); + } + + //now add a line segment to both the inside and outside paths + auto border = stroke.borders; + auto side = 1; + + while (side >= 0) { + auto pt = to + delta; + + //the ends of lineto borders are movable + _borderLineTo(border, pt, true); + + delta.x = -delta.x; + delta.y = -delta.y; + + --side; + ++border; + } + + stroke.angleIn = angle; + stroke.center = to; + stroke.lineLength = lineLength; +} + + +static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) +{ + SwPoint bezStack[37]; //TODO: static? + auto limit = bezStack + 32; + auto arc = bezStack; + auto firstArc = true; + arc[0] = to; + arc[1] = ctrl2; + arc[2] = ctrl1; + arc[3] = stroke.center; + + while (arc >= bezStack) { + SwFixed angleIn, angleOut, angleMid; + + //initialize with current direction + angleIn = angleOut = angleMid = stroke.angleIn; + + if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) { + if (stroke.firstPt) stroke.angleIn = angleIn; + mathSplitCubic(arc); + arc += 3; + continue; + } + + if (firstArc) { + firstArc = false; + //process corner if necessary + if (stroke.firstPt) { + _firstSubPath(stroke, angleIn, 0); + } else { + stroke.angleOut = angleIn; + _processCorner(stroke, 0); + } + } else if (abs(mathDiff(stroke.angleIn, angleIn)) > (SW_ANGLE_PI / 8) / 4) { + //if the deviation from one arc to the next is too great add a round corner + stroke.center = arc[3]; + stroke.angleOut = angleIn; + stroke.join = StrokeJoin::Round; + + _processCorner(stroke, 0); + + //reinstate line join style + stroke.join = stroke.joinSaved; + } + + //the arc's angle is small enough; we can add it directly to each border + auto theta1 = mathDiff(angleIn, angleMid) / 2; + auto theta2 = mathDiff(angleMid, angleOut) / 2; + auto phi1 = mathMean(angleIn, angleMid); + auto phi2 = mathMean(angleMid, angleOut); + auto length1 = mathDivide(stroke.width, mathCos(theta1)); + auto length2 = mathDivide(stroke.width, mathCos(theta2)); + SwFixed alpha0 = 0; + + //compute direction of original arc + if (stroke.handleWideStrokes) { + alpha0 = mathAtan(arc[0] - arc[3]); + } + + auto border = stroke.borders; + int32_t side = 0; + + while (side < 2) { + auto rotate = SIDE_TO_ROTATE(side); + + //compute control points + SwPoint _ctrl1 = {static_cast(length1), 0}; + mathRotate(_ctrl1, phi1 + rotate); + SCALE(stroke, _ctrl1); + _ctrl1 += arc[2]; + + SwPoint _ctrl2 = {static_cast(length2), 0}; + mathRotate(_ctrl2, phi2 + rotate); + SCALE(stroke, _ctrl2); + _ctrl2 += arc[1]; + + //compute end point + SwPoint _end = {static_cast(stroke.width), 0}; + mathRotate(_end, angleOut + rotate); + SCALE(stroke, _end); + _end += arc[0]; + + if (stroke.handleWideStrokes) { + /* determine whether the border radius is greater than the radius of + curvature of the original arc */ + auto _start = border->pts[border->ptsCnt - 1]; + auto alpha1 = mathAtan(_end - _start); + + //is the direction of the border arc opposite to that of the original arc? + if (abs(mathDiff(alpha0, alpha1)) > SW_ANGLE_PI / 2) { + + //use the sine rule to find the intersection point + auto beta = mathAtan(arc[3] - _start); + auto gamma = mathAtan(arc[0] - _end); + auto bvec = _end - _start; + auto blen = mathLength(bvec); + auto sinA = abs(mathSin(alpha1 - gamma)); + auto sinB = abs(mathSin(beta - gamma)); + auto alen = mathMulDiv(blen, sinA, sinB); + + SwPoint delta = {static_cast(alen), 0}; + mathRotate(delta, beta); + delta += _start; + + //circumnavigate the negative sector backwards + border->movable = false; + _borderLineTo(border, delta, false); + _borderLineTo(border, _end, false); + _borderCubicTo(border, _ctrl2, _ctrl1, _start); + + //and then move to the endpoint + _borderLineTo(border, _end, false); + + ++side; + ++border; + continue; + } + } + _borderCubicTo(border, _ctrl1, _ctrl2, _end); + ++side; + ++border; + } + arc -= 3; + stroke.angleIn = angleOut; + } + stroke.center = to; +} + + +static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side) +{ + if (stroke.cap == StrokeCap::Square) { + auto rotate = SIDE_TO_ROTATE(side); + auto border = stroke.borders + side; + + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle); + SCALE(stroke, delta); + + SwPoint delta2 = {static_cast(stroke.width), 0}; + mathRotate(delta2, angle + rotate); + SCALE(stroke, delta2); + delta += stroke.center + delta2; + + _borderLineTo(border, delta, false); + + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle); + SCALE(stroke, delta); + + delta2 = {static_cast(stroke.width), 0}; + mathRotate(delta2, angle - rotate); + SCALE(stroke, delta2); + delta += delta2 + stroke.center; + + _borderLineTo(border, delta, false); + + } else if (stroke.cap == StrokeCap::Round) { + + stroke.angleIn = angle; + stroke.angleOut = angle + SW_ANGLE_PI; + _arcTo(stroke, side); + return; + + } else { //Butt + auto rotate = SIDE_TO_ROTATE(side); + auto border = stroke.borders + side; + + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle + rotate); + SCALE(stroke, delta); + delta += stroke.center; + + _borderLineTo(border, delta, false); + + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle - rotate); + SCALE(stroke, delta); + delta += stroke.center; + + _borderLineTo(border, delta, false); + } +} + + +static void _addReverseLeft(SwStroke& stroke, bool opened) +{ + auto right = stroke.borders + 0; + auto left = stroke.borders + 1; + auto newPts = left->ptsCnt - left->start; + + if (newPts <= 0) return; + + _growBorder(right, newPts); + + auto dstPt = right->pts + right->ptsCnt; + auto dstTag = right->tags + right->ptsCnt; + auto srcPt = left->pts + left->ptsCnt - 1; + auto srcTag = left->tags + left->ptsCnt - 1; + + while (srcPt >= left->pts + left->start) { + *dstPt = *srcPt; + *dstTag = *srcTag; + + if (opened) { + dstTag[0] &= ~(SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); + } else { + //switch begin/end tags if necessary + auto ttag = dstTag[0] & (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); + if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END) + dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); + } + --srcPt; + --srcTag; + ++dstPt; + ++dstTag; + } + + left->ptsCnt = left->start; + right->ptsCnt += newPts; + right->movable = false; + left->movable = false; +} + + +static void _beginSubPath(SwStroke& stroke, const SwPoint& to, bool closed) +{ + /* We cannot process the first point because there is not enough + information regarding its corner/cap. Later, it will be processed + in the _endSubPath() */ + + stroke.firstPt = true; + stroke.center = to; + stroke.closedSubPath = closed; + + /* Determine if we need to check whether the border radius is greater + than the radius of curvature of a curve, to handle this case specially. + This is only required if bevel joins or butt caps may be created because + round & miter joins and round & square caps cover the nagative sector + created with wide strokes. */ + if ((stroke.join != StrokeJoin::Round) || (!stroke.closedSubPath && stroke.cap == StrokeCap::Butt)) + stroke.handleWideStrokes = true; + else + stroke.handleWideStrokes = false; + + stroke.ptStartSubPath = to; + stroke.angleIn = 0; +} + + +static void _endSubPath(SwStroke& stroke) +{ + if (stroke.closedSubPath) { + //close the path if needed + if (stroke.center != stroke.ptStartSubPath) + _lineTo(stroke, stroke.ptStartSubPath); + + //process the corner + stroke.angleOut = stroke.subPathAngle; + auto turn = mathDiff(stroke.angleIn, stroke.angleOut); + + //No specific corner processing is required if the turn is 0 + if (turn != 0) { + //when we turn to the right, the inside is 0 + int32_t inside = 0; + + //otherwise, the inside is 1 + if (turn < 0) inside = 1; + + _inside(stroke, inside, stroke.subPathLineLength); //inside + _outside(stroke, 1 - inside, stroke.subPathLineLength); //outside + } + + _borderClose(stroke.borders + 0, false); + _borderClose(stroke.borders + 1, true); + } else { + auto right = stroke.borders; + + /* all right, this is an opened path, we need to add a cap between + right & left, add the reverse of left, then add a final cap + between left & right */ + _addCap(stroke, stroke.angleIn, 0); + + //add reversed points from 'left' to 'right' + _addReverseLeft(stroke, true); + + //now add the final cap + stroke.center = stroke.ptStartSubPath; + _addCap(stroke, stroke.subPathAngle + SW_ANGLE_PI, 0); + + /* now end the right subpath accordingly. The left one is rewind + and deosn't need further processing */ + _borderClose(right, false); + } +} + + +static void _getCounts(SwStrokeBorder* border, uint32_t& ptsCnt, uint32_t& cntrsCnt) +{ + auto count = border->ptsCnt; + auto tags = border->tags; + uint32_t _ptsCnt = 0; + uint32_t _cntrsCnt = 0; + bool inCntr = false; + + while (count > 0) { + if (tags[0] & SW_STROKE_TAG_BEGIN) { + if (inCntr) goto fail; + inCntr = true; + } else if (!inCntr) goto fail; + + if (tags[0] & SW_STROKE_TAG_END) { + inCntr = false; + ++_cntrsCnt; + } + --count; + ++_ptsCnt; + ++tags; + } + + if (inCntr) goto fail; + + ptsCnt = _ptsCnt; + cntrsCnt = _cntrsCnt; + + return; + +fail: + ptsCnt = 0; + cntrsCnt = 0; +} + + +static void _exportBorderOutline(const SwStroke& stroke, SwOutline* outline, uint32_t side) +{ + auto border = stroke.borders + side; + if (border->ptsCnt == 0) return; + + memcpy(outline->pts.data + outline->pts.count, border->pts, border->ptsCnt * sizeof(SwPoint)); + + auto cnt = border->ptsCnt; + auto src = border->tags; + auto tags = outline->types.data + outline->types.count; + auto idx = outline->pts.count; + + while (cnt > 0) { + if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT; + else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC; + else TVGERR("SW_ENGINE", "Invalid stroke tag was given! = %d", *src); + if (*src & SW_STROKE_TAG_END) outline->cntrs.push(idx); + ++src; + ++tags; + ++idx; + --cnt; + } + outline->pts.count += border->ptsCnt; + outline->types.count += border->ptsCnt; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void strokeFree(SwStroke* stroke) +{ + if (!stroke) return; + + //free borders + if (stroke->borders[0].pts) free(stroke->borders[0].pts); + if (stroke->borders[0].tags) free(stroke->borders[0].tags); + if (stroke->borders[1].pts) free(stroke->borders[1].pts); + if (stroke->borders[1].tags) free(stroke->borders[1].tags); + + fillFree(stroke->fill); + stroke->fill = nullptr; + + free(stroke); +} + + +void strokeReset(SwStroke* stroke, const RenderShape* rshape, const Matrix* transform) +{ + if (transform) { + stroke->sx = sqrtf(powf(transform->e11, 2.0f) + powf(transform->e21, 2.0f)); + stroke->sy = sqrtf(powf(transform->e12, 2.0f) + powf(transform->e22, 2.0f)); + } else { + stroke->sx = stroke->sy = 1.0f; + } + + stroke->width = HALF_STROKE(rshape->strokeWidth()); + stroke->cap = rshape->strokeCap(); + stroke->miterlimit = static_cast(rshape->strokeMiterlimit()) << 16; + + //Save line join: it can be temporarily changed when stroking curves... + stroke->joinSaved = stroke->join = rshape->strokeJoin(); + + stroke->borders[0].ptsCnt = 0; + stroke->borders[0].start = -1; + stroke->borders[1].ptsCnt = 0; + stroke->borders[1].start = -1; +} + + +bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline) +{ + uint32_t first = 0; + uint32_t i = 0; + + for (auto cntr = outline.cntrs.begin(); cntr < outline.cntrs.end(); ++cntr, ++i) { + auto last = *cntr; //index of last point in contour + auto limit = outline.pts.data + last; + + //Skip empty points + if (last <= first) { + first = last + 1; + continue; + } + + auto start = outline.pts[first]; + auto pt = outline.pts.data + first; + auto types = outline.types.data + first; + auto type = types[0]; + + //A contour cannot start with a cubic control point + if (type == SW_CURVE_TYPE_CUBIC) return false; + + auto closed = outline.closed.data ? outline.closed.data[i]: false; + + _beginSubPath(*stroke, start, closed); + + while (pt < limit) { + ++pt; + ++types; + + //emit a signel line_to + if (types[0] == SW_CURVE_TYPE_POINT) { + _lineTo(*stroke, *pt); + //types cubic + } else { + if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) return false; + + pt += 2; + types += 2; + + if (pt <= limit) { + _cubicTo(*stroke, pt[-2], pt[-1], pt[0]); + continue; + } + _cubicTo(*stroke, pt[-2], pt[-1], start); + goto close; + } + } + close: + if (!stroke->firstPt) _endSubPath(*stroke); + first = last + 1; + } + return true; +} + + +SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid) +{ + uint32_t count1, count2, count3, count4; + + _getCounts(stroke->borders + 0, count1, count2); + _getCounts(stroke->borders + 1, count3, count4); + + auto ptsCnt = count1 + count3; + auto cntrsCnt = count2 + count4; + + auto outline = mpoolReqStrokeOutline(mpool, tid); + outline->pts.reserve(ptsCnt); + outline->types.reserve(ptsCnt); + outline->cntrs.reserve(cntrsCnt); + + _exportBorderOutline(*stroke, outline, 0); //left + _exportBorderOutline(*stroke, outline, 1); //right + + return outline; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgAccessor.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgAccessor.cpp new file mode 100644 index 00000000000..903437f29d3 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgAccessor.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgIteratorAccessor.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static bool accessChildren(Iterator* it, function func) +{ + while (auto child = it->next()) { + //Access the child + if (!func(child)) return false; + + //Access the children of the child + if (auto it2 = IteratorAccessor::iterator(child)) { + if (!accessChildren(it2, func)) { + delete(it2); + return false; + } + delete(it2); + } + } + return true; +} + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +unique_ptr Accessor::set(unique_ptr picture, function func) noexcept +{ + auto p = picture.get(); + if (!p || !func) return picture; + + //Use the Preorder Tree-Search + + //Root + if (!func(p)) return picture; + + //Children + if (auto it = IteratorAccessor::iterator(p)) { + accessChildren(it, func); + delete(it); + } + return picture; +} + + +Accessor::~Accessor() +{ + +} + + +Accessor::Accessor() : pImpl(nullptr) +{ + +} + + +unique_ptr Accessor::gen() noexcept +{ + return unique_ptr(new Accessor); +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgAnimation.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgAnimation.cpp new file mode 100644 index 00000000000..be6c2dc7de9 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgAnimation.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgFrameModule.h" +#include "tvgAnimation.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Animation::~Animation() +{ + delete(pImpl); +} + + +Animation::Animation() : pImpl(new Impl) +{ +} + + +Result Animation::frame(float no) noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + + if (!loader) return Result::InsufficientCondition; + if (!loader->animatable()) return Result::NonSupport; + + if (static_cast(loader)->frame(no)) return Result::Success; + return Result::InsufficientCondition; +} + + +Picture* Animation::picture() const noexcept +{ + return pImpl->picture; +} + + +float Animation::curFrame() const noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + + if (!loader) return 0; + if (!loader->animatable()) return 0; + + return static_cast(loader)->curFrame(); +} + + +float Animation::totalFrame() const noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + + if (!loader) return 0; + if (!loader->animatable()) return 0; + + return static_cast(loader)->totalFrame(); +} + + +float Animation::duration() const noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + + if (!loader) return 0; + if (!loader->animatable()) return 0; + + return static_cast(loader)->duration(); +} + + +Result Animation::segment(float begin, float end) noexcept +{ + if (begin < 0.0 || end > 1.0 || begin >= end) return Result::InvalidArguments; + + auto loader = pImpl->picture->pImpl->loader; + if (!loader) return Result::InsufficientCondition; + if (!loader->animatable()) return Result::NonSupport; + + static_cast(loader)->segment(begin, end); + + return Result::Success; +} + + +Result Animation::segment(float *begin, float *end) noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + if (!loader) return Result::InsufficientCondition; + if (!loader->animatable()) return Result::NonSupport; + if (!begin && !end) return Result::InvalidArguments; + + static_cast(loader)->segment(begin, end); + + return Result::Success; +} + + +unique_ptr Animation::gen() noexcept +{ + return unique_ptr(new Animation); +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgAnimation.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgAnimation.h new file mode 100644 index 00000000000..14212eb67ad --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgAnimation.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_ANIMATION_H_ +#define _TVG_ANIMATION_H_ + +#include "tvgCommon.h" +#include "tvgPaint.h" +#include "tvgPicture.h" + +struct Animation::Impl +{ + Picture* picture = nullptr; + + Impl() + { + picture = Picture::gen().release(); + PP(picture)->ref(); + } + + ~Impl() + { + if (PP(picture)->unref() == 0) { + delete(picture); + } + } +}; + +#endif //_TVG_ANIMATION_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgCanvas.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgCanvas.cpp new file mode 100644 index 00000000000..9d51b07ca28 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgCanvas.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgCanvas.h" + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Canvas::Canvas(RenderMethod *pRenderer):pImpl(new Impl(pRenderer)) +{ +} + + +Canvas::~Canvas() +{ + delete(pImpl); +} + + +list& Canvas::paints() noexcept +{ + return pImpl->paints; +} + + +Result Canvas::push(unique_ptr paint) noexcept +{ + return pImpl->push(std::move(paint)); +} + + +Result Canvas::clear(bool paints, bool buffer) noexcept +{ + return pImpl->clear(paints, buffer); +} + + +Result Canvas::draw() noexcept +{ + TVGLOG("RENDERER", "Draw S. -------------------------------- Canvas(%p)", this); + auto ret = pImpl->draw(); + TVGLOG("RENDERER", "Draw E. -------------------------------- Canvas(%p)", this); + + return ret; +} + + +Result Canvas::update(Paint* paint) noexcept +{ + TVGLOG("RENDERER", "Update S. ------------------------------ Canvas(%p)", this); + auto ret = pImpl->update(paint, false); + TVGLOG("RENDERER", "Update E. ------------------------------ Canvas(%p)", this); + + return ret; +} + + +Result Canvas::sync() noexcept +{ + return pImpl->sync(); +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgCanvas.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgCanvas.h new file mode 100644 index 00000000000..238ee3e0d82 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgCanvas.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_CANVAS_H_ +#define _TVG_CANVAS_H_ + +#include "tvgPaint.h" + + +struct Canvas::Impl +{ + list paints; + RenderMethod* renderer; + bool refresh = false; //if all paints should be updated by force. + bool drawing = false; //on drawing condition? + + Impl(RenderMethod* pRenderer) : renderer(pRenderer) + { + renderer->ref(); + } + + ~Impl() + { + //make it sure any deffered jobs + if (renderer) renderer->sync(); + + clearPaints(); + + if (renderer && (renderer->unref() == 0)) delete(renderer); + } + + void clearPaints() + { + for (auto paint : paints) { + if (P(paint)->unref() == 0) delete(paint); + } + paints.clear(); + } + + Result push(unique_ptr paint) + { + //You can not push paints during rendering. + if (drawing) return Result::InsufficientCondition; + + auto p = paint.release(); + if (!p) return Result::MemoryCorruption; + PP(p)->ref(); + paints.push_back(p); + + return update(p, true); + } + + Result clear(bool paints, bool buffer) + { + if (drawing) return Result::InsufficientCondition; + + //Clear render target + if (buffer) { + if (!renderer || !renderer->clear()) return Result::InsufficientCondition; + } + + if (paints) clearPaints(); + + return Result::Success; + } + + void needRefresh() + { + refresh = true; + } + + Result update(Paint* paint, bool force) + { + if (paints.empty() || drawing || !renderer) return Result::InsufficientCondition; + + Array clips; + auto flag = RenderUpdateFlag::None; + if (refresh || force) flag = RenderUpdateFlag::All; + + if (paint) { + paint->pImpl->update(renderer, nullptr, clips, 255, flag); + } else { + for (auto paint : paints) { + paint->pImpl->update(renderer, nullptr, clips, 255, flag); + } + refresh = false; + } + return Result::Success; + } + + Result draw() + { + if (drawing || paints.empty() || !renderer || !renderer->preRender()) return Result::InsufficientCondition; + + bool rendered = false; + for (auto paint : paints) { + if (paint->pImpl->render(renderer)) rendered = true; + } + + if (!rendered || !renderer->postRender()) return Result::InsufficientCondition; + + drawing = true; + + return Result::Success; + } + + Result sync() + { + if (!drawing) return Result::InsufficientCondition; + + if (renderer->sync()) { + drawing = false; + return Result::Success; + } + + return Result::InsufficientCondition; + } +}; + +#endif /* _TVG_CANVAS_H_ */ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgCommon.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgCommon.h new file mode 100644 index 00000000000..d080ea19cf6 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgCommon.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_COMMON_H_ +#define _TVG_COMMON_H_ + +#include "config.h" +#include "thorvg.h" + +using namespace std; +using namespace tvg; + +//for MSVC Compat +#ifdef _MSC_VER + #define TVG_UNUSED + #define strncasecmp _strnicmp + #define strcasecmp _stricmp +#else + #define TVG_UNUSED __attribute__ ((__unused__)) +#endif + +// Portable 'fallthrough' attribute +#if __has_cpp_attribute(fallthrough) + #ifdef _MSC_VER + #define TVG_FALLTHROUGH [[fallthrough]]; + #else + #define TVG_FALLTHROUGH __attribute__ ((fallthrough)); + #endif +#else + #define TVG_FALLTHROUGH +#endif + +#if defined(_MSC_VER) && defined(__clang__) + #define strncpy strncpy_s + #define strdup _strdup +#endif + +//TVG class identifier values +#define TVG_CLASS_ID_UNDEFINED 0 +#define TVG_CLASS_ID_SHAPE 1 +#define TVG_CLASS_ID_SCENE 2 +#define TVG_CLASS_ID_PICTURE 3 +#define TVG_CLASS_ID_LINEAR 4 +#define TVG_CLASS_ID_RADIAL 5 +#define TVG_CLASS_ID_TEXT 6 + +enum class FileType { Png = 0, Jpg, Webp, Tvg, Svg, Lottie, Ttf, Raw, Gif, Unknown }; + +using Size = Point; + +#ifdef THORVG_LOG_ENABLED + constexpr auto ErrorColor = "\033[31m"; //red + constexpr auto ErrorBgColor = "\033[41m";//bg red + constexpr auto LogColor = "\033[32m"; //green + constexpr auto LogBgColor = "\033[42m"; //bg green + constexpr auto GreyColor = "\033[90m"; //grey + constexpr auto ResetColors = "\033[0m"; //default + #define TVGERR(tag, fmt, ...) fprintf(stderr, "%s[E]%s %s" tag "%s (%s %d): %s" fmt "\n", ErrorBgColor, ResetColors, ErrorColor, GreyColor, __FILE__, __LINE__, ResetColors, ##__VA_ARGS__) + #define TVGLOG(tag, fmt, ...) fprintf(stdout, "%s[L]%s %s" tag "%s (%s %d): %s" fmt "\n", LogBgColor, ResetColors, LogColor, GreyColor, __FILE__, __LINE__, ResetColors, ##__VA_ARGS__) +#else + #define TVGERR(...) do {} while(0) + #define TVGLOG(...) do {} while(0) +#endif + +uint16_t THORVG_VERSION_NUMBER(); + + +#define P(A) ((A)->pImpl) //Access to pimpl. +#define PP(A) (((Paint*)(A))->pImpl) //Access to pimpl. + +#endif //_TVG_COMMON_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgFill.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgFill.cpp new file mode 100644 index 00000000000..ea1010051ee --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgFill.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgFill.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +Fill* RadialGradient::Impl::duplicate() +{ + auto ret = RadialGradient::gen(); + if (!ret) return nullptr; + + ret->pImpl->cx = cx; + ret->pImpl->cy = cy; + ret->pImpl->r = r; + ret->pImpl->fx = fx; + ret->pImpl->fy = fy; + ret->pImpl->fr = fr; + + return ret.release(); +} + + +Result RadialGradient::Impl::radial(float cx, float cy, float r, float fx, float fy, float fr) +{ + if (r < 0 || fr < 0) return Result::InvalidArguments; + + this->cx = cx; + this->cy = cy; + this->r = r; + this->fx = fx; + this->fy = fy; + this->fr = fr; + + return Result::Success; +}; + + +Fill* LinearGradient::Impl::duplicate() +{ + auto ret = LinearGradient::gen(); + if (!ret) return nullptr; + + ret->pImpl->x1 = x1; + ret->pImpl->y1 = y1; + ret->pImpl->x2 = x2; + ret->pImpl->y2 = y2; + + return ret.release(); +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Fill::Fill():pImpl(new Impl()) +{ +} + + +Fill::~Fill() +{ + delete(pImpl); +} + + +Result Fill::colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept +{ + if ((!colorStops && cnt > 0) || (colorStops && cnt == 0)) return Result::InvalidArguments; + + if (cnt == 0) { + if (pImpl->colorStops) { + free(pImpl->colorStops); + pImpl->colorStops = nullptr; + pImpl->cnt = 0; + } + return Result::Success; + } + + if (pImpl->cnt != cnt) { + pImpl->colorStops = static_cast(realloc(pImpl->colorStops, cnt * sizeof(ColorStop))); + } + + pImpl->cnt = cnt; + memcpy(pImpl->colorStops, colorStops, cnt * sizeof(ColorStop)); + + return Result::Success; +} + + +uint32_t Fill::colorStops(const ColorStop** colorStops) const noexcept +{ + if (colorStops) *colorStops = pImpl->colorStops; + + return pImpl->cnt; +} + + +Result Fill::spread(FillSpread s) noexcept +{ + pImpl->spread = s; + + return Result::Success; +} + + +FillSpread Fill::spread() const noexcept +{ + return pImpl->spread; +} + + +Result Fill::transform(const Matrix& m) noexcept +{ + if (!pImpl->transform) { + pImpl->transform = static_cast(malloc(sizeof(Matrix))); + } + *pImpl->transform = m; + return Result::Success; +} + + +Matrix Fill::transform() const noexcept +{ + if (pImpl->transform) return *pImpl->transform; + return {1, 0, 0, 0, 1, 0, 0, 0, 1}; +} + + +Fill* Fill::duplicate() const noexcept +{ + return pImpl->duplicate(); +} + + +uint32_t Fill::identifier() const noexcept +{ + return pImpl->id; +} + + +RadialGradient::RadialGradient():pImpl(new Impl()) +{ + Fill::pImpl->id = TVG_CLASS_ID_RADIAL; + Fill::pImpl->method(new FillDup(pImpl)); +} + + +RadialGradient::~RadialGradient() +{ + delete(pImpl); +} + + +Result RadialGradient::radial(float cx, float cy, float r) noexcept +{ + return pImpl->radial(cx, cy, r, cx, cy, 0.0f); +} + + +Result RadialGradient::radial(float* cx, float* cy, float* r) const noexcept +{ + if (cx) *cx = pImpl->cx; + if (cy) *cy = pImpl->cy; + if (r) *r = pImpl->r; + + return Result::Success; +} + + +unique_ptr RadialGradient::gen() noexcept +{ + return unique_ptr(new RadialGradient); +} + + +uint32_t RadialGradient::identifier() noexcept +{ + return TVG_CLASS_ID_RADIAL; +} + + +LinearGradient::LinearGradient():pImpl(new Impl()) +{ + Fill::pImpl->id = TVG_CLASS_ID_LINEAR; + Fill::pImpl->method(new FillDup(pImpl)); +} + + +LinearGradient::~LinearGradient() +{ + delete(pImpl); +} + + +Result LinearGradient::linear(float x1, float y1, float x2, float y2) noexcept +{ + pImpl->x1 = x1; + pImpl->y1 = y1; + pImpl->x2 = x2; + pImpl->y2 = y2; + + return Result::Success; +} + + +Result LinearGradient::linear(float* x1, float* y1, float* x2, float* y2) const noexcept +{ + if (x1) *x1 = pImpl->x1; + if (x2) *x2 = pImpl->x2; + if (y1) *y1 = pImpl->y1; + if (y2) *y2 = pImpl->y2; + + return Result::Success; +} + + +unique_ptr LinearGradient::gen() noexcept +{ + return unique_ptr(new LinearGradient); +} + + +uint32_t LinearGradient::identifier() noexcept +{ + return TVG_CLASS_ID_LINEAR; +} + diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgFill.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgFill.h new file mode 100644 index 00000000000..47f0c051c02 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgFill.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_FILL_H_ +#define _TVG_FILL_H_ + +#include +#include +#include "tvgCommon.h" + +template +struct DuplicateMethod +{ + virtual ~DuplicateMethod() {} + virtual T* duplicate() = 0; +}; + +template +struct FillDup : DuplicateMethod +{ + T* inst = nullptr; + + FillDup(T* _inst) : inst(_inst) {} + ~FillDup() {} + + Fill* duplicate() override + { + return inst->duplicate(); + } +}; + +struct Fill::Impl +{ + ColorStop* colorStops = nullptr; + Matrix* transform = nullptr; + uint32_t cnt = 0; + FillSpread spread; + DuplicateMethod* dup = nullptr; + uint8_t id; + + ~Impl() + { + delete(dup); + free(colorStops); + free(transform); + } + + void method(DuplicateMethod* dup) + { + this->dup = dup; + } + + Fill* duplicate() + { + auto ret = dup->duplicate(); + if (!ret) return nullptr; + + ret->pImpl->cnt = cnt; + ret->pImpl->spread = spread; + ret->pImpl->colorStops = static_cast(malloc(sizeof(ColorStop) * cnt)); + memcpy(ret->pImpl->colorStops, colorStops, sizeof(ColorStop) * cnt); + if (transform) { + ret->pImpl->transform = static_cast(malloc(sizeof(Matrix))); + *ret->pImpl->transform = *transform; + } + return ret; + } +}; + + +struct RadialGradient::Impl +{ + float cx = 0.0f, cy = 0.0f; + float fx = 0.0f, fy = 0.0f; + float r = 0.0f, fr = 0.0f; + + Fill* duplicate(); + Result radial(float cx, float cy, float r, float fx, float fy, float fr); +}; + + +struct LinearGradient::Impl +{ + float x1 = 0.0f; + float y1 = 0.0f; + float x2 = 0.0f; + float y2 = 0.0f; + + Fill* duplicate(); +}; + + +#endif //_TVG_FILL_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgFrameModule.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgFrameModule.h new file mode 100644 index 00000000000..37d01e050db --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgFrameModule.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_FRAME_MODULE_H_ +#define _TVG_FRAME_MODULE_H_ + +#include "tvgLoadModule.h" + +namespace tvg +{ + +class FrameModule: public ImageLoader +{ +public: + float segmentBegin = 0.0f; + float segmentEnd = 1.0f; + + FrameModule(FileType type) : ImageLoader(type) {} + virtual ~FrameModule() {} + + virtual bool frame(float no) = 0; //set the current frame number + virtual float totalFrame() = 0; //return the total frame count + virtual float curFrame() = 0; //return the current frame number + virtual float duration() = 0; //return the animation duration in seconds + + void segment(float* begin, float* end) + { + if (begin) *begin = segmentBegin; + if (end) *end = segmentEnd; + } + + void segment(float begin, float end) + { + segmentBegin = begin; + segmentEnd = end; + } + + virtual bool animatable() override { return true; } +}; + +} + +#endif //_TVG_FRAME_MODULE_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgGlCanvas.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgGlCanvas.cpp new file mode 100644 index 00000000000..940e6682f8c --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgGlCanvas.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgCanvas.h" + +#ifdef THORVG_GL_RASTER_SUPPORT + #include "tvgGlRenderer.h" +#else + class GlRenderer : public RenderMethod + { + //Non Supported. Dummy Class */ + }; +#endif + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct GlCanvas::Impl +{ +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +#ifdef THORVG_GL_RASTER_SUPPORT +GlCanvas::GlCanvas() : Canvas(GlRenderer::gen()), pImpl(new Impl) +#else +GlCanvas::GlCanvas() : Canvas(nullptr), pImpl(new Impl) +#endif +{ +} + + + +GlCanvas::~GlCanvas() +{ + delete(pImpl); +} + + +Result GlCanvas::target(int32_t id, uint32_t w, uint32_t h) noexcept +{ +#ifdef THORVG_GL_RASTER_SUPPORT + //We know renderer type, avoid dynamic_cast for performance. + auto renderer = static_cast(Canvas::pImpl->renderer); + if (!renderer) return Result::MemoryCorruption; + + if (!renderer->target(id, w, h)) return Result::Unknown; + + //Paints must be updated again with this new target. + Canvas::pImpl->needRefresh(); + + return Result::Success; +#endif + return Result::NonSupport; +} + + +unique_ptr GlCanvas::gen() noexcept +{ +#ifdef THORVG_GL_RASTER_SUPPORT + if (GlRenderer::init() <= 0) return nullptr; + return unique_ptr(new GlCanvas); +#endif + return nullptr; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgInitializer.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgInitializer.cpp new file mode 100644 index 00000000000..74e2aa65008 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgInitializer.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgCommon.h" +#include "tvgTaskScheduler.h" +#include "tvgLoader.h" + +#ifdef _WIN32 + #include +#endif + +#ifdef THORVG_SW_RASTER_SUPPORT + #include "tvgSwRenderer.h" +#endif + +#ifdef THORVG_GL_RASTER_SUPPORT + #include "tvgGlRenderer.h" +#endif + +#ifdef THORVG_WG_RASTER_SUPPORT + #include "tvgWgRenderer.h" +#endif + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static int _initCnt = 0; +static uint16_t _version = 0; + +//enum class operation helper +static constexpr bool operator &(CanvasEngine a, CanvasEngine b) +{ + return int(a) & int(b); +} + +static bool _buildVersionInfo() +{ + auto SRC = THORVG_VERSION_STRING; //ex) 0.3.99 + auto p = SRC; + const char* x; + + char major[3]; + x = strchr(p, '.'); + if (!x) return false; + memcpy(major, p, x - p); + major[x - p] = '\0'; + p = x + 1; + + char minor[3]; + x = strchr(p, '.'); + if (!x) return false; + memcpy(minor, p, x - p); + minor[x - p] = '\0'; + p = x + 1; + + char micro[3]; + x = SRC + strlen(THORVG_VERSION_STRING); + memcpy(micro, p, x - p); + micro[x - p] = '\0'; + + char sum[7]; + snprintf(sum, sizeof(sum), "%s%s%s", major, minor, micro); + + _version = atoi(sum); + + return true; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Result Initializer::init(uint32_t threads, CanvasEngine engine) noexcept +{ + auto nonSupport = true; + + if (engine == CanvasEngine::All || engine & CanvasEngine::Sw) { + #ifdef THORVG_SW_RASTER_SUPPORT + if (!SwRenderer::init(threads)) return Result::FailedAllocation; + nonSupport = false; + #endif + } + + if (engine == CanvasEngine::All || engine & CanvasEngine::Gl) { + #ifdef THORVG_GL_RASTER_SUPPORT + if (!GlRenderer::init(threads)) return Result::FailedAllocation; + nonSupport = false; + #endif + } + + if (engine == CanvasEngine::All || engine & CanvasEngine::Wg) { + #ifdef THORVG_WG_RASTER_SUPPORT + if (!WgRenderer::init(threads)) return Result::FailedAllocation; + nonSupport = false; + #endif + } + + if (nonSupport) return Result::NonSupport; + + if (_initCnt++ > 0) return Result::Success; + + if (!_buildVersionInfo()) return Result::Unknown; + + if (!LoaderMgr::init()) return Result::Unknown; + + TaskScheduler::init(threads); + + return Result::Success; +} + + +Result Initializer::term(CanvasEngine engine) noexcept +{ + if (_initCnt == 0) return Result::InsufficientCondition; + + auto nonSupport = true; + + if (engine == CanvasEngine::All || engine & CanvasEngine::Sw) { + #ifdef THORVG_SW_RASTER_SUPPORT + if (!SwRenderer::term()) return Result::InsufficientCondition; + nonSupport = false; + #endif + } + + if (engine == CanvasEngine::All || engine & CanvasEngine::Gl) { + #ifdef THORVG_GL_RASTER_SUPPORT + if (!GlRenderer::term()) return Result::InsufficientCondition; + nonSupport = false; + #endif + } + + if (engine == CanvasEngine::All || engine & CanvasEngine::Wg) { + #ifdef THORVG_WG_RASTER_SUPPORT + if (!WgRenderer::term()) return Result::InsufficientCondition; + nonSupport = false; + #endif + } + + if (nonSupport) return Result::NonSupport; + + if (--_initCnt > 0) return Result::Success; + + TaskScheduler::term(); + + if (!LoaderMgr::term()) return Result::Unknown; + + return Result::Success; +} + + +uint16_t THORVG_VERSION_NUMBER() +{ + return _version; +} + diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgIteratorAccessor.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgIteratorAccessor.h new file mode 100644 index 00000000000..46e900a5828 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgIteratorAccessor.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_ITERATOR_ACCESSOR_H_ +#define _TVG_ITERATOR_ACCESSOR_H_ + +#include "tvgPaint.h" + +namespace tvg +{ + +class IteratorAccessor +{ +public: + //Utility Method: Iterator Accessor + static Iterator* iterator(const Paint* paint) + { + return paint->pImpl->iterator(); + } +}; + +} + +#endif //_TVG_ITERATOR_ACCESSOR_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgLoadModule.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgLoadModule.h new file mode 100644 index 00000000000..84d35bc0c65 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgLoadModule.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOAD_MODULE_H_ +#define _TVG_LOAD_MODULE_H_ + +#include "tvgRender.h" +#include "tvgInlist.h" + + +struct LoadModule +{ + INLIST_ITEM(LoadModule); + + //Use either hashkey(data) or hashpath(path) + union { + uintptr_t hashkey; + char* hashpath = nullptr; + }; + + FileType type; //current loader file type + uint16_t sharing = 0; //reference count + bool readied = false; //read done already. + bool pathcache = false; //cached by path + + LoadModule(FileType type) : type(type) {} + virtual ~LoadModule() + { + if (pathcache) free(hashpath); + } + + virtual bool open(const string& path) { return false; } + virtual bool open(const char* data, uint32_t size, const string& rpath, bool copy) { return false; } + virtual bool resize(Paint* paint, float w, float h) { return false; } + virtual void sync() {}; //finish immediately if any async update jobs. + + virtual bool read() + { + if (readied) return false; + readied = true; + return true; + } + + bool cached() + { + if (hashkey) return true; + return false; + } + + virtual bool close() + { + if (sharing == 0) return true; + --sharing; + return false; + } +}; + + +struct ImageLoader : LoadModule +{ + static ColorSpace cs; //desired value + + float w = 0, h = 0; //default image size + Surface surface; + + ImageLoader(FileType type) : LoadModule(type) {} + + virtual bool animatable() { return false; } //true if this loader supports animation. + virtual Paint* paint() { return nullptr; } + + virtual Surface* bitmap() + { + if (surface.data) return &surface; + return nullptr; + } +}; + + +struct FontLoader : LoadModule +{ + float scale = 1.0f; + + FontLoader(FileType type) : LoadModule(type) {} + + virtual bool request(Shape* shape, char* text, bool italic = false) = 0; +}; + +#endif //_TVG_LOAD_MODULE_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgLoader.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgLoader.cpp new file mode 100644 index 00000000000..eb695b31d81 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgLoader.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include + +#include "tvgInlist.h" +#include "tvgLoader.h" +#include "tvgLock.h" + +#ifdef THORVG_SVG_LOADER_SUPPORT + #include "tvgSvgLoader.h" +#endif + +#ifdef THORVG_PNG_LOADER_SUPPORT + #include "tvgPngLoader.h" +#endif + +#ifdef THORVG_TVG_LOADER_SUPPORT + #include "tvgTvgLoader.h" +#endif + +#ifdef THORVG_JPG_LOADER_SUPPORT + #include "tvgJpgLoader.h" +#endif + +#ifdef THORVG_WEBP_LOADER_SUPPORT + #include "tvgWebpLoader.h" +#endif + +#ifdef THORVG_TTF_LOADER_SUPPORT + #include "tvgTtfLoader.h" +#endif + +#ifdef THORVG_LOTTIE_LOADER_SUPPORT + #include "tvgLottieLoader.h" +#endif + +#include "tvgRawLoader.h" + + +uintptr_t HASH_KEY(const char* data) +{ + return reinterpret_cast(data); +} + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +ColorSpace ImageLoader::cs = ColorSpace::ARGB8888; + +static Key key; +static Inlist _activeLoaders; + + +static LoadModule* _find(FileType type) +{ + switch(type) { + case FileType::Png: { +#ifdef THORVG_PNG_LOADER_SUPPORT + return new PngLoader; +#endif + break; + } + case FileType::Jpg: { +#ifdef THORVG_JPG_LOADER_SUPPORT + return new JpgLoader; +#endif + break; + } + case FileType::Webp: { +#ifdef THORVG_WEBP_LOADER_SUPPORT + return new WebpLoader; +#endif + break; + } + case FileType::Tvg: { +#ifdef THORVG_TVG_LOADER_SUPPORT + return new TvgLoader; +#endif + break; + } + case FileType::Svg: { +#ifdef THORVG_SVG_LOADER_SUPPORT + return new SvgLoader; +#endif + break; + } + case FileType::Ttf: { +#ifdef THORVG_TTF_LOADER_SUPPORT + return new TtfLoader; +#endif + break; + } + case FileType::Lottie: { +#ifdef THORVG_LOTTIE_LOADER_SUPPORT + return new LottieLoader; +#endif + break; + } + case FileType::Raw: { + return new RawLoader; + break; + } + default: { + break; + } + } + +#ifdef THORVG_LOG_ENABLED + const char *format; + switch(type) { + case FileType::Tvg: { + format = "TVG"; + break; + } + case FileType::Svg: { + format = "SVG"; + break; + } + case FileType::Ttf: { + format = "TTF"; + break; + } + case FileType::Lottie: { + format = "lottie(json)"; + break; + } + case FileType::Raw: { + format = "RAW"; + break; + } + case FileType::Png: { + format = "PNG"; + break; + } + case FileType::Jpg: { + format = "JPG"; + break; + } + case FileType::Webp: { + format = "WEBP"; + break; + } + default: { + format = "???"; + break; + } + } + TVGLOG("RENDERER", "%s format is not supported", format); +#endif + return nullptr; +} + + +static LoadModule* _findByPath(const string& path) +{ + auto ext = path.substr(path.find_last_of(".") + 1); + if (!ext.compare("tvg")) return _find(FileType::Tvg); + if (!ext.compare("svg")) return _find(FileType::Svg); + if (!ext.compare("json")) return _find(FileType::Lottie); + if (!ext.compare("png")) return _find(FileType::Png); + if (!ext.compare("jpg")) return _find(FileType::Jpg); + if (!ext.compare("webp")) return _find(FileType::Webp); + if (!ext.compare("ttf") || !ext.compare("ttc")) return _find(FileType::Ttf); + if (!ext.compare("otf") || !ext.compare("otc")) return _find(FileType::Ttf); + return nullptr; +} + + +static FileType _convert(const string& mimeType) +{ + auto type = FileType::Unknown; + + if (mimeType == "tvg") type = FileType::Tvg; + else if (mimeType == "svg" || mimeType == "svg+xml") type = FileType::Svg; + else if (mimeType == "ttf" || mimeType == "otf") type = FileType::Ttf; + else if (mimeType == "lottie") type = FileType::Lottie; + else if (mimeType == "raw") type = FileType::Raw; + else if (mimeType == "png") type = FileType::Png; + else if (mimeType == "jpg" || mimeType == "jpeg") type = FileType::Jpg; + else if (mimeType == "webp") type = FileType::Webp; + else TVGLOG("RENDERER", "Given mimetype is unknown = \"%s\".", mimeType.c_str()); + + return type; +} + + +static LoadModule* _findByType(const string& mimeType) +{ + return _find(_convert(mimeType)); +} + + +static LoadModule* _findFromCache(const string& path) +{ + ScopedLock lock(key); + + auto loader = _activeLoaders.head; + + while (loader) { + if (loader->pathcache && !strcmp(loader->hashpath, path.c_str())) { + ++loader->sharing; + return loader; + } + loader = loader->next; + } + return nullptr; +} + + +static LoadModule* _findFromCache(const char* data, uint32_t size, const string& mimeType) +{ + auto type = _convert(mimeType); + if (type == FileType::Unknown) return nullptr; + + ScopedLock lock(key); + auto loader = _activeLoaders.head; + + auto key = HASH_KEY(data); + + while (loader) { + if (loader->type == type && loader->hashkey == key) { + ++loader->sharing; + return loader; + } + loader = loader->next; + } + return nullptr; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +bool LoaderMgr::init() +{ + return true; +} + + +bool LoaderMgr::term() +{ + auto loader = _activeLoaders.head; + + //clean up the remained font loaders which is globally used. + while (loader && loader->type == FileType::Ttf) { + auto ret = loader->close(); + auto tmp = loader; + loader = loader->next; + _activeLoaders.remove(tmp); + if (ret) delete(tmp); + } + return true; +} + + +bool LoaderMgr::retrieve(LoadModule* loader) +{ + if (!loader) return false; + if (loader->close()) { + if (loader->cached()) { + ScopedLock lock(key); + _activeLoaders.remove(loader); + } + delete(loader); + } + return true; +} + + +LoadModule* LoaderMgr::loader(const string& path, bool* invalid) +{ + *invalid = false; + + //TODO: lottie is not sharable. + auto allowCache = true; + auto ext = path.substr(path.find_last_of(".") + 1); + if (!ext.compare("json")) allowCache = false; + + if (allowCache) { + if (auto loader = _findFromCache(path)) return loader; + } + + if (auto loader = _findByPath(path)) { + if (loader->open(path)) { + if (allowCache) { + loader->hashpath = strdup(path.c_str()); + loader->pathcache = true; + { + ScopedLock lock(key); + _activeLoaders.back(loader); + } + } + return loader; + } + delete(loader); + } + //Unkown MimeType. Try with the candidates in the order + for (int i = 0; i < static_cast(FileType::Raw); i++) { + if (auto loader = _find(static_cast(i))) { + if (loader->open(path)) { + if (allowCache) { + loader->hashpath = strdup(path.c_str()); + loader->pathcache = true; + { + ScopedLock lock(key); + _activeLoaders.back(loader); + } + } + return loader; + } + delete(loader); + } + } + *invalid = true; + return nullptr; +} + + +bool LoaderMgr::retrieve(const string& path) +{ + return retrieve(_findFromCache(path)); +} + + +LoadModule* LoaderMgr::loader(const char* key) +{ + auto loader = _activeLoaders.head; + + while (loader) { + if (loader->pathcache && strstr(loader->hashpath, key)) { + ++loader->sharing; + return loader; + } + loader = loader->next; + } + return nullptr; +} + + +LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mimeType, const string& rpath, bool copy) +{ + //Note that users could use the same data pointer with the different content. + //Thus caching is only valid for shareable. + auto allowCache = !copy; + + //TODO: lottie is not sharable. + if (allowCache) { + auto type = _convert(mimeType); + if (type == FileType::Lottie) allowCache = false; + } + + if (allowCache) { + if (auto loader = _findFromCache(data, size, mimeType)) return loader; + } + + //Try with the given MimeType + if (!mimeType.empty()) { + if (auto loader = _findByType(mimeType)) { + if (loader->open(data, size, rpath, copy)) { + if (allowCache) { + loader->hashkey = HASH_KEY(data); + ScopedLock lock(key); + _activeLoaders.back(loader); + } + return loader; + } else { + TVGLOG("LOADER", "Given mimetype \"%s\" seems incorrect or not supported.", mimeType.c_str()); + delete(loader); + } + } + } + //Unkown MimeType. Try with the candidates in the order + for (int i = 0; i < static_cast(FileType::Raw); i++) { + auto loader = _find(static_cast(i)); + if (loader) { + if (loader->open(data, size, rpath, copy)) { + if (allowCache) { + loader->hashkey = HASH_KEY(data); + ScopedLock lock(key); + _activeLoaders.back(loader); + } + return loader; + } + delete(loader); + } + } + return nullptr; +} + + +LoadModule* LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, bool premultiplied, bool copy) +{ + //Note that users could use the same data pointer with the different content. + //Thus caching is only valid for shareable. + if (!copy) { + //TODO: should we check premultiplied?? + if (auto loader = _findFromCache((const char*)(data), w * h, "raw")) return loader; + } + + //function is dedicated for raw images only + auto loader = new RawLoader; + if (loader->open(data, w, h, premultiplied, copy)) { + if (!copy) { + loader->hashkey = HASH_KEY((const char*)data); + ScopedLock lock(key); + _activeLoaders.back(loader); + } + return loader; + } + delete(loader); + return nullptr; +} \ No newline at end of file diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgLoader.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgLoader.h new file mode 100644 index 00000000000..86d36db1068 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgLoader.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOADER_H_ +#define _TVG_LOADER_H_ + +#include "tvgLoadModule.h" + +struct LoaderMgr +{ + static bool init(); + static bool term(); + static LoadModule* loader(const string& path, bool* invalid); + static LoadModule* loader(const char* data, uint32_t size, const string& mimeType, const string& rpath, bool copy); + static LoadModule* loader(const uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy); + static LoadModule* loader(const char* key); + static bool retrieve(const string& path); + static bool retrieve(LoadModule* loader); +}; + +#endif //_TVG_LOADER_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPaint.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPaint.cpp new file mode 100644 index 00000000000..95d481c3301 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPaint.cpp @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" +#include "tvgPaint.h" +#include "tvgShape.h" +#include "tvgPicture.h" +#include "tvgScene.h" +#include "tvgText.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +#define PAINT_METHOD(ret, METHOD) \ + switch (id) { \ + case TVG_CLASS_ID_SHAPE: ret = P((Shape*)paint)->METHOD; break; \ + case TVG_CLASS_ID_SCENE: ret = P((Scene*)paint)->METHOD; break; \ + case TVG_CLASS_ID_PICTURE: ret = P((Picture*)paint)->METHOD; break; \ + case TVG_CLASS_ID_TEXT: ret = P((Text*)paint)->METHOD; break; \ + default: ret = {}; \ + } + + + +static Result _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform, RenderTransform* rTransform, RenderRegion& viewport) +{ + /* Access Shape class by Paint is bad... but it's ok still it's an internal usage. */ + auto shape = static_cast(cmpTarget); + + //Rectangle Candidates? + const Point* pts; + auto ptsCnt = shape->pathCoords(&pts); + + //nothing to clip + if (ptsCnt == 0) return Result::InvalidArguments; + + if (ptsCnt != 4) return Result::InsufficientCondition; + + if (rTransform) rTransform->update(); + + //No rotation and no skewing + if (pTransform && (!mathRightAngle(&pTransform->m) || mathSkewed(&pTransform->m))) return Result::InsufficientCondition; + if (rTransform && (!mathRightAngle(&rTransform->m) || mathSkewed(&rTransform->m))) return Result::InsufficientCondition; + + //Perpendicular Rectangle? + auto pt1 = pts + 0; + auto pt2 = pts + 1; + auto pt3 = pts + 2; + auto pt4 = pts + 3; + + if ((mathEqual(pt1->x, pt2->x) && mathEqual(pt2->y, pt3->y) && mathEqual(pt3->x, pt4->x) && mathEqual(pt1->y, pt4->y)) || + (mathEqual(pt2->x, pt3->x) && mathEqual(pt1->y, pt2->y) && mathEqual(pt1->x, pt4->x) && mathEqual(pt3->y, pt4->y))) { + + auto v1 = *pt1; + auto v2 = *pt3; + + if (rTransform) { + mathMultiply(&v1, &rTransform->m); + mathMultiply(&v2, &rTransform->m); + } + + if (pTransform) { + mathMultiply(&v1, &pTransform->m); + mathMultiply(&v2, &pTransform->m); + } + + //sorting + if (v1.x > v2.x) { + auto tmp = v2.x; + v2.x = v1.x; + v1.x = tmp; + } + + if (v1.y > v2.y) { + auto tmp = v2.y; + v2.y = v1.y; + v1.y = tmp; + } + + viewport.x = static_cast(v1.x); + viewport.y = static_cast(v1.y); + viewport.w = static_cast(ceil(v2.x - viewport.x)); + viewport.h = static_cast(ceil(v2.y - viewport.y)); + + if (viewport.w < 0) viewport.w = 0; + if (viewport.h < 0) viewport.h = 0; + + return Result::Success; + } + return Result::InsufficientCondition; +} + + +RenderRegion Paint::Impl::bounds(RenderMethod* renderer) const +{ + RenderRegion ret; + PAINT_METHOD(ret, bounds(renderer)); + return ret; +} + + +Iterator* Paint::Impl::iterator() +{ + Iterator* ret; + PAINT_METHOD(ret, iterator()); + return ret; +} + + +Paint* Paint::Impl::duplicate() +{ + Paint* ret; + PAINT_METHOD(ret, duplicate()); + + //duplicate Transform + if (rTransform) { + ret->pImpl->rTransform = new RenderTransform(); + *ret->pImpl->rTransform = *rTransform; + ret->pImpl->renderFlag |= RenderUpdateFlag::Transform; + } + + ret->pImpl->opacity = opacity; + + if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method); + + return ret; +} + + +bool Paint::Impl::rotate(float degree) +{ + if (rTransform) { + if (mathEqual(degree, rTransform->degree)) return true; + } else { + if (mathZero(degree)) return true; + rTransform = new RenderTransform(); + } + rTransform->degree = degree; + if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform; + + return true; +} + + +bool Paint::Impl::scale(float factor) +{ + if (rTransform) { + if (mathEqual(factor, rTransform->scale)) return true; + } else { + if (mathEqual(factor, 1.0f)) return true; + rTransform = new RenderTransform(); + } + rTransform->scale = factor; + if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform; + + return true; +} + + +bool Paint::Impl::translate(float x, float y) +{ + if (rTransform) { + if (mathEqual(x, rTransform->x) && mathEqual(y, rTransform->y)) return true; + } else { + if (mathZero(x) && mathZero(y)) return true; + rTransform = new RenderTransform(); + } + rTransform->x = x; + rTransform->y = y; + if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform; + + return true; +} + + +bool Paint::Impl::render(RenderMethod* renderer) +{ + Compositor* cmp = nullptr; + + /* Note: only ClipPath is processed in update() step. + Create a composition image. */ + if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) { + RenderRegion region; + PAINT_METHOD(region, bounds(renderer)); + + if (MASK_REGION_MERGING(compData->method)) region.add(P(compData->target)->bounds(renderer)); + if (region.w == 0 || region.h == 0) return true; + cmp = renderer->target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method)); + if (renderer->beginComposite(cmp, CompositeMethod::None, 255)) { + compData->target->pImpl->render(renderer); + } + } + + if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity); + + renderer->blend(blendMethod); + + bool ret; + PAINT_METHOD(ret, render(renderer)); + + if (cmp) renderer->endComposite(cmp); + + return ret; +} + + +RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pTransform, Array& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) +{ + if (this->renderer != renderer) { + if (this->renderer) TVGERR("RENDERER", "paint's renderer has been changed!"); + renderer->ref(); + this->renderer = renderer; + } + + if (renderFlag & RenderUpdateFlag::Transform) { + if (!rTransform) return nullptr; + rTransform->update(); + } + + /* 1. Composition Pre Processing */ + RenderData trd = nullptr; //composite target render data + RenderRegion viewport; + Result compFastTrack = Result::InsufficientCondition; + bool childClipper = false; + + if (compData) { + auto target = compData->target; + auto method = compData->method; + target->pImpl->ctxFlag &= ~ContextFlag::FastTrack; //reset + + /* If the transformation has no rotational factors and the ClipPath/Alpha(InvAlpha)Masking involves a simple rectangle, + we can optimize by using the viewport instead of the regular ClipPath/AlphaMasking sequence for improved performance. */ + auto tryFastTrack = false; + if (target->identifier() == TVG_CLASS_ID_SHAPE) { + if (method == CompositeMethod::ClipPath) tryFastTrack = true; + else { + auto shape = static_cast(target); + uint8_t a; + shape->fillColor(nullptr, nullptr, nullptr, &a); + //no gradient fill & no compositions of the composition target. + if (!shape->fill() && !(PP(shape)->compData)) { + if (method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) tryFastTrack = true; + else if (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0)) tryFastTrack = true; + } + } + if (tryFastTrack) { + RenderRegion viewport2; + if ((compFastTrack = _compFastTrack(target, pTransform, target->pImpl->rTransform, viewport2)) == Result::Success) { + viewport = renderer->viewport(); + viewport2.intersect(viewport); + renderer->viewport(viewport2); + target->pImpl->ctxFlag |= ContextFlag::FastTrack; + } + } + } + if (compFastTrack == Result::InsufficientCondition) { + childClipper = compData->method == CompositeMethod::ClipPath ? true : false; + trd = target->pImpl->update(renderer, pTransform, clips, 255, pFlag, childClipper); + if (childClipper) clips.push(trd); + } + } + + /* 2. Main Update */ + auto newFlag = static_cast(pFlag | renderFlag); + renderFlag = RenderUpdateFlag::None; + opacity = MULTIPLY(opacity, this->opacity); + + RenderData rd = nullptr; + RenderTransform outTransform(pTransform, rTransform); + PAINT_METHOD(rd, update(renderer, &outTransform, clips, opacity, newFlag, clipper)); + + /* 3. Composition Post Processing */ + if (compFastTrack == Result::Success) renderer->viewport(viewport); + else if (childClipper) clips.pop(); + + return rd; +} + + +bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking) +{ + Matrix* m = nullptr; + bool ret; + + //Case: No transformed, quick return! + if (!transformed || !(m = this->transform())) { + PAINT_METHOD(ret, bounds(x, y, w, h, stroking)); + return ret; + } + + //Case: Transformed + auto tx = 0.0f; + auto ty = 0.0f; + auto tw = 0.0f; + auto th = 0.0f; + + PAINT_METHOD(ret, bounds(&tx, &ty, &tw, &th, stroking)); + + //Get vertices + Point pt[4] = {{tx, ty}, {tx + tw, ty}, {tx + tw, ty + th}, {tx, ty + th}}; + + //New bounding box + auto x1 = FLT_MAX; + auto y1 = FLT_MAX; + auto x2 = -FLT_MAX; + auto y2 = -FLT_MAX; + + //Compute the AABB after transformation + for (int i = 0; i < 4; i++) { + mathMultiply(&pt[i], m); + + if (pt[i].x < x1) x1 = pt[i].x; + if (pt[i].x > x2) x2 = pt[i].x; + if (pt[i].y < y1) y1 = pt[i].y; + if (pt[i].y > y2) y2 = pt[i].y; + } + + if (x) *x = x1; + if (y) *y = y1; + if (w) *w = x2 - x1; + if (h) *h = y2 - y1; + + return ret; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Paint :: Paint() : pImpl(new Impl(this)) +{ +} + + +Paint :: ~Paint() +{ + delete(pImpl); +} + + +Result Paint::rotate(float degree) noexcept +{ + if (pImpl->rotate(degree)) return Result::Success; + return Result::FailedAllocation; +} + + +Result Paint::scale(float factor) noexcept +{ + if (pImpl->scale(factor)) return Result::Success; + return Result::FailedAllocation; +} + + +Result Paint::translate(float x, float y) noexcept +{ + if (pImpl->translate(x, y)) return Result::Success; + return Result::FailedAllocation; +} + + +Result Paint::transform(const Matrix& m) noexcept +{ + if (pImpl->transform(m)) return Result::Success; + return Result::FailedAllocation; +} + + +Matrix Paint::transform() noexcept +{ + auto pTransform = pImpl->transform(); + if (pTransform) return *pTransform; + return {1, 0, 0, 0, 1, 0, 0, 0, 1}; +} + + +Result Paint::bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept +{ + if (pImpl->bounds(x, y, w, h, transformed, true)) return Result::Success; + return Result::InsufficientCondition; +} + + +Paint* Paint::duplicate() const noexcept +{ + return pImpl->duplicate(); +} + + +Result Paint::composite(std::unique_ptr target, CompositeMethod method) noexcept +{ + auto p = target.release(); + if (pImpl->composite(this, p, method)) return Result::Success; + delete(p); + return Result::InvalidArguments; +} + + +CompositeMethod Paint::composite(const Paint** target) const noexcept +{ + if (pImpl->compData) { + if (target) *target = pImpl->compData->target; + return pImpl->compData->method; + } else { + if (target) *target = nullptr; + return CompositeMethod::None; + } +} + + +Result Paint::opacity(uint8_t o) noexcept +{ + if (pImpl->opacity == o) return Result::Success; + + pImpl->opacity = o; + pImpl->renderFlag |= RenderUpdateFlag::Color; + + return Result::Success; +} + + +uint8_t Paint::opacity() const noexcept +{ + return pImpl->opacity; +} + + +uint32_t Paint::identifier() const noexcept +{ + return pImpl->id; +} + + +Result Paint::blend(BlendMethod method) const noexcept +{ + if (pImpl->blendMethod != method) { + pImpl->blendMethod = method; + pImpl->renderFlag |= RenderUpdateFlag::Blend; + } + + return Result::Success; +} + + +BlendMethod Paint::blend() const noexcept +{ + return pImpl->blendMethod; +} \ No newline at end of file diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPaint.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPaint.h new file mode 100644 index 00000000000..39bc24914e5 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPaint.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_PAINT_H_ +#define _TVG_PAINT_H_ + +#include "tvgRender.h" +#include "tvgMath.h" + +namespace tvg +{ + enum ContextFlag : uint8_t {Invalid = 0, FastTrack = 1}; + + struct Iterator + { + virtual ~Iterator() {} + virtual const Paint* next() = 0; + virtual uint32_t count() = 0; + virtual void begin() = 0; + }; + + struct Composite + { + Paint* target; + Paint* source; + CompositeMethod method; + }; + + struct Paint::Impl + { + Paint* paint = nullptr; + RenderTransform* rTransform = nullptr; + Composite* compData = nullptr; + RenderMethod* renderer = nullptr; + BlendMethod blendMethod = BlendMethod::Normal; //uint8_t + uint8_t renderFlag = RenderUpdateFlag::None; + uint8_t ctxFlag = ContextFlag::Invalid; + uint8_t id; + uint8_t opacity = 255; + uint8_t refCnt = 0; //reference count + + Impl(Paint* pnt) : paint(pnt) {} + + ~Impl() + { + if (compData) { + if (P(compData->target)->unref() == 0) delete(compData->target); + free(compData); + } + delete(rTransform); + if (renderer && (renderer->unref() == 0)) delete(renderer); + } + + uint8_t ref() + { + if (refCnt == 255) TVGERR("RENDERER", "Corrupted reference count!"); + return ++refCnt; + } + + uint8_t unref() + { + if (refCnt == 0) TVGERR("RENDERER", "Corrupted reference count!"); + return --refCnt; + } + + bool transform(const Matrix& m) + { + if (!rTransform) { + if (mathIdentity(&m)) return true; + rTransform = new RenderTransform(); + if (!rTransform) return false; + } + rTransform->override(m); + renderFlag |= RenderUpdateFlag::Transform; + + return true; + } + + Matrix* transform() + { + if (rTransform) { + rTransform->update(); + return &rTransform->m; + } + return nullptr; + } + + bool composite(Paint* source, Paint* target, CompositeMethod method) + { + //Invalid case + if ((!target && method != CompositeMethod::None) || (target && method == CompositeMethod::None)) return false; + + if (compData) { + P(compData->target)->unref(); + if ((compData->target != target) && P(compData->target)->refCnt == 0) { + delete(compData->target); + } + //Reset scenario + if (!target && method == CompositeMethod::None) { + free(compData); + compData = nullptr; + return true; + } + } else { + if (!target && method == CompositeMethod::None) return true; + compData = static_cast(calloc(1, sizeof(Composite))); + } + P(target)->ref(); + compData->target = target; + compData->source = source; + compData->method = method; + return true; + } + + RenderRegion bounds(RenderMethod* renderer) const; + Iterator* iterator(); + bool rotate(float degree); + bool scale(float factor); + bool translate(float x, float y); + bool bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking); + RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false); + bool render(RenderMethod* renderer); + Paint* duplicate(); + }; +} + +#endif //_TVG_PAINT_H_ \ No newline at end of file diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPicture.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPicture.cpp new file mode 100644 index 00000000000..2b707146805 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPicture.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgPicture.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +RenderUpdateFlag Picture::Impl::load() +{ + if (loader) { + if (!paint) { + paint = loader->paint(); + if (paint) { + if (w != loader->w || h != loader->h) { + if (!resizing) { + w = loader->w; + h = loader->h; + } + loader->resize(paint, w, h); + resizing = false; + } + return RenderUpdateFlag::None; + } + } else loader->sync(); + + if (!surface) { + if ((surface = loader->bitmap())) { + return RenderUpdateFlag::Image; + } + } + } + return RenderUpdateFlag::None; +} + + +bool Picture::Impl::needComposition(uint8_t opacity) +{ + //In this case, paint(scene) would try composition itself. + if (opacity < 255) return false; + + //Composition test + const Paint* target; + auto method = picture->composite(&target); + if (!target || method == tvg::CompositeMethod::ClipPath) return false; + if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return false; + + return true; +} + + +bool Picture::Impl::render(RenderMethod* renderer) +{ + bool ret = false; + if (surface) return renderer->renderImage(rd); + else if (paint) { + Compositor* cmp = nullptr; + if (needComp) { + cmp = renderer->target(bounds(renderer), renderer->colorSpace()); + renderer->beginComposite(cmp, CompositeMethod::None, 255); + } + ret = paint->pImpl->render(renderer); + if (cmp) renderer->endComposite(cmp); + } + return ret; +} + + +bool Picture::Impl::size(float w, float h) +{ + this->w = w; + this->h = h; + resizing = true; + return true; +} + + +RenderRegion Picture::Impl::bounds(RenderMethod* renderer) +{ + if (rd) return renderer->region(rd); + if (paint) return paint->pImpl->bounds(renderer); + return {0, 0, 0, 0}; +} + + +RenderTransform Picture::Impl::resizeTransform(const RenderTransform* pTransform) +{ + //Overriding Transformation by the desired image size + auto sx = w / loader->w; + auto sy = h / loader->h; + auto scale = sx < sy ? sx : sy; + + RenderTransform tmp; + tmp.m = {scale, 0, 0, 0, scale, 0, 0, 0, 1}; + + if (!pTransform) return tmp; + else return RenderTransform(pTransform, &tmp); +} + + +Result Picture::Impl::load(ImageLoader* loader) +{ + //Same resource has been loaded. + if (this->loader == loader) { + this->loader->sharing--; //make it sure the reference counting. + return Result::Success; + } else if (this->loader) { + LoaderMgr::retrieve(this->loader); + } + + this->loader = loader; + + if (!loader->read()) return Result::Unknown; + + this->w = loader->w; + this->h = loader->h; + + return Result::Success; +} + + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Picture::Picture() : pImpl(new Impl(this)) +{ + Paint::pImpl->id = TVG_CLASS_ID_PICTURE; +} + + +Picture::~Picture() +{ + delete(pImpl); +} + + +unique_ptr Picture::gen() noexcept +{ + return unique_ptr(new Picture); +} + + +uint32_t Picture::identifier() noexcept +{ + return TVG_CLASS_ID_PICTURE; +} + + +Result Picture::load(const std::string& path) noexcept +{ + if (path.empty()) return Result::InvalidArguments; + + return pImpl->load(path); +} + + +Result Picture::load(const char* data, uint32_t size, const string& mimeType, const string& rpath, bool copy) noexcept +{ + if (!data || size <= 0) return Result::InvalidArguments; + + return pImpl->load(data, size, mimeType, rpath, copy); +} + + +Result Picture::load(uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy) noexcept +{ + if (!data || w <= 0 || h <= 0) return Result::InvalidArguments; + + return pImpl->load(data, w, h, premultiplied, copy); +} + + +Result Picture::size(float w, float h) noexcept +{ + if (pImpl->size(w, h)) return Result::Success; + return Result::InsufficientCondition; +} + + +Result Picture::size(float* w, float* h) const noexcept +{ + if (!pImpl->loader) return Result::InsufficientCondition; + if (w) *w = pImpl->w; + if (h) *h = pImpl->h; + return Result::Success; +} + + +Result Picture::mesh(const Polygon* triangles, uint32_t triangleCnt) noexcept +{ + if (!triangles && triangleCnt > 0) return Result::InvalidArguments; + if (triangles && triangleCnt == 0) return Result::InvalidArguments; + + pImpl->mesh(triangles, triangleCnt); + return Result::Success; +} + + +uint32_t Picture::mesh(const Polygon** triangles) const noexcept +{ + if (triangles) *triangles = pImpl->rm.triangles; + return pImpl->rm.triangleCnt; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPicture.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPicture.h new file mode 100644 index 00000000000..c6f60c8f2ec --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgPicture.h @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_PICTURE_H_ +#define _TVG_PICTURE_H_ + +#include +#include "tvgPaint.h" +#include "tvgLoader.h" + + +struct PictureIterator : Iterator +{ + Paint* paint = nullptr; + Paint* ptr = nullptr; + + PictureIterator(Paint* p) : paint(p) {} + + const Paint* next() override + { + if (!ptr) ptr = paint; + else ptr = nullptr; + return ptr; + } + + uint32_t count() override + { + if (paint) return 1; + else return 0; + } + + void begin() override + { + ptr = nullptr; + } +}; + + +struct Picture::Impl +{ + ImageLoader* loader = nullptr; + + Paint* paint = nullptr; //vector picture uses + Surface* surface = nullptr; //bitmap picture uses + RenderData rd = nullptr; //engine data + float w = 0, h = 0; + RenderMesh rm; //mesh data + Picture* picture = nullptr; + bool resizing = false; + bool needComp = false; //need composition + + RenderTransform resizeTransform(const RenderTransform* pTransform); + bool needComposition(uint8_t opacity); + bool render(RenderMethod* renderer); + bool size(float w, float h); + RenderRegion bounds(RenderMethod* renderer); + Result load(ImageLoader* ploader); + + Impl(Picture* p) : picture(p) + { + } + + ~Impl() + { + LoaderMgr::retrieve(loader); + if (surface) { + if (auto renderer = PP(picture)->renderer) { + renderer->dispose(rd); + } + } + delete(paint); + } + + RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) + { + auto flag = load(); + + if (surface) { + auto transform = resizeTransform(pTransform); + rd = renderer->prepare(surface, &rm, rd, &transform, clips, opacity, static_cast(pFlag | flag)); + } else if (paint) { + if (resizing) { + loader->resize(paint, w, h); + resizing = false; + } + needComp = needComposition(opacity) ? true : false; + rd = paint->pImpl->update(renderer, pTransform, clips, opacity, static_cast(pFlag | flag), clipper); + } + return rd; + } + + bool bounds(float* x, float* y, float* w, float* h, bool stroking) + { + if (rm.triangleCnt > 0) { + auto triangles = rm.triangles; + auto min = triangles[0].vertex[0].pt; + auto max = triangles[0].vertex[0].pt; + + for (uint32_t i = 0; i < rm.triangleCnt; ++i) { + if (triangles[i].vertex[0].pt.x < min.x) min.x = triangles[i].vertex[0].pt.x; + else if (triangles[i].vertex[0].pt.x > max.x) max.x = triangles[i].vertex[0].pt.x; + if (triangles[i].vertex[0].pt.y < min.y) min.y = triangles[i].vertex[0].pt.y; + else if (triangles[i].vertex[0].pt.y > max.y) max.y = triangles[i].vertex[0].pt.y; + + if (triangles[i].vertex[1].pt.x < min.x) min.x = triangles[i].vertex[1].pt.x; + else if (triangles[i].vertex[1].pt.x > max.x) max.x = triangles[i].vertex[1].pt.x; + if (triangles[i].vertex[1].pt.y < min.y) min.y = triangles[i].vertex[1].pt.y; + else if (triangles[i].vertex[1].pt.y > max.y) max.y = triangles[i].vertex[1].pt.y; + + if (triangles[i].vertex[2].pt.x < min.x) min.x = triangles[i].vertex[2].pt.x; + else if (triangles[i].vertex[2].pt.x > max.x) max.x = triangles[i].vertex[2].pt.x; + if (triangles[i].vertex[2].pt.y < min.y) min.y = triangles[i].vertex[2].pt.y; + else if (triangles[i].vertex[2].pt.y > max.y) max.y = triangles[i].vertex[2].pt.y; + } + if (x) *x = min.x; + if (y) *y = min.y; + if (w) *w = max.x - min.x; + if (h) *h = max.y - min.y; + } else { + if (x) *x = 0; + if (y) *y = 0; + if (w) *w = this->w; + if (h) *h = this->h; + } + return true; + } + + Result load(const string& path) + { + if (paint || surface) return Result::InsufficientCondition; + + bool invalid; //Invalid Path + auto loader = static_cast(LoaderMgr::loader(path, &invalid)); + if (!loader) { + if (invalid) return Result::InvalidArguments; + return Result::NonSupport; + } + return load(loader); + } + + Result load(const char* data, uint32_t size, const string& mimeType, const string& rpath, bool copy) + { + if (paint || surface) return Result::InsufficientCondition; + auto loader = static_cast(LoaderMgr::loader(data, size, mimeType, rpath, copy)); + if (!loader) return Result::NonSupport; + return load(loader); + } + + Result load(uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy) + { + if (paint || surface) return Result::InsufficientCondition; + + auto loader = static_cast(LoaderMgr::loader(data, w, h, premultiplied, copy)); + if (!loader) return Result::FailedAllocation; + + return load(loader); + } + + void mesh(const Polygon* triangles, const uint32_t triangleCnt) + { + if (triangles && triangleCnt > 0) { + this->rm.triangleCnt = triangleCnt; + this->rm.triangles = (Polygon*)malloc(sizeof(Polygon) * triangleCnt); + memcpy(this->rm.triangles, triangles, sizeof(Polygon) * triangleCnt); + } else { + free(this->rm.triangles); + this->rm.triangles = nullptr; + this->rm.triangleCnt = 0; + } + } + + Paint* duplicate() + { + load(); + + auto ret = Picture::gen().release(); + auto dup = ret->pImpl; + + if (paint) dup->paint = paint->duplicate(); + + if (loader) { + dup->loader = loader; + ++dup->loader->sharing; + } + + dup->surface = surface; + dup->w = w; + dup->h = h; + dup->resizing = resizing; + + if (rm.triangleCnt > 0) { + dup->rm.triangleCnt = rm.triangleCnt; + dup->rm.triangles = (Polygon*)malloc(sizeof(Polygon) * rm.triangleCnt); + memcpy(dup->rm.triangles, rm.triangles, sizeof(Polygon) * rm.triangleCnt); + } + + return ret; + } + + Iterator* iterator() + { + load(); + return new PictureIterator(paint); + } + + uint32_t* data(uint32_t* w, uint32_t* h) + { + //Try it, If not loaded yet. + load(); + + if (loader) { + if (w) *w = static_cast(loader->w); + if (h) *h = static_cast(loader->h); + } else { + if (w) *w = 0; + if (h) *h = 0; + } + if (surface) return surface->buf32; + else return nullptr; + } + + RenderUpdateFlag load(); +}; + +#endif //_TVG_PICTURE_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgRender.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgRender.cpp new file mode 100644 index 00000000000..bdfb9f322eb --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgRender.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" +#include "tvgRender.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void RenderTransform::override(const Matrix& m) +{ + this->m = m; + overriding = true; +} + + +void RenderTransform::update() +{ + if (overriding) return; + + mathIdentity(&m); + + mathScale(&m, scale, scale); + + if (!mathZero(degree)) mathRotate(&m, degree); + + mathTranslate(&m, x, y); +} + + +RenderTransform::RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs) +{ + if (lhs && rhs) m = mathMultiply(&lhs->m, &rhs->m); + else if (lhs) m = lhs->m; + else if (rhs) m = rhs->m; + else mathIdentity(&m); +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgRender.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgRender.h new file mode 100644 index 00000000000..0caccd16018 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgRender.h @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_RENDER_H_ +#define _TVG_RENDER_H_ + +#include "tvgCommon.h" +#include "tvgArray.h" +#include "tvgLock.h" + +namespace tvg +{ + +using RenderData = void*; +using pixel_t = uint32_t; + +enum RenderUpdateFlag : uint8_t {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, Blend = 128, All = 255}; + +struct Surface; + +enum ColorSpace : uint8_t +{ + ABGR8888 = 0, //The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied. + ARGB8888, //The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied. + ABGR8888S, //The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied. + ARGB8888S, //The channels are joined in the order: alpha, red, green, blue. Colors are un-alpha-premultiplied. + Grayscale8, //One single channel data. + Unsupported //TODO: Change to the default, At the moment, we put it in the last to align with SwCanvas::Colorspace. +}; + +struct Surface +{ + union { + pixel_t* data = nullptr; //system based data pointer + uint32_t* buf32; //for explicit 32bits channels + uint8_t* buf8; //for explicit 8bits grayscale + }; + Key key; //a reserved lock for the thread safety + uint32_t stride = 0; + uint32_t w = 0, h = 0; + ColorSpace cs = ColorSpace::Unsupported; + uint8_t channelSize = 0; + bool premultiplied = false; //Alpha-premultiplied + + Surface() + { + } + + Surface(const Surface* rhs) + { + data = rhs->data; + stride = rhs->stride; + w = rhs->w; + h = rhs->h; + cs = rhs->cs; + channelSize = rhs->channelSize; + premultiplied = rhs->premultiplied; + } +}; + +struct Compositor +{ + CompositeMethod method; + uint8_t opacity; +}; + +struct RenderMesh +{ + Polygon* triangles = nullptr; + uint32_t triangleCnt = 0; + + ~RenderMesh() + { + free(triangles); + } +}; + +struct RenderRegion +{ + int32_t x, y, w, h; + + void intersect(const RenderRegion& rhs) + { + auto x1 = x + w; + auto y1 = y + h; + auto x2 = rhs.x + rhs.w; + auto y2 = rhs.y + rhs.h; + + x = (x > rhs.x) ? x : rhs.x; + y = (y > rhs.y) ? y : rhs.y; + w = ((x1 < x2) ? x1 : x2) - x; + h = ((y1 < y2) ? y1 : y2) - y; + + if (w < 0) w = 0; + if (h < 0) h = 0; + } + + void add(const RenderRegion& rhs) + { + if (rhs.x < x) { + w += (x - rhs.x); + x = rhs.x; + } + if (rhs.y < y) { + h += (y - rhs.y); + y = rhs.y; + } + if (rhs.x + rhs.w > x + w) w = (rhs.x + rhs.w) - x; + if (rhs.y + rhs.h > y + h) h = (rhs.y + rhs.h) - y; + } +}; + +struct RenderTransform +{ + Matrix m; //3x3 Matrix Elements + float x = 0.0f; + float y = 0.0f; + float degree = 0.0f; //rotation degree + float scale = 1.0f; //scale factor + bool overriding = false; //user transform? + + void update(); + void override(const Matrix& m); + + RenderTransform() {} + RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs); +}; + +struct RenderStroke +{ + float width = 0.0f; + uint8_t color[4] = {0, 0, 0, 0}; + Fill *fill = nullptr; + float* dashPattern = nullptr; + uint32_t dashCnt = 0; + float dashOffset = 0.0f; + float miterlimit = 4.0f; + StrokeCap cap = StrokeCap::Square; + StrokeJoin join = StrokeJoin::Bevel; + bool strokeFirst = false; + + struct { + float begin = 0.0f; + float end = 1.0f; + bool individual = false; + } trim; + + ~RenderStroke() + { + free(dashPattern); + delete(fill); + } +}; + +struct RenderShape +{ + struct + { + Array cmds; + Array pts; + } path; + + Fill *fill = nullptr; + uint8_t color[4] = {0, 0, 0, 0}; //r, g, b, a + RenderStroke *stroke = nullptr; + FillRule rule = FillRule::Winding; + + ~RenderShape() + { + delete(fill); + delete(stroke); + } + + void fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const + { + if (r) *r = color[0]; + if (g) *g = color[1]; + if (b) *b = color[2]; + if (a) *a = color[3]; + } + + float strokeWidth() const + { + if (!stroke) return 0; + return stroke->width; + } + + bool strokeTrim() const + { + if (!stroke) return false; + if (stroke->trim.begin == 0.0f && stroke->trim.end == 1.0f) return false; + if (stroke->trim.begin == 1.0f && stroke->trim.end == 0.0f) return false; + return true; + } + + bool strokeFill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const + { + if (!stroke) return false; + + if (r) *r = stroke->color[0]; + if (g) *g = stroke->color[1]; + if (b) *b = stroke->color[2]; + if (a) *a = stroke->color[3]; + + return true; + } + + const Fill* strokeFill() const + { + if (!stroke) return nullptr; + return stroke->fill; + } + + uint32_t strokeDash(const float** dashPattern, float* offset) const + { + if (!stroke) return 0; + if (dashPattern) *dashPattern = stroke->dashPattern; + if (offset) *offset = stroke->dashOffset; + return stroke->dashCnt; + } + + StrokeCap strokeCap() const + { + if (!stroke) return StrokeCap::Square; + return stroke->cap; + } + + StrokeJoin strokeJoin() const + { + if (!stroke) return StrokeJoin::Bevel; + return stroke->join; + } + + float strokeMiterlimit() const + { + if (!stroke) return 4.0f; + + return stroke->miterlimit;; + } +}; + +class RenderMethod +{ +private: + uint32_t refCnt = 0; //reference count + Key key; + +public: + uint32_t ref() + { + ScopedLock lock(key); + return (++refCnt); + } + + uint32_t unref() + { + ScopedLock lock(key); + return (--refCnt); + } + + virtual ~RenderMethod() {} + virtual RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0; + virtual RenderData prepare(const Array& scene, RenderData data, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) = 0; + virtual RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) = 0; + virtual bool preRender() = 0; + virtual bool renderShape(RenderData data) = 0; + virtual bool renderImage(RenderData data) = 0; + virtual bool postRender() = 0; + virtual void dispose(RenderData data) = 0; + virtual RenderRegion region(RenderData data) = 0; + virtual RenderRegion viewport() = 0; + virtual bool viewport(const RenderRegion& vp) = 0; + virtual bool blend(BlendMethod method) = 0; + virtual ColorSpace colorSpace() = 0; + + virtual bool clear() = 0; + virtual bool sync() = 0; + + virtual Compositor* target(const RenderRegion& region, ColorSpace cs) = 0; + virtual bool beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) = 0; + virtual bool endComposite(Compositor* cmp) = 0; +}; + +static inline bool MASK_REGION_MERGING(CompositeMethod method) +{ + switch(method) { + case CompositeMethod::AlphaMask: + case CompositeMethod::InvAlphaMask: + case CompositeMethod::LumaMask: + case CompositeMethod::InvLumaMask: + case CompositeMethod::SubtractMask: + case CompositeMethod::IntersectMask: + return false; + //these might expand the rendering region + case CompositeMethod::AddMask: + case CompositeMethod::DifferenceMask: + return true; + default: + TVGERR("RENDERER", "Unsupported Composite Method! = %d", (int)method); + return false; + } +} + +static inline uint8_t CHANNEL_SIZE(ColorSpace cs) +{ + switch(cs) { + case ColorSpace::ABGR8888: + case ColorSpace::ABGR8888S: + case ColorSpace::ARGB8888: + case ColorSpace::ARGB8888S: + return sizeof(uint32_t); + case ColorSpace::Grayscale8: + return sizeof(uint8_t); + case ColorSpace::Unsupported: + default: + TVGERR("RENDERER", "Unsupported Channel Size! = %d", (int)cs); + return 0; + } +} + +static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod* renderer, CompositeMethod method) +{ + switch(method) { + case CompositeMethod::AlphaMask: + case CompositeMethod::InvAlphaMask: + case CompositeMethod::AddMask: + case CompositeMethod::DifferenceMask: + case CompositeMethod::SubtractMask: + case CompositeMethod::IntersectMask: + return ColorSpace::Grayscale8; + //TODO: Optimize Luma/InvLuma colorspace to Grayscale8 + case CompositeMethod::LumaMask: + case CompositeMethod::InvLumaMask: + return renderer->colorSpace(); + default: + TVGERR("RENDERER", "Unsupported Composite Size! = %d", (int)method); + return ColorSpace::Unsupported; + } +} + +static inline uint8_t MULTIPLY(uint8_t c, uint8_t a) +{ + return (((c) * (a) + 0xff) >> 8); +} + + +} + +#endif //_TVG_RENDER_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgSaveModule.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgSaveModule.h new file mode 100644 index 00000000000..e610dca1b60 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgSaveModule.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_SAVE_MODULE_H_ +#define _TVG_SAVE_MODULE_H_ + +#include "tvgIteratorAccessor.h" + +namespace tvg +{ + +class SaveModule +{ +public: + virtual ~SaveModule() {} + + virtual bool save(Paint* paint, Paint* bg, const string& path, uint32_t quality) = 0; + virtual bool save(Animation* animation, Paint* bg, const string& path, uint32_t quality, uint32_t fps) = 0; + virtual bool close() = 0; +}; + +} + +#endif //_TVG_SAVE_MODULE_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgSaver.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgSaver.cpp new file mode 100644 index 00000000000..ac18dabec35 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgSaver.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgCommon.h" +#include "tvgSaveModule.h" +#include "tvgPaint.h" + +#ifdef THORVG_TVG_SAVER_SUPPORT + #include "tvgTvgSaver.h" +#endif +#ifdef THORVG_GIF_SAVER_SUPPORT + #include "tvgGifSaver.h" +#endif + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct Saver::Impl +{ + SaveModule* saveModule = nullptr; + Paint* bg = nullptr; + + ~Impl() + { + delete(saveModule); + delete(bg); + } +}; + + +static SaveModule* _find(FileType type) +{ + switch(type) { + case FileType::Tvg: { +#ifdef THORVG_TVG_SAVER_SUPPORT + return new TvgSaver; +#endif + break; + } + case FileType::Gif: { +#ifdef THORVG_GIF_SAVER_SUPPORT + return new GifSaver; +#endif + break; + } + default: { + break; + } + } + +#ifdef THORVG_LOG_ENABLED + const char *format; + switch(type) { + case FileType::Tvg: { + format = "TVG"; + break; + } + case FileType::Gif: { + format = "GIF"; + break; + } + default: { + format = "???"; + break; + } + } + TVGLOG("RENDERER", "%s format is not supported", format); +#endif + return nullptr; +} + + +static SaveModule* _find(const string& path) +{ + auto ext = path.substr(path.find_last_of(".") + 1); + if (!ext.compare("tvg")) { + return _find(FileType::Tvg); + } else if (!ext.compare("gif")) { + return _find(FileType::Gif); + } + return nullptr; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Saver::Saver() : pImpl(new Impl()) +{ +} + + +Saver::~Saver() +{ + delete(pImpl); +} + + +Result Saver::save(unique_ptr paint, const string& path, uint32_t quality) noexcept +{ + auto p = paint.release(); + if (!p) return Result::MemoryCorruption; + + //Already on saving an other resource. + if (pImpl->saveModule) { + if (P(p)->refCnt == 0) delete(p); + return Result::InsufficientCondition; + } + + if (auto saveModule = _find(path)) { + if (saveModule->save(p, pImpl->bg, path, quality)) { + pImpl->saveModule = saveModule; + return Result::Success; + } else { + if (P(p)->refCnt == 0) delete(p); + delete(saveModule); + return Result::Unknown; + } + } + if (P(p)->refCnt == 0) delete(p); + return Result::NonSupport; +} + + +Result Saver::background(unique_ptr paint) noexcept +{ + delete(pImpl->bg); + pImpl->bg = paint.release(); + + return Result::Success; +} + + +Result Saver::save(unique_ptr animation, const string& path, uint32_t quality, uint32_t fps) noexcept +{ + auto a = animation.release(); + if (!a) return Result::MemoryCorruption; + + //animation holds the picture, it must be 1 at the bottom. + auto remove = PP(a->picture())->refCnt <= 1 ? true : false; + + if (mathZero(a->totalFrame())) { + if (remove) delete(a); + return Result::InsufficientCondition; + } + + //Already on saving an other resource. + if (pImpl->saveModule) { + if (remove) delete(a); + return Result::InsufficientCondition; + } + + if (auto saveModule = _find(path)) { + if (saveModule->save(a, pImpl->bg, path, quality, fps)) { + pImpl->saveModule = saveModule; + return Result::Success; + } else { + if (remove) delete(a); + delete(saveModule); + return Result::Unknown; + } + } + if (remove) delete(a); + return Result::NonSupport; +} + + +Result Saver::sync() noexcept +{ + if (!pImpl->saveModule) return Result::InsufficientCondition; + pImpl->saveModule->close(); + delete(pImpl->saveModule); + pImpl->saveModule = nullptr; + + return Result::Success; +} + + +unique_ptr Saver::gen() noexcept +{ + return unique_ptr(new Saver); +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgScene.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgScene.cpp new file mode 100644 index 00000000000..7582f3f953d --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgScene.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgScene.h" + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Scene::Scene() : pImpl(new Impl(this)) +{ + Paint::pImpl->id = TVG_CLASS_ID_SCENE; +} + + +Scene::~Scene() +{ + delete(pImpl); +} + + +unique_ptr Scene::gen() noexcept +{ + return unique_ptr(new Scene); +} + + +uint32_t Scene::identifier() noexcept +{ + return TVG_CLASS_ID_SCENE; +} + + +Result Scene::push(unique_ptr paint) noexcept +{ + auto p = paint.release(); + if (!p) return Result::MemoryCorruption; + PP(p)->ref(); + pImpl->paints.push_back(p); + + return Result::Success; +} + + +Result Scene::clear(bool free) noexcept +{ + pImpl->clear(free); + + return Result::Success; +} + + +list& Scene::paints() noexcept +{ + return pImpl->paints; +} \ No newline at end of file diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgScene.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgScene.h new file mode 100644 index 00000000000..8b1981edfab --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgScene.h @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_SCENE_H_ +#define _TVG_SCENE_H_ + +#include +#include "tvgPaint.h" + + +struct SceneIterator : Iterator +{ + list* paints; + list::iterator itr; + + SceneIterator(list* p) : paints(p) + { + begin(); + } + + const Paint* next() override + { + if (itr == paints->end()) return nullptr; + auto paint = *itr; + ++itr; + return paint; + } + + uint32_t count() override + { + return paints->size(); + } + + void begin() override + { + itr = paints->begin(); + } +}; + +struct Scene::Impl +{ + list paints; + RenderData rd = nullptr; + Scene* scene = nullptr; + uint8_t opacity; //for composition + bool needComp = false; //composite or not + + Impl(Scene* s) : scene(s) + { + } + + ~Impl() + { + for (auto paint : paints) { + if (P(paint)->unref() == 0) delete(paint); + } + + if (auto renderer = PP(scene)->renderer) { + renderer->dispose(rd); + } + } + + bool needComposition(uint8_t opacity) + { + if (opacity == 0 || paints.empty()) return false; + + //Masking may require composition (even if opacity == 255) + auto compMethod = scene->composite(nullptr); + if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true; + + //Blending may require composition (even if opacity == 255) + if (scene->blend() != BlendMethod::Normal) return true; + + //Half translucent requires intermediate composition. + if (opacity == 255) return false; + + //If scene has several children or only scene, it may require composition. + //OPTIMIZE: the bitmap type of the picture would not need the composition. + //OPTIMIZE: a single paint of a scene would not need the composition. + if (paints.size() == 1 && paints.front()->identifier() == TVG_CLASS_ID_SHAPE) return false; + + return true; + } + + RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flag, bool clipper) + { + if ((needComp = needComposition(opacity))) { + /* Overriding opacity value. If this scene is half-translucent, + It must do intermeidate composition with that opacity value. */ + this->opacity = opacity; + opacity = 255; + } + + if (clipper) { + Array rds(paints.size()); + for (auto paint : paints) { + rds.push(paint->pImpl->update(renderer, transform, clips, opacity, flag, true)); + } + rd = renderer->prepare(rds, rd, transform, clips, opacity, flag); + return rd; + } else { + for (auto paint : paints) { + paint->pImpl->update(renderer, transform, clips, opacity, flag, false); + } + return nullptr; + } + } + + bool render(RenderMethod* renderer) + { + Compositor* cmp = nullptr; + auto ret = true; + + if (needComp) { + cmp = renderer->target(bounds(renderer), renderer->colorSpace()); + renderer->beginComposite(cmp, CompositeMethod::None, opacity); + } + + for (auto paint : paints) { + ret &= paint->pImpl->render(renderer); + } + + if (cmp) renderer->endComposite(cmp); + + return ret; + } + + RenderRegion bounds(RenderMethod* renderer) const + { + if (paints.empty()) return {0, 0, 0, 0}; + + int32_t x1 = INT32_MAX; + int32_t y1 = INT32_MAX; + int32_t x2 = 0; + int32_t y2 = 0; + + for (auto paint : paints) { + auto region = paint->pImpl->bounds(renderer); + + //Merge regions + if (region.x < x1) x1 = region.x; + if (x2 < region.x + region.w) x2 = (region.x + region.w); + if (region.y < y1) y1 = region.y; + if (y2 < region.y + region.h) y2 = (region.y + region.h); + } + + return {x1, y1, (x2 - x1), (y2 - y1)}; + } + + bool bounds(float* px, float* py, float* pw, float* ph, bool stroking) + { + if (paints.empty()) return false; + + auto x1 = FLT_MAX; + auto y1 = FLT_MAX; + auto x2 = -FLT_MAX; + auto y2 = -FLT_MAX; + + for (auto paint : paints) { + auto x = FLT_MAX; + auto y = FLT_MAX; + auto w = 0.0f; + auto h = 0.0f; + + if (!P(paint)->bounds(&x, &y, &w, &h, true, stroking)) continue; + + //Merge regions + if (x < x1) x1 = x; + if (x2 < x + w) x2 = (x + w); + if (y < y1) y1 = y; + if (y2 < y + h) y2 = (y + h); + } + + if (px) *px = x1; + if (py) *py = y1; + if (pw) *pw = (x2 - x1); + if (ph) *ph = (y2 - y1); + + return true; + } + + Paint* duplicate() + { + auto ret = Scene::gen().release(); + auto dup = ret->pImpl; + + for (auto paint : paints) { + auto cdup = paint->duplicate(); + P(cdup)->ref(); + dup->paints.push_back(cdup); + } + + return ret; + } + + void clear(bool free) + { + for (auto paint : paints) { + if (P(paint)->unref() == 0 && free) delete(paint); + } + paints.clear(); + } + + Iterator* iterator() + { + return new SceneIterator(&paints); + } +}; + +#endif //_TVG_SCENE_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgShape.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgShape.cpp new file mode 100644 index 00000000000..a6d09d9229d --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgShape.cpp @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" +#include "tvgShape.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Shape :: Shape() : pImpl(new Impl(this)) +{ + Paint::pImpl->id = TVG_CLASS_ID_SHAPE; +} + + +Shape :: ~Shape() +{ + delete(pImpl); +} + + +unique_ptr Shape::gen() noexcept +{ + return unique_ptr(new Shape); +} + + +uint32_t Shape::identifier() noexcept +{ + return TVG_CLASS_ID_SHAPE; +} + + +Result Shape::reset() noexcept +{ + pImpl->rs.path.cmds.clear(); + pImpl->rs.path.pts.clear(); + + pImpl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +uint32_t Shape::pathCommands(const PathCommand** cmds) const noexcept +{ + if (cmds) *cmds = pImpl->rs.path.cmds.data; + return pImpl->rs.path.cmds.count; +} + + +uint32_t Shape::pathCoords(const Point** pts) const noexcept +{ + if (pts) *pts = pImpl->rs.path.pts.data; + return pImpl->rs.path.pts.count; +} + + +Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept +{ + if (cmdCnt == 0 || ptsCnt == 0 || !cmds || !pts) return Result::InvalidArguments; + + pImpl->grow(cmdCnt, ptsCnt); + pImpl->append(cmds, cmdCnt, pts, ptsCnt); + + return Result::Success; +} + + +Result Shape::moveTo(float x, float y) noexcept +{ + pImpl->moveTo(x, y); + + return Result::Success; +} + + +Result Shape::lineTo(float x, float y) noexcept +{ + pImpl->lineTo(x, y); + + return Result::Success; +} + + +Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept +{ + pImpl->cubicTo(cx1, cy1, cx2, cy2, x, y); + + return Result::Success; +} + + +Result Shape::close() noexcept +{ + pImpl->close(); + + return Result::Success; +} + + +Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept +{ + auto rxKappa = rx * PATH_KAPPA; + auto ryKappa = ry * PATH_KAPPA; + + pImpl->grow(6, 13); + pImpl->moveTo(cx + rx, cy); + pImpl->cubicTo(cx + rx, cy + ryKappa, cx + rxKappa, cy + ry, cx, cy + ry); + pImpl->cubicTo(cx - rxKappa, cy + ry, cx - rx, cy + ryKappa, cx - rx, cy); + pImpl->cubicTo(cx - rx, cy - ryKappa, cx - rxKappa, cy - ry, cx, cy - ry); + pImpl->cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy); + pImpl->close(); + + return Result::Success; +} + +Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept +{ + //just circle + if (sweep >= 360.0f || sweep <= -360.0f) return appendCircle(cx, cy, radius, radius); + + const float arcPrecision = 1e-5f; + startAngle = mathDeg2Rad(startAngle); + sweep = mathDeg2Rad(sweep); + + auto nCurves = static_cast(fabsf(sweep / MATH_PI2)); + if (fabsf(sweep / MATH_PI2) - nCurves > arcPrecision) ++nCurves; + auto sweepSign = (sweep < 0 ? -1 : 1); + auto fract = fmodf(sweep, MATH_PI2); + fract = (fabsf(fract) < arcPrecision) ? MATH_PI2 * sweepSign : fract; + + //Start from here + Point start = {radius * cosf(startAngle), radius * sinf(startAngle)}; + + if (pie) { + pImpl->moveTo(cx, cy); + pImpl->lineTo(start.x + cx, start.y + cy); + } else { + pImpl->moveTo(start.x + cx, start.y + cy); + } + + for (int i = 0; i < nCurves; ++i) { + auto endAngle = startAngle + ((i != nCurves - 1) ? MATH_PI2 * sweepSign : fract); + Point end = {radius * cosf(endAngle), radius * sinf(endAngle)}; + + //variables needed to calculate bezier control points + + //get bezier control points using article: + //(http://itc.ktu.lt/index.php/ITC/article/view/11812/6479) + auto ax = start.x; + auto ay = start.y; + auto bx = end.x; + auto by = end.y; + auto q1 = ax * ax + ay * ay; + auto q2 = ax * bx + ay * by + q1; + auto k2 = (4.0f/3.0f) * ((sqrtf(2 * q1 * q2) - q2) / (ax * by - ay * bx)); + + start = end; //Next start point is the current end point + + end.x += cx; + end.y += cy; + + Point ctrl1 = {ax - k2 * ay + cx, ay + k2 * ax + cy}; + Point ctrl2 = {bx + k2 * by + cx, by - k2 * bx + cy}; + + pImpl->cubicTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, end.x, end.y); + + startAngle = endAngle; + } + + if (pie) pImpl->close(); + + return Result::Success; +} + + +Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry) noexcept +{ + auto halfW = w * 0.5f; + auto halfH = h * 0.5f; + + //clamping cornerRadius by minimum size + if (rx > halfW) rx = halfW; + if (ry > halfH) ry = halfH; + + //rectangle + if (rx == 0 && ry == 0) { + pImpl->grow(5, 4); + pImpl->moveTo(x, y); + pImpl->lineTo(x + w, y); + pImpl->lineTo(x + w, y + h); + pImpl->lineTo(x, y + h); + pImpl->close(); + //rounded rectangle or circle + } else { + auto hrx = rx * PATH_KAPPA; + auto hry = ry * PATH_KAPPA; + pImpl->grow(10, 17); + pImpl->moveTo(x + rx, y); + pImpl->lineTo(x + w - rx, y); + pImpl->cubicTo(x + w - rx + hrx, y, x + w, y + ry - hry, x + w, y + ry); + pImpl->lineTo(x + w, y + h - ry); + pImpl->cubicTo(x + w, y + h - ry + hry, x + w - rx + hrx, y + h, x + w - rx, y + h); + pImpl->lineTo(x + rx, y + h); + pImpl->cubicTo(x + rx - hrx, y + h, x, y + h - ry + hry, x, y + h - ry); + pImpl->lineTo(x, y + ry); + pImpl->cubicTo(x, y + ry - hry, x + rx - hrx, y, x + rx, y); + pImpl->close(); + } + + return Result::Success; +} + + +Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept +{ + if (pImpl->rs.fill) { + delete(pImpl->rs.fill); + pImpl->rs.fill = nullptr; + pImpl->flag |= RenderUpdateFlag::Gradient; + } + + if (r == pImpl->rs.color[0] && g == pImpl->rs.color[1] && b == pImpl->rs.color[2] && a == pImpl->rs.color[3]) return Result::Success; + + pImpl->rs.color[0] = r; + pImpl->rs.color[1] = g; + pImpl->rs.color[2] = b; + pImpl->rs.color[3] = a; + pImpl->flag |= RenderUpdateFlag::Color; + + return Result::Success; +} + + +Result Shape::fill(unique_ptr f) noexcept +{ + auto p = f.release(); + if (!p) return Result::MemoryCorruption; + + if (pImpl->rs.fill && pImpl->rs.fill != p) delete(pImpl->rs.fill); + pImpl->rs.fill = p; + pImpl->flag |= RenderUpdateFlag::Gradient; + + return Result::Success; +} + + +Result Shape::fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept +{ + pImpl->rs.fillColor(r, g, b, a); + + return Result::Success; +} + + +const Fill* Shape::fill() const noexcept +{ + return pImpl->rs.fill; +} + + +Result Shape::order(bool strokeFirst) noexcept +{ + if (!pImpl->strokeFirst(strokeFirst)) return Result::FailedAllocation; + + return Result::Success; +} + + +Result Shape::strokeWidth(float width) noexcept +{ + if (!pImpl->strokeWidth(width)) return Result::FailedAllocation; + + return Result::Success; +} + + +float Shape::strokeWidth() const noexcept +{ + return pImpl->rs.strokeWidth(); +} + + +Result Shape::strokeFill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept +{ + if (!pImpl->strokeFill(r, g, b, a)) return Result::FailedAllocation; + + return Result::Success; +} + + +Result Shape::strokeFill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept +{ + if (!pImpl->rs.strokeFill(r, g, b, a)) return Result::InsufficientCondition; + + return Result::Success; +} + + +Result Shape::strokeFill(unique_ptr f) noexcept +{ + return pImpl->strokeFill(std::move(f)); +} + + +const Fill* Shape::strokeFill() const noexcept +{ + return pImpl->rs.strokeFill(); +} + + +Result Shape::strokeDash(const float* dashPattern, uint32_t cnt, float offset) noexcept +{ + return pImpl->strokeDash(dashPattern, cnt, offset); +} + + +uint32_t Shape::strokeDash(const float** dashPattern, float* offset) const noexcept +{ + return pImpl->rs.strokeDash(dashPattern, offset); +} + + +Result Shape::strokeCap(StrokeCap cap) noexcept +{ + if (!pImpl->strokeCap(cap)) return Result::FailedAllocation; + + return Result::Success; +} + + +Result Shape::strokeJoin(StrokeJoin join) noexcept +{ + if (!pImpl->strokeJoin(join)) return Result::FailedAllocation; + + return Result::Success; +} + +Result Shape::strokeMiterlimit(float miterlimit) noexcept +{ + // https://www.w3.org/TR/SVG2/painting.html#LineJoin + // - A negative value for stroke-miterlimit must be treated as an illegal value. + if (miterlimit < 0.0f) return Result::NonSupport; + // TODO Find out a reasonable max value. + if (!pImpl->strokeMiterlimit(miterlimit)) return Result::FailedAllocation; + + return Result::Success; +} + + +StrokeCap Shape::strokeCap() const noexcept +{ + return pImpl->rs.strokeCap(); +} + + +StrokeJoin Shape::strokeJoin() const noexcept +{ + return pImpl->rs.strokeJoin(); +} + +float Shape::strokeMiterlimit() const noexcept +{ + return pImpl->rs.strokeMiterlimit(); +} + + +Result Shape::fill(FillRule r) noexcept +{ + pImpl->rs.rule = r; + + return Result::Success; +} + + +FillRule Shape::fillRule() const noexcept +{ + return pImpl->rs.rule; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgShape.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgShape.h new file mode 100644 index 00000000000..937c7e34026 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgShape.h @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_SHAPE_H_ +#define _TVG_SHAPE_H_ + +#include +#include "tvgMath.h" +#include "tvgPaint.h" + + +struct Shape::Impl +{ + RenderShape rs; //shape data + RenderData rd = nullptr; //engine data + Shape* shape; + uint8_t flag = RenderUpdateFlag::None; + uint8_t opacity; //for composition + bool needComp = false; //composite or not + + Impl(Shape* s) : shape(s) + { + } + + ~Impl() + { + if (auto renderer = PP(shape)->renderer) { + renderer->dispose(rd); + } + } + + bool render(RenderMethod* renderer) + { + Compositor* cmp = nullptr; + bool ret; + + if (needComp) { + cmp = renderer->target(bounds(renderer), renderer->colorSpace()); + renderer->beginComposite(cmp, CompositeMethod::None, opacity); + } + ret = renderer->renderShape(rd); + if (cmp) renderer->endComposite(cmp); + return ret; + } + + bool needComposition(uint8_t opacity) + { + if (opacity == 0) return false; + + //Shape composition is only necessary when stroking & fill are valid. + if (!rs.stroke || rs.stroke->width < FLT_EPSILON || (!rs.stroke->fill && rs.stroke->color[3] == 0)) return false; + if (!rs.fill && rs.color[3] == 0) return false; + + //translucent fill & stroke + if (opacity < 255) return true; + + //Composition test + const Paint* target; + auto method = shape->composite(&target); + if (!target || method == CompositeMethod::ClipPath) return false; + if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) { + if (target->identifier() == TVG_CLASS_ID_SHAPE) { + auto shape = static_cast(target); + if (!shape->fill()) { + uint8_t r, g, b, a; + shape->fillColor(&r, &g, &b, &a); + if (a == 0 || a == 255) { + if (method == CompositeMethod::LumaMask || method == CompositeMethod::InvLumaMask) { + if ((r == 255 && g == 255 && b == 255) || (r == 0 && g == 0 && b == 0)) return false; + } else return false; + } + } + } + } + + return true; + } + + RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) + { + if ((needComp = needComposition(opacity))) { + /* Overriding opacity value. If this scene is half-translucent, + It must do intermeidate composition with that opacity value. */ + this->opacity = opacity; + opacity = 255; + } + + rd = renderer->prepare(rs, rd, transform, clips, opacity, static_cast(pFlag | flag), clipper); + flag = RenderUpdateFlag::None; + return rd; + } + + RenderRegion bounds(RenderMethod* renderer) + { + return renderer->region(rd); + } + + bool bounds(float* x, float* y, float* w, float* h, bool stroking) + { + //Path bounding size + if (rs.path.pts.count > 0 ) { + auto pts = rs.path.pts.begin(); + Point min = { pts->x, pts->y }; + Point max = { pts->x, pts->y }; + + for (auto pts2 = pts + 1; pts2 < rs.path.pts.end(); ++pts2) { + if (pts2->x < min.x) min.x = pts2->x; + if (pts2->y < min.y) min.y = pts2->y; + if (pts2->x > max.x) max.x = pts2->x; + if (pts2->y > max.y) max.y = pts2->y; + } + + if (x) *x = min.x; + if (y) *y = min.y; + if (w) *w = max.x - min.x; + if (h) *h = max.y - min.y; + } + + //Stroke feathering + if (stroking && rs.stroke) { + if (x) *x -= rs.stroke->width * 0.5f; + if (y) *y -= rs.stroke->width * 0.5f; + if (w) *w += rs.stroke->width; + if (h) *h += rs.stroke->width; + } + return rs.path.pts.count > 0 ? true : false; + } + + void reserveCmd(uint32_t cmdCnt) + { + rs.path.cmds.reserve(cmdCnt); + } + + void reservePts(uint32_t ptsCnt) + { + rs.path.pts.reserve(ptsCnt); + } + + void grow(uint32_t cmdCnt, uint32_t ptsCnt) + { + rs.path.cmds.grow(cmdCnt); + rs.path.pts.grow(ptsCnt); + } + + void append(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) + { + memcpy(rs.path.cmds.end(), cmds, sizeof(PathCommand) * cmdCnt); + memcpy(rs.path.pts.end(), pts, sizeof(Point) * ptsCnt); + rs.path.cmds.count += cmdCnt; + rs.path.pts.count += ptsCnt; + + flag |= RenderUpdateFlag::Path; + } + + void moveTo(float x, float y) + { + rs.path.cmds.push(PathCommand::MoveTo); + rs.path.pts.push({x, y}); + + flag |= RenderUpdateFlag::Path; + } + + void lineTo(float x, float y) + { + rs.path.cmds.push(PathCommand::LineTo); + rs.path.pts.push({x, y}); + + flag |= RenderUpdateFlag::Path; + } + + void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) + { + rs.path.cmds.push(PathCommand::CubicTo); + rs.path.pts.push({cx1, cy1}); + rs.path.pts.push({cx2, cy2}); + rs.path.pts.push({x, y}); + + flag |= RenderUpdateFlag::Path; + } + + void close() + { + //Don't close multiple times. + if (rs.path.cmds.count > 0 && rs.path.cmds.last() == PathCommand::Close) return; + + rs.path.cmds.push(PathCommand::Close); + + flag |= RenderUpdateFlag::Path; + } + + bool strokeWidth(float width) + { + if (!rs.stroke) rs.stroke = new RenderStroke(); + rs.stroke->width = width; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeTrim(float begin, float end, bool individual) + { + if (!rs.stroke) { + if (begin == 0.0f && end == 1.0f) return true; + rs.stroke = new RenderStroke(); + } + + if (mathEqual(rs.stroke->trim.begin, begin) && mathEqual(rs.stroke->trim.end, end)) return true; + + rs.stroke->trim.begin = begin; + rs.stroke->trim.end = end; + rs.stroke->trim.individual = individual; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeCap(StrokeCap cap) + { + if (!rs.stroke) rs.stroke = new RenderStroke(); + rs.stroke->cap = cap; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeJoin(StrokeJoin join) + { + if (!rs.stroke) rs.stroke = new RenderStroke(); + rs.stroke->join = join; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeMiterlimit(float miterlimit) + { + if (!rs.stroke) rs.stroke = new RenderStroke(); + rs.stroke->miterlimit = miterlimit; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeFill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + { + if (!rs.stroke) rs.stroke = new RenderStroke(); + if (rs.stroke->fill) { + delete(rs.stroke->fill); + rs.stroke->fill = nullptr; + flag |= RenderUpdateFlag::GradientStroke; + } + + rs.stroke->color[0] = r; + rs.stroke->color[1] = g; + rs.stroke->color[2] = b; + rs.stroke->color[3] = a; + + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + Result strokeFill(unique_ptr f) + { + auto p = f.release(); + if (!p) return Result::MemoryCorruption; + + if (!rs.stroke) rs.stroke = new RenderStroke(); + if (rs.stroke->fill && rs.stroke->fill != p) delete(rs.stroke->fill); + rs.stroke->fill = p; + + flag |= RenderUpdateFlag::Stroke; + flag |= RenderUpdateFlag::GradientStroke; + + return Result::Success; + } + + Result strokeDash(const float* pattern, uint32_t cnt, float offset) + { + if ((cnt == 1) || (!pattern && cnt > 0) || (pattern && cnt == 0)) { + return Result::InvalidArguments; + } + + for (uint32_t i = 0; i < cnt; i++) { + if (pattern[i] < FLT_EPSILON) return Result::InvalidArguments; + } + + //Reset dash + if (!pattern && cnt == 0) { + free(rs.stroke->dashPattern); + rs.stroke->dashPattern = nullptr; + } else { + if (!rs.stroke) rs.stroke = new RenderStroke(); + if (rs.stroke->dashCnt != cnt) { + free(rs.stroke->dashPattern); + rs.stroke->dashPattern = nullptr; + } + if (!rs.stroke->dashPattern) { + rs.stroke->dashPattern = static_cast(malloc(sizeof(float) * cnt)); + if (!rs.stroke->dashPattern) return Result::FailedAllocation; + } + for (uint32_t i = 0; i < cnt; ++i) { + rs.stroke->dashPattern[i] = pattern[i]; + } + } + rs.stroke->dashCnt = cnt; + rs.stroke->dashOffset = offset; + flag |= RenderUpdateFlag::Stroke; + + return Result::Success; + } + + bool strokeFirst() + { + if (!rs.stroke) return true; + return rs.stroke->strokeFirst; + } + + bool strokeFirst(bool strokeFirst) + { + if (!rs.stroke) rs.stroke = new RenderStroke(); + rs.stroke->strokeFirst = strokeFirst; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + void update(RenderUpdateFlag flag) + { + this->flag |= flag; + } + + Paint* duplicate() + { + auto ret = Shape::gen().release(); + auto dup = ret->pImpl; + + dup->rs.rule = rs.rule; + + //Color + memcpy(dup->rs.color, rs.color, sizeof(rs.color)); + dup->flag = RenderUpdateFlag::Color; + + //Path + if (rs.path.cmds.count > 0 && rs.path.pts.count > 0) { + dup->rs.path.cmds = rs.path.cmds; + dup->rs.path.pts = rs.path.pts; + dup->flag |= RenderUpdateFlag::Path; + } + + //Stroke + if (rs.stroke) { + dup->rs.stroke = new RenderStroke(); + *dup->rs.stroke = *rs.stroke; + memcpy(dup->rs.stroke->color, rs.stroke->color, sizeof(rs.stroke->color)); + if (rs.stroke->dashCnt > 0) { + dup->rs.stroke->dashPattern = static_cast(malloc(sizeof(float) * rs.stroke->dashCnt)); + memcpy(dup->rs.stroke->dashPattern, rs.stroke->dashPattern, sizeof(float) * rs.stroke->dashCnt); + } + if (rs.stroke->fill) { + dup->rs.stroke->fill = rs.stroke->fill->duplicate(); + dup->flag |= RenderUpdateFlag::GradientStroke; + } + dup->flag |= RenderUpdateFlag::Stroke; + } + + //Fill + if (rs.fill) { + dup->rs.fill = rs.fill->duplicate(); + dup->flag |= RenderUpdateFlag::Gradient; + } + + return ret; + } + + Iterator* iterator() + { + return nullptr; + } +}; + +#endif //_TVG_SHAPE_H_ diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgSwCanvas.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgSwCanvas.cpp new file mode 100644 index 00000000000..d154a600d59 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgSwCanvas.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgCanvas.h" +#include "tvgLoadModule.h" + +#ifdef THORVG_SW_RASTER_SUPPORT + #include "tvgSwRenderer.h" +#else + class SwRenderer : public RenderMethod + { + //Non Supported. Dummy Class */ + }; +#endif + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct SwCanvas::Impl +{ +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +#ifdef THORVG_SW_RASTER_SUPPORT +SwCanvas::SwCanvas() : Canvas(SwRenderer::gen()), pImpl(new Impl) +#else +SwCanvas::SwCanvas() : Canvas(nullptr), pImpl(new Impl) +#endif +{ +} + + +SwCanvas::~SwCanvas() +{ + delete(pImpl); +} + + +Result SwCanvas::mempool(MempoolPolicy policy) noexcept +{ +#ifdef THORVG_SW_RASTER_SUPPORT + //We know renderer type, avoid dynamic_cast for performance. + auto renderer = static_cast(Canvas::pImpl->renderer); + if (!renderer) return Result::MemoryCorruption; + + //It can't change the policy during the running. + if (!Canvas::pImpl->paints.empty()) return Result::InsufficientCondition; + + if (policy == MempoolPolicy::Individual) renderer->mempool(false); + else renderer->mempool(true); + + return Result::Success; +#endif + return Result::NonSupport; +} + + +Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept +{ +#ifdef THORVG_SW_RASTER_SUPPORT + //We know renderer type, avoid dynamic_cast for performance. + auto renderer = static_cast(Canvas::pImpl->renderer); + if (!renderer) return Result::MemoryCorruption; + + if (!renderer->target(buffer, stride, w, h, static_cast(cs))) return Result::InvalidArguments; + + //Paints must be updated again with this new target. + Canvas::pImpl->needRefresh(); + + //FIXME: The value must be associated with an individual canvas instance. + ImageLoader::cs = static_cast(cs); + + return Result::Success; +#endif + return Result::NonSupport; +} + + +unique_ptr SwCanvas::gen() noexcept +{ +#ifdef THORVG_SW_RASTER_SUPPORT + if (SwRenderer::init() <= 0) return nullptr; + return unique_ptr(new SwCanvas); +#endif + return nullptr; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgTaskScheduler.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgTaskScheduler.cpp new file mode 100644 index 00000000000..d4932772975 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgTaskScheduler.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgArray.h" +#include "tvgInlist.h" +#include "tvgTaskScheduler.h" + +#ifdef THORVG_THREAD_SUPPORT + #include + #include +#endif + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +namespace tvg { + +struct TaskSchedulerImpl; +static TaskSchedulerImpl* inst = nullptr; + +#ifdef THORVG_THREAD_SUPPORT + +static thread_local bool _async = true; + +struct TaskQueue { + Inlist taskDeque; + mutex mtx; + condition_variable ready; + bool done = false; + + bool tryPop(Task** task) + { + unique_lock lock{mtx, try_to_lock}; + if (!lock || taskDeque.empty()) return false; + *task = taskDeque.front(); + return true; + } + + bool tryPush(Task* task) + { + { + unique_lock lock{mtx, try_to_lock}; + if (!lock) return false; + taskDeque.back(task); + } + ready.notify_one(); + return true; + } + + void complete() + { + { + lock_guard lock{mtx}; + done = true; + } + ready.notify_all(); + } + + bool pop(Task** task) + { + unique_lock lock{mtx}; + + while (taskDeque.empty() && !done) { + ready.wait(lock); + } + + if (taskDeque.empty()) return false; + + *task = taskDeque.front(); + return true; + } + + void push(Task* task) + { + { + lock_guard lock{mtx}; + taskDeque.back(task); + } + ready.notify_one(); + } +}; + + +struct TaskSchedulerImpl +{ + Array threads; + Array taskQueues; + atomic idx{0}; + + TaskSchedulerImpl(uint32_t threadCnt) + { + threads.reserve(threadCnt); + taskQueues.reserve(threadCnt); + + for (uint32_t i = 0; i < threadCnt; ++i) { + taskQueues.push(new TaskQueue); + threads.push(new thread); + } + for (uint32_t i = 0; i < threadCnt; ++i) { + *threads.data[i] = thread([&, i] { run(i); }); + } + } + + ~TaskSchedulerImpl() + { + for (auto tq = taskQueues.begin(); tq < taskQueues.end(); ++tq) { + (*tq)->complete(); + } + for (auto thread = threads.begin(); thread < threads.end(); ++thread) { + (*thread)->join(); + delete(*thread); + } + for (auto tq = taskQueues.begin(); tq < taskQueues.end(); ++tq) { + delete(*tq); + } + } + + void run(unsigned i) + { + Task* task; + + //Thread Loop + while (true) { + auto success = false; + for (uint32_t x = 0; x < threads.count * 2; ++x) { + if (taskQueues[(i + x) % threads.count]->tryPop(&task)) { + success = true; + break; + } + } + + if (!success && !taskQueues[i]->pop(&task)) break; + (*task)(i + 1); + } + } + + void request(Task* task) + { + //Async + if (threads.count > 0 && _async) { + task->prepare(); + auto i = idx++; + for (uint32_t n = 0; n < threads.count; ++n) { + if (taskQueues[(i + n) % threads.count]->tryPush(task)) return; + } + taskQueues[i % threads.count]->push(task); + //Sync + } else { + task->run(0); + } + } + + uint32_t threadCnt() + { + return threads.count; + } +}; + +#else //THORVG_THREAD_SUPPORT + +static bool _async = true; + +struct TaskSchedulerImpl +{ + TaskSchedulerImpl(TVG_UNUSED uint32_t threadCnt) {} + void request(Task* task) { task->run(0); } + uint32_t threadCnt() { return 0; } +}; + +#endif //THORVG_THREAD_SUPPORT + +} //namespace + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void TaskScheduler::init(uint32_t threads) +{ + if (inst) return; + inst = new TaskSchedulerImpl(threads); +} + + +void TaskScheduler::term() +{ + delete(inst); + inst = nullptr; +} + + +void TaskScheduler::request(Task* task) +{ + if (inst) inst->request(task); +} + + +uint32_t TaskScheduler::threads() +{ + if (inst) return inst->threadCnt(); + return 0; +} + + +void TaskScheduler::async(bool on) +{ + //toggle async tasking for each thread on/off + _async = on; +} \ No newline at end of file diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgTaskScheduler.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgTaskScheduler.h new file mode 100644 index 00000000000..fb9de21cceb --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgTaskScheduler.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_TASK_SCHEDULER_H_ +#define _TVG_TASK_SCHEDULER_H_ + +#include +#include + +#include "tvgCommon.h" +#include "tvgInlist.h" + +namespace tvg { + +#ifdef THORVG_THREAD_SUPPORT + +struct Task +{ +private: + mutex mtx; + condition_variable cv; + bool ready = true; + bool pending = false; + +public: + INLIST_ITEM(Task); + + virtual ~Task() = default; + + void done() + { + if (!pending) return; + + unique_lock lock(mtx); + while (!ready) cv.wait(lock); + pending = false; + } + +protected: + virtual void run(unsigned tid) = 0; + +private: + void operator()(unsigned tid) + { + run(tid); + + lock_guard lock(mtx); + ready = true; + cv.notify_one(); + } + + void prepare() + { + ready = false; + pending = true; + } + + friend struct TaskSchedulerImpl; +}; + +#else //THORVG_THREAD_SUPPORT + +struct Task +{ +public: + INLIST_ITEM(Task); + + virtual ~Task() = default; + void done() {} + +protected: + virtual void run(unsigned tid) = 0; + +private: + friend struct TaskSchedulerImpl; +}; + +#endif //THORVG_THREAD_SUPPORT + + +struct TaskScheduler +{ + static uint32_t threads(); + static void init(uint32_t threads); + static void term(); + static void request(Task* task); + static void async(bool on); +}; + +} //namespace + +#endif //_TVG_TASK_SCHEDULER_H_ + \ No newline at end of file diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgText.cpp b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgText.cpp new file mode 100644 index 00000000000..1fe244c11d3 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgText.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include "tvgText.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +Text::Text() : pImpl(new Impl) +{ + Paint::pImpl->id = TVG_CLASS_ID_TEXT; +} + + +Text::~Text() +{ + delete(pImpl); +} + + +Result Text::text(const char* text) noexcept +{ + return pImpl->text(text); +} + + +Result Text::font(const char* name, float size, const char* style) noexcept +{ + return pImpl->font(name, size, style); +} + + +Result Text::load(const std::string& path) noexcept +{ + bool invalid; //invalid path + if (!LoaderMgr::loader(path, &invalid)) { + if (invalid) return Result::InvalidArguments; + else return Result::NonSupport; + } + + return Result::Success; +} + + +Result Text::unload(const std::string& path) noexcept +{ + if (LoaderMgr::retrieve(path)) return Result::Success; + return Result::InsufficientCondition; +} + + +Result Text::fill(uint8_t r, uint8_t g, uint8_t b) noexcept +{ + if (!pImpl->paint) return Result::InsufficientCondition; + + return pImpl->fill(r, g, b); +} + + +Result Text::fill(unique_ptr f) noexcept +{ + if (!pImpl->paint) return Result::InsufficientCondition; + + auto p = f.release(); + if (!p) return Result::MemoryCorruption; + + return pImpl->fill(p); +} + + +unique_ptr Text::gen() noexcept +{ + return unique_ptr(new Text); +} + + +uint32_t Text::identifier() noexcept +{ + return TVG_CLASS_ID_TEXT; +} diff --git a/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgText.h b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgText.h new file mode 100644 index 00000000000..f4fb12259a0 --- /dev/null +++ b/Tests/LottieMetalTest/thorvg/Sources/renderer/tvgText.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_TEXT_H +#define _TVG_TEXT_H + +#include +#include "tvgShape.h" +#include "tvgFill.h" + +#ifdef THORVG_TTF_LOADER_SUPPORT + #include "tvgTtfLoader.h" +#else + #include "tvgLoader.h" +#endif + +struct Text::Impl +{ + FontLoader* loader = nullptr; + Shape* paint = nullptr; + char* utf8 = nullptr; + float fontSize; + bool italic = false; + bool changed = false; + + ~Impl() + { + free(utf8); + LoaderMgr::retrieve(loader); + delete(paint); + } + + Result fill(uint8_t r, uint8_t g, uint8_t b) + { + return paint->fill(r, g, b); + } + + Result fill(Fill* f) + { + return paint->fill(cast(f)); + } + + Result text(const char* utf8) + { + free(this->utf8); + if (utf8) this->utf8 = strdup(utf8); + else this->utf8 = nullptr; + changed = true; + + return Result::Success; + } + + Result font(const char* name, float size, const char* style) + { + auto loader = LoaderMgr::loader(name); + if (!loader) return Result::InsufficientCondition; + + //Same resource has been loaded. + if (this->loader == loader) { + this->loader->sharing--; //make it sure the reference counting. + return Result::Success; + } else if (this->loader) { + LoaderMgr::retrieve(this->loader); + } + this->loader = static_cast(loader); + + if (!paint) paint = Shape::gen().release(); + + fontSize = size; + if (style && strstr(style, "italic")) italic = true; + changed = true; + return Result::Success; + } + + RenderRegion bounds(RenderMethod* renderer) + { + if (paint) return P(paint)->bounds(renderer); + else return {0, 0, 0, 0}; + } + + bool render(RenderMethod* renderer) + { + if (paint) return PP(paint)->render(renderer); + return false; + } + + bool load() + { + if (!loader) return false; + + //reload + if (changed) { + loader->request(paint, utf8, italic); + loader->read(); + changed = false; + } + if (paint) { + loader->resize(paint, fontSize, fontSize); + return true; + } + return false; + } + + RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) + { + if (!load()) return nullptr; + + //transform the gradient coordinates based on the final scaled font. + if (P(paint)->flag & RenderUpdateFlag::Gradient) { + auto fill = P(paint)->rs.fill; + auto scale = 1.0f / loader->scale; + if (fill->identifier() == TVG_CLASS_ID_LINEAR) { + P(static_cast(fill))->x1 *= scale; + P(static_cast(fill))->y1 *= scale; + P(static_cast(fill))->x2 *= scale; + P(static_cast(fill))->y2 *= scale; + } else { + P(static_cast(fill))->cx *= scale; + P(static_cast(fill))->cy *= scale; + P(static_cast(fill))->r *= scale; + P(static_cast(fill))->fx *= scale; + P(static_cast(fill))->fy *= scale; + P(static_cast(fill))->fr *= scale; + } + } + return PP(paint)->update(renderer, transform, clips, opacity, pFlag, clipper); + } + + bool bounds(float* x, float* y, float* w, float* h, TVG_UNUSED bool stroking) + { + if (!load() || !paint) return false; + paint->bounds(x, y, w, h, true); + return true; + } + + Paint* duplicate() + { + load(); + + auto ret = Text::gen().release(); + auto dup = ret->pImpl; + if (paint) dup->paint = static_cast(paint->duplicate()); + + if (loader) { + dup->loader = loader; + ++dup->loader->sharing; + } + + dup->utf8 = strdup(utf8); + dup->italic = italic; + dup->fontSize = fontSize; + + return ret; + } + + Iterator* iterator() + { + return nullptr; + } +}; + + + +#endif //_TVG_TEXT_H diff --git a/WORKSPACE b/WORKSPACE index 5bbed74da1f..f8853de18ef 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -115,9 +115,9 @@ http_archive( http_archive( name = "rules_swift_package_manager", - sha256 = "eef16c8a5f9fa6102049f762823e773601a44398baf2a5de7ef7cbebcb888870", + sha256 = "7a75091c5d3132c1a8c24378a6beba469b435daeee9d0870686d77d0c974fbe5", urls = [ - "https://github.com/cgrindel/rules_swift_package_manager/releases/download/v0.28.0/rules_swift_package_manager.v0.28.0.tar.gz", + "https://github.com/cgrindel/rules_swift_package_manager/releases/download/v0.31.1/rules_swift_package_manager.v0.31.1.tar.gz", ], ) diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index e7d3924d194..03be8088976 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -615,7 +615,7 @@ def build(bazel, arguments): os.makedirs(artifacts_path, exist_ok=True) os.makedirs(artifacts_path + '/DSYMs', exist_ok=True) - built_ipa_path_prefix = 'bazel-out/ios_arm64-opt-ios-arm64-min12.0-applebin_ios-ST-*' + built_ipa_path_prefix = 'bazel-out/ios_arm64-opt-ios-arm64-min14.0-applebin_ios-ST-*' ipa_paths = glob.glob('{}/bin/Telegram/Telegram.ipa'.format(built_ipa_path_prefix)) if len(ipa_paths) == 0: print('Could not find the IPA at bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/Telegram.ipa') diff --git a/ci/fastlane/Fastfile b/ci/fastlane/Fastfile index 3b9a18d8fe5..b87f35b4648 100644 --- a/ci/fastlane/Fastfile +++ b/ci/fastlane/Fastfile @@ -229,8 +229,7 @@ lane :generate_project do |options| team_id: TEAM_ID, git_url: SIGN_URL, git_branch: TEAM_ID, - api_key: generate_app_store_connect_api_key(), - skip_provisioning_profiles: true + api_key: generate_app_store_connect_api_key() ) configuration_path = resolve_telegram_configuration() @@ -285,7 +284,7 @@ end lane :update_spm_pkgs do |options| bazel_path = sh("cd #{SOURCE_PATH} && echo $(python3 #{SOURCE_PATH}/build-system/Make/LocateBazel.py)") - sh("#{bazel_path} bazel run //:swift_update_pkgs") + sh('#{bazel_path} bazel run //:swift_update_pkgs') end lane :nicegram_match do |options| diff --git a/submodules/AccountContext/BUILD b/submodules/AccountContext/BUILD index d560c462340..640fad3af0d 100644 --- a/submodules/AccountContext/BUILD +++ b/submodules/AccountContext/BUILD @@ -9,7 +9,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/TelegramAudio:TelegramAudio", diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index bec69985fcb..f4a040e5012 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -14,6 +14,7 @@ import InAppPurchaseManager import AnimationCache import MultiAnimationRenderer import Photos +import TextFormat public final class TelegramApplicationOpenUrlCompletion { public let completion: (Bool) -> Void @@ -565,6 +566,7 @@ public enum PeerInfoControllerMode { case reaction(MessageId) case forumTopic(thread: ChatReplyThreadMessage) case recommendedChannels + case myProfile } public enum ContactListActionItemInlineIconPosition { @@ -889,6 +891,7 @@ public enum CollectibleItemInfoScreenSubject { public protocol SharedAccountContext: AnyObject { var sharedContainerPath: String { get } var basePath: String { get } + var networkArguments: NetworkInitializationArguments { get } var mainWindow: Window1? { get } var accountManager: AccountManager { get } var appLockContext: AppLockContext { get } @@ -937,7 +940,7 @@ public protocol SharedAccountContext: AnyObject { func makeOverlayAudioPlayerController(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController? func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController? - func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController + func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController func makePeersNearbyController(context: AccountContext) -> ViewController func makeComposeController(context: AccountContext) -> ViewController func makeChatListController(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController @@ -971,8 +974,9 @@ public protocol SharedAccountContext: AnyObject { func makeSetupTwoFactorAuthController(context: AccountContext) -> ViewController func makeStorageManagementController(context: AccountContext) -> ViewController func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController - func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? + func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController + func makeStorySearchController(context: AccountContext, query: String) -> ViewController func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController func makeArchiveSettingsController(context: AccountContext) -> ViewController func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController @@ -995,7 +999,7 @@ public protocol SharedAccountContext: AnyObject { func navigateToChatController(_ params: NavigateToChatControllerParams) func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) - func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal + func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack) -> Signal func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal func openStorageUsage(context: AccountContext) func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController) @@ -1004,7 +1008,7 @@ public protocol SharedAccountContext: AnyObject { func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set, messages: [EngineMessage.Id: EngineMessage], peers: [EnginePeer.Id: EnginePeer]) -> Signal func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal func resolveUrlWithProgress(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal - func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?, progress: Promise?, completion: (() -> Void)?) + func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?, progress: Promise?, completion: (() -> Void)?) func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) @@ -1024,11 +1028,11 @@ public protocol SharedAccountContext: AnyObject { func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController - func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController + func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, actionPerformed: ((Bool) -> Void)?) -> ViewController func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController - func makeStickerEditorScreen(context: AccountContext, source: Any?, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController + func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController @@ -1043,6 +1047,12 @@ 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: Int64?, completion: @escaping (Int64) -> Void) -> ViewController + func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController + func makeStarsTransactionScreen(context: AccountContext, transaction: StarsContext.State.Transaction) -> ViewController + func makeStarsReceiptScreen(context: AccountContext, receipt: BotPaymentReceipt) -> ViewController + func makeDebugSettingsController(context: AccountContext?) -> ViewController? func navigateToCurrentCall() @@ -1056,7 +1066,7 @@ public protocol SharedAccountContext: AnyObject { var hasGroupCallOnScreen: Signal { get } var currentGroupCallController: ViewController? { get } - + func switchToAccount(id: AccountRecordId, fromSettingsController settingsController: ViewController?, withChatListController chatListController: ViewController?) func beginNewAuth(testingEnvironment: Bool) } @@ -1073,6 +1083,42 @@ public protocol AccountGroupCallContext: AnyObject { public protocol AccountGroupCallContextCache: AnyObject { } +public struct ChatSendMessageActionSheetControllerSendParameters { + public struct Effect { + public let id: Int64 + + public init(id: Int64) { + self.id = id + } + } + + public var effect: Effect? + public var textIsAboveMedia: Bool + + public init( + effect: Effect?, + textIsAboveMedia: Bool + ) { + self.effect = effect + self.textIsAboveMedia = textIsAboveMedia + } +} + +public enum ChatSendMessageActionSheetControllerSendMode { + case generic + case silently + case whenOnline +} + +public protocol ChatSendMessageActionSheetControllerSourceSendButtonNode: ASDisplayNode { + func makeCustomContents() -> UIView? +} + +public protocol ChatSendMessageActionSheetController: ViewController { + typealias SendMode = ChatSendMessageActionSheetControllerSendMode + typealias SendParameters = ChatSendMessageActionSheetControllerSendParameters +} + public protocol AccountContext: AnyObject { var sharedContext: SharedAccountContext { get } var account: Account { get } @@ -1087,6 +1133,7 @@ public protocol AccountContext: AnyObject { var wallpaperUploadManager: WallpaperUploadManager? { get } var watchManager: WatchManager? { get } var inAppPurchaseManager: InAppPurchaseManager? { get } + var starsContext: StarsContext? { get } var currentLimitsConfiguration: Atomic { get } var currentContentSettings: Atomic { get } @@ -1101,6 +1148,8 @@ public protocol AccountContext: AnyObject { var animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> { get } var animatedEmojiStickersValue: [String: [StickerPackItem]] { get } var additionalAnimatedEmojiStickers: Signal<[String: [Int: StickerPackItem]], NoError> { get } + var availableReactions: Signal { get } + var availableMessageEffects: Signal { get } var isPremium: Bool { get } var userLimits: EngineConfiguration.UserLimits { get } @@ -1227,3 +1276,28 @@ public struct StickersSearchConfiguration { } } } + +public protocol ShareControllerAccountContext: AnyObject { + var accountId: AccountRecordId { get } + var accountPeerId: EnginePeer.Id { get } + var stateManager: AccountStateManager { get } + var engineData: TelegramEngine.EngineData { get } + var animationCache: AnimationCache { get } + var animationRenderer: MultiAnimationRenderer { get } + var contentSettings: ContentSettings { get } + var appConfiguration: AppConfiguration { get } + + func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> +} + +public protocol ShareControllerEnvironment: AnyObject { + var presentationData: PresentationData { get } + var updatedPresentationData: Signal { get } + var isMainApp: Bool { get } + var energyUsageSettings: EnergyUsageSettings { get } + + var mediaManager: MediaManager? { get } + + func setAccountUserInterfaceInUse(id: AccountRecordId) -> Disposable + func donateSendMessageIntent(account: ShareControllerAccountContext, peerIds: [EnginePeer.Id]) +} diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 95b401b6aaf..76892927342 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -44,6 +44,7 @@ public final class ChatMessageItemAssociatedData: Equatable { public let currentlyPlayingMessageId: EngineMessage.Index? public let isCopyProtectionEnabled: Bool public let availableReactions: AvailableReactions? + public let availableMessageEffects: AvailableMessageEffects? public let savedMessageTags: SavedMessageTags? public let defaultReaction: MessageReaction.Reaction? public let isPremium: Bool @@ -75,6 +76,7 @@ public final class ChatMessageItemAssociatedData: Equatable { currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, + availableMessageEffects: AvailableMessageEffects?, savedMessageTags: SavedMessageTags?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, @@ -105,6 +107,7 @@ public final class ChatMessageItemAssociatedData: Equatable { self.currentlyPlayingMessageId = currentlyPlayingMessageId self.isCopyProtectionEnabled = isCopyProtectionEnabled self.availableReactions = availableReactions + self.availableMessageEffects = availableMessageEffects self.savedMessageTags = savedMessageTags self.defaultReaction = defaultReaction self.isPremium = isPremium @@ -296,9 +299,11 @@ public struct ChatControllerInitialBotAppStart { public enum ChatControllerInteractionNavigateToPeer { public struct InfoParams { public let switchToRecommendedChannels: Bool + public let ignoreInSavedMessages: Bool - public init(switchToRecommendedChannels: Bool) { + public init(switchToRecommendedChannels: Bool = false, ignoreInSavedMessages: Bool = false) { self.switchToRecommendedChannels = switchToRecommendedChannels + self.ignoreInSavedMessages = ignoreInSavedMessages } } @@ -397,8 +402,9 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable { case strikethrough case underline case spoiler - case quote + case quote(isCollapsed: Bool) case codeBlock(language: String?) + case collapsedQuote(text: ChatTextInputStateText) public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) @@ -427,9 +433,11 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable { case 8: self = .spoiler case 9: - self = .quote + self = .quote(isCollapsed: try container.decodeIfPresent(Bool.self, forKey: "isCollapsed") ?? false) case 10: self = .codeBlock(language: try container.decodeIfPresent(String.self, forKey: "l")) + case 11: + self = .collapsedQuote(text: try container.decode(ChatTextInputStateText.self, forKey: "text")) default: assertionFailure() self = .bold @@ -461,11 +469,15 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable { try container.encode(7 as Int32, forKey: "t") case .spoiler: try container.encode(8 as Int32, forKey: "t") - case .quote: + case let .quote(isCollapsed): try container.encode(9 as Int32, forKey: "t") + try container.encode(isCollapsed, forKey: "isCollapsed") case let .codeBlock(language): try container.encode(10 as Int32, forKey: "t") try container.encodeIfPresent(language, forKey: "l") + case let .collapsedQuote(text): + try container.encode(11 as Int32, forKey: "t") + try container.encode(text, forKey: "text") } } } @@ -518,6 +530,7 @@ public struct ChatTextInputStateText: Codable, Equatable { public init(attributedText: NSAttributedString) { self.text = attributedText.string + var parsedAttributes: [ChatTextInputStateTextAttribute] = [] attributedText.enumerateAttributes(in: NSRange(location: 0, length: attributedText.length), options: [], using: { attributes, range, _ in for (key, value) in attributes { @@ -539,16 +552,24 @@ public struct ChatTextInputStateText: Codable, Equatable { parsedAttributes.append(ChatTextInputStateTextAttribute(type: .underline, range: range.location ..< (range.location + range.length))) } else if key == ChatTextInputAttributes.spoiler { parsedAttributes.append(ChatTextInputStateTextAttribute(type: .spoiler, range: range.location ..< (range.location + range.length))) - } else if key == ChatTextInputAttributes.block, let value = value as? ChatTextInputTextQuoteAttribute { - switch value.kind { - case .quote: - parsedAttributes.append(ChatTextInputStateTextAttribute(type: .quote, range: range.location ..< (range.location + range.length))) - case let .code(language): - parsedAttributes.append(ChatTextInputStateTextAttribute(type: .codeBlock(language: language), range: range.location ..< (range.location + range.length))) - } } } }) + attributedText.enumerateAttribute(ChatTextInputAttributes.block, in: NSRange(location: 0, length: attributedText.length), options: [], using: { value, range, _ in + if let value = value as? ChatTextInputTextQuoteAttribute { + switch value.kind { + case .quote: + parsedAttributes.append(ChatTextInputStateTextAttribute(type: .quote(isCollapsed: value.isCollapsed), range: range.location ..< (range.location + range.length))) + case let .code(language): + parsedAttributes.append(ChatTextInputStateTextAttribute(type: .codeBlock(language: language), range: range.location ..< (range.location + range.length))) + } + } + }) + attributedText.enumerateAttribute(ChatTextInputAttributes.collapsedBlock, in: NSRange(location: 0, length: attributedText.length), options: [], using: { value, range, _ in + if let value = value as? NSAttributedString { + parsedAttributes.append(ChatTextInputStateTextAttribute(type: .collapsedQuote(text: ChatTextInputStateText(attributedText: value)), range: range.location ..< (range.location + range.length))) + } + }) self.attributes = parsedAttributes } @@ -590,10 +611,12 @@ public struct ChatTextInputStateText: Codable, Equatable { result.addAttribute(ChatTextInputAttributes.underline, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) case .spoiler: result.addAttribute(ChatTextInputAttributes.spoiler, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) - case .quote: - result.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) + case let .quote(isCollapsed): + result.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: isCollapsed), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) case let .codeBlock(language): - result.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: language)), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) + result.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: language), isCollapsed: false), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) + case let .collapsedQuote(text): + result.addAttribute(ChatTextInputAttributes.collapsedBlock, value: text.attributedText(), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) } } return result @@ -979,6 +1002,7 @@ public protocol ChatController: ViewController { var chatLocation: ChatLocation { get } var canReadHistory: ValuePromise { get } var parentController: ViewController? { get set } + var customNavigationController: NavigationController? { get set } var purposefulAction: (() -> Void)? { get set } @@ -993,7 +1017,11 @@ public protocol ChatController: ViewController { var visibleContextController: ViewController? { get } + var searching: ValuePromise { get } + var alwaysShowSearchResultsAsList: Bool { get set } + var includeSavedPeersInSearchResults: Bool { get set } + var showListEmptyResults: Bool { get set } func updatePresentationMode(_ mode: ChatControllerPresentationMode) func beginMessageSearch(_ query: String) @@ -1014,6 +1042,8 @@ public protocol ChatController: ViewController { func performScrollToTop() -> Bool func transferScrollingVelocity(_ velocity: CGFloat) func updateIsScrollingLockedAtTop(isScrollingLockedAtTop: Bool) + + func playShakeAnimation() } public protocol ChatMessagePreviewItemNode: AnyObject { @@ -1044,6 +1074,9 @@ public protocol ChatMessageItemNodeProtocol: ListViewItemNode { func targetReactionView(value: MessageReaction.Reaction) -> UIView? func targetForStoryTransition(id: StoryId) -> UIView? func contentFrame() -> CGRect + func matchesMessage(id: MessageId) -> Bool + func cancelInsertionAnimations() + func messages() -> [Message] } public final class ChatControllerNavigationData: CustomViewControllerNavigationData { @@ -1106,6 +1139,7 @@ public enum ChatQuickReplyShortcutType { public enum ChatCustomContentsKind: Equatable { case quickReplyMessageInput(shortcut: String, shortcutType: ChatQuickReplyShortcutType) case businessLinkSetup(link: TelegramBusinessChatLinks.Link) + case hashTagSearch(publicPosts: Bool) } public protocol ChatCustomContentsProtocol: AnyObject { @@ -1119,6 +1153,11 @@ public protocol ChatCustomContentsProtocol: AnyObject { func quickReplyUpdateShortcut(value: String) func businessLinkUpdate(message: String, entities: [MessageTextEntity], title: String?) + + func loadMore() + + func hashtagSearchUpdate(query: String) + var hashtagSearchResultsUpdate: ((SearchMessagesResult, SearchMessagesState)) -> Void { get set } } public enum ChatHistoryListDisplayHeaders { diff --git a/submodules/AccountContext/Sources/ContactSelectionController.swift b/submodules/AccountContext/Sources/ContactSelectionController.swift index ac7907088c6..19d4c5c60ab 100644 --- a/submodules/AccountContext/Sources/ContactSelectionController.swift +++ b/submodules/AccountContext/Sources/ContactSelectionController.swift @@ -3,7 +3,7 @@ import Display import SwiftSignalKit public protocol ContactSelectionController: ViewController { - var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?)?, NoError> { get } + var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?, NoError> { get } var displayProgress: Bool { get set } var dismissed: (() -> Void)? { get set } var presentScheduleTimePicker: (@escaping (Int32) -> Void) -> Void { get set } diff --git a/submodules/AccountContext/Sources/PeerSelectionController.swift b/submodules/AccountContext/Sources/PeerSelectionController.swift index e34b0ba5383..7230f902ce4 100644 --- a/submodules/AccountContext/Sources/PeerSelectionController.swift +++ b/submodules/AccountContext/Sources/PeerSelectionController.swift @@ -147,7 +147,7 @@ public enum PeerSelectionControllerContext { public protocol PeerSelectionController: ViewController { var peerSelected: ((EnginePeer, Int64?) -> Void)? { get set } - var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? { get set } + var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.SendParameters?) -> Void)? { get set } var inProgress: Bool { get set } var customDismiss: (() -> Void)? { get set } } diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index 2373353de53..d847a744d40 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -122,6 +122,7 @@ public struct PremiumConfiguration { public static var defaultValue: PremiumConfiguration { return PremiumConfiguration( isPremiumDisabled: false, + areStarsDisabled: true, subscriptionManagementUrl: "", showPremiumGiftInAttachMenu: false, showPremiumGiftInTextField: false, @@ -147,6 +148,7 @@ public struct PremiumConfiguration { } public let isPremiumDisabled: Bool + public let areStarsDisabled: Bool public let subscriptionManagementUrl: String public let showPremiumGiftInAttachMenu: Bool public let showPremiumGiftInTextField: Bool @@ -171,6 +173,7 @@ public struct PremiumConfiguration { fileprivate init( isPremiumDisabled: Bool, + areStarsDisabled: Bool, subscriptionManagementUrl: String, showPremiumGiftInAttachMenu: Bool, showPremiumGiftInTextField: Bool, @@ -194,6 +197,7 @@ public struct PremiumConfiguration { minGroupAudioTranscriptionLevel: Int32 ) { self.isPremiumDisabled = isPremiumDisabled + self.areStarsDisabled = areStarsDisabled self.subscriptionManagementUrl = subscriptionManagementUrl self.showPremiumGiftInAttachMenu = showPremiumGiftInAttachMenu self.showPremiumGiftInTextField = showPremiumGiftInTextField @@ -225,6 +229,7 @@ public struct PremiumConfiguration { } return PremiumConfiguration( isPremiumDisabled: data["premium_purchase_blocked"] as? Bool ?? defaultValue.isPremiumDisabled, + areStarsDisabled: data["stars_purchase_blocked"] as? Bool ?? defaultValue.areStarsDisabled, subscriptionManagementUrl: data["premium_manage_subscription_url"] as? String ?? "", showPremiumGiftInAttachMenu: data["premium_gift_attach_menu_icon"] as? Bool ?? defaultValue.showPremiumGiftInAttachMenu, showPremiumGiftInTextField: data["premium_gift_text_field_icon"] as? Bool ?? defaultValue.showPremiumGiftInTextField, diff --git a/submodules/AccountUtils/BUILD b/submodules/AccountUtils/BUILD index 0cf5cb66fff..d386fc2083a 100644 --- a/submodules/AccountUtils/BUILD +++ b/submodules/AccountUtils/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ActionSheetPeerItem/BUILD b/submodules/ActionSheetPeerItem/BUILD index d3aa0cc103c..bd228ab2272 100644 --- a/submodules/ActionSheetPeerItem/BUILD +++ b/submodules/ActionSheetPeerItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/ActivityIndicator/BUILD b/submodules/ActivityIndicator/BUILD index 38c3fb98885..e9c09432da6 100644 --- a/submodules/ActivityIndicator/BUILD +++ b/submodules/ActivityIndicator/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/AdUI/BUILD b/submodules/AdUI/BUILD index d78e91cebc6..c4c04d271a9 100644 --- a/submodules/AdUI/BUILD +++ b/submodules/AdUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/AlertUI/BUILD b/submodules/AlertUI/BUILD index 2d9a5628f88..b6f55778f32 100644 --- a/submodules/AlertUI/BUILD +++ b/submodules/AlertUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/AnimatedAvatarSetNode/BUILD b/submodules/AnimatedAvatarSetNode/BUILD index 21321e8fc32..a4b0d1a4321 100644 --- a/submodules/AnimatedAvatarSetNode/BUILD +++ b/submodules/AnimatedAvatarSetNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/AnimatedCountLabelNode/BUILD b/submodules/AnimatedCountLabelNode/BUILD index 55f7c774f76..6b09ff4b979 100644 --- a/submodules/AnimatedCountLabelNode/BUILD +++ b/submodules/AnimatedCountLabelNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/AnimatedNavigationStripeNode/BUILD b/submodules/AnimatedNavigationStripeNode/BUILD index 74d6a6c9706..ca756f228a2 100644 --- a/submodules/AnimatedNavigationStripeNode/BUILD +++ b/submodules/AnimatedNavigationStripeNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/AnimatedStickerNode/BUILD b/submodules/AnimatedStickerNode/BUILD index 61d3f19ba7d..02f98ea7f9b 100644 --- a/submodules/AnimatedStickerNode/BUILD +++ b/submodules/AnimatedStickerNode/BUILD @@ -21,7 +21,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ] + optimization_flags, deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index b6b3a76dd39..7e3ccaeb2a8 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -168,7 +168,7 @@ public protocol AnimatedStickerNode: ASDisplayNode { var visibility: Bool { get set } var overrideVisibility: Bool { get set } - var isPlayingChanged: (Bool) -> Void { get } + var isPlayingChanged: (Bool) -> Void { get set } func cloneCurrentFrame(from otherNode: AnimatedStickerNode?) func setup(source: AnimatedStickerNodeSource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode, mode: AnimatedStickerMode) diff --git a/submodules/AnimationCompression/BUILD b/submodules/AnimationCompression/BUILD index 55b637ec066..3aff657d55a 100644 --- a/submodules/AnimationCompression/BUILD +++ b/submodules/AnimationCompression/BUILD @@ -47,7 +47,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":AnimationCompressionBundle", diff --git a/submodules/AnimationUI/BUILD b/submodules/AnimationUI/BUILD index adf8470d173..94179d70c8f 100644 --- a/submodules/AnimationUI/BUILD +++ b/submodules/AnimationUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/AppLock/BUILD b/submodules/AppLock/BUILD index 90ad4bed734..3ae7a9ac9cd 100644 --- a/submodules/AppLock/BUILD +++ b/submodules/AppLock/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/AppLock/Sources/AppLock.swift b/submodules/AppLock/Sources/AppLock.swift index d640244da43..f2b7cc0cd47 100644 --- a/submodules/AppLock/Sources/AppLock.swift +++ b/submodules/AppLock/Sources/AppLock.swift @@ -289,14 +289,18 @@ public final class AppLockContextImpl: AppLockContext { } } } - passcodeController.presentedOverCoveringView = true + // MARK: Nicegram, presentedOverCoveringView = false + // for correct behavior of telegram passcode with nicegram screens (assistant, wallet, etc.) + passcodeController.presentedOverCoveringView = false passcodeController.isOpaqueWhenInOverlay = true strongSelf.passcodeController = passcodeController if let rootViewController = strongSelf.rootController { if let _ = rootViewController.presentedViewController as? UIActivityViewController { } else if let _ = rootViewController.presentedViewController as? PKPaymentAuthorizationViewController { } else { - rootViewController.dismiss(animated: false, completion: nil) + // MARK: Nicegram, comment rootViewController.dismiss + // for correct behavior of telegram passcode with nicegram screens (assistant, wallet, etc.) + // rootViewController.dismiss(animated: false, completion: nil) } } // MARK: Nicegram DB Changes @@ -333,10 +337,9 @@ public final class AppLockContextImpl: AppLockContext { if let _ = rootViewController.presentedViewController as? UIActivityViewController { } else if let _ = rootViewController.presentedViewController as? PKPaymentAuthorizationViewController { } else { - // MARK: Nicegram, change dismiss to alpha=0 - // (assistant hides when app enters background) - rootViewController.presentedViewController?.view.alpha = 0 -// rootViewController.dismiss(animated: false, completion: nil) + // MARK: Nicegram, comment rootViewController.dismiss + // for correct behavior of telegram passcode with nicegram screens (assistant, wallet, etc.) + // rootViewController.dismiss(animated: false, completion: nil) } } } diff --git a/submodules/ArchivedStickerPacksNotice/BUILD b/submodules/ArchivedStickerPacksNotice/BUILD index c9898e569df..bc0115bb796 100644 --- a/submodules/ArchivedStickerPacksNotice/BUILD +++ b/submodules/ArchivedStickerPacksNotice/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h index 3119c019765..5683097d5ad 100644 --- a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode.h @@ -569,6 +569,8 @@ AS_EXTERN NSInteger const ASDefaultDrawingPriority; @property (nonatomic) bool disableClearContentsOnHide; +- (void)displayImmediately; + @end /** diff --git a/submodules/AttachmentTextInputPanelNode/BUILD b/submodules/AttachmentTextInputPanelNode/BUILD index 59571663187..fde8d8822c7 100644 --- a/submodules/AttachmentTextInputPanelNode/BUILD +++ b/submodules/AttachmentTextInputPanelNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift index 10fa313b6ea..045e0a6459f 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift @@ -6,8 +6,10 @@ import TelegramCore import TelegramPresentationData import ContextUI import ChatPresentationInterfaceState +import ComponentFlow +import AccountContext -final class AttachmentTextInputActionButtonsNode: ASDisplayNode { +final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode { private let strings: PresentationStrings let sendContainerNode: ASDisplayNode @@ -16,6 +18,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { var sendButtonHasApplyIcon = false var animatingSendButton = false let textNode: ImmediateTextNode + + private var theme: PresentationTheme var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)? @@ -31,9 +35,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { private var validLayout: CGSize? init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) { - let theme = presentationInterfaceState.theme - let strings = presentationInterfaceState.strings - self.strings = strings + self.theme = presentationInterfaceState.theme + self.strings = presentationInterfaceState.strings self.sendContainerNode = ASDisplayNode() self.sendContainerNode.layer.allowsGroupOpacity = true @@ -54,7 +57,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { self.sendButton.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { - if strongSelf.sendButtonHasApplyIcon || !strongSelf.sendButtonLongPressEnabled { + if !strongSelf.sendButtonLongPressEnabled { if highlighted { strongSelf.sendContainerNode.layer.removeAnimation(forKey: "opacity") strongSelf.sendContainerNode.alpha = 0.4 @@ -64,9 +67,11 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { } } else { if highlighted { - strongSelf.sendContainerNode.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false) - } else if let presentationLayer = strongSelf.sendButton.layer.presentation() { - strongSelf.sendContainerNode.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false) + let transition: Transition = .easeInOut(duration: 0.4) + transition.setScale(layer: strongSelf.sendContainerNode.layer, scale: 0.75) + } else { + let transition: Transition = .easeInOut(duration: 0.25) + transition.setScale(layer: strongSelf.sendContainerNode.layer, scale: 1.0) } } } @@ -90,7 +95,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { return } if !strongSelf.sendButtonHasApplyIcon { - strongSelf.sendButtonLongPressed?(strongSelf.sendContainerNode, recognizer) + strongSelf.sendButtonLongPressed?(strongSelf, recognizer) } } @@ -112,7 +117,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { self.validLayout = size let width: CGFloat - let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: size.height)) + let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) if minimized { width = 44.0 } else { @@ -140,4 +145,16 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { self.accessibilityLabel = self.strings.MediaPicker_Send self.accessibilityHint = nil } + + func makeCustomContents() -> UIView? { + if !self.textNode.alpha.isZero { + let textView = ImmediateTextView() + textView.attributedText = NSAttributedString(string: self.strings.MediaPicker_Send, font: Font.semibold(17.0), textColor: self.theme.chat.inputPanel.actionControlForegroundColor) + let textSize = textView.updateLayout(CGSize(width: 100.0, height: 100.0)) + let _ = textSize + textView.frame = self.textNode.frame + return textView + } + return nil + } } diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index e9066fb90dd..99a7985e272 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -269,7 +269,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)? - public var sendMessage: (AttachmentTextInputPanelSendMode) -> Void = { _ in } + public var sendMessage: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void = { _, _ in } public var updateHeight: (Bool) -> Void = { _ in } private var updatingInputState = false @@ -338,7 +338,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) } - textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) + textInputNode.attributedText = textAttributedStringForStateText(context: self.context, stateText: state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count) self.updatingInputState = false self.updateTextNodeText(animated: animated) @@ -1020,7 +1022,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS public func chatInputTextNodeDidUpdateText() { if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState { let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) - refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) + refreshChatTextInputAttributes(context: self.context, textView: textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) self.updateSpoiler() @@ -1192,9 +1196,13 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS textInputNode.textView.isScrollEnabled = false - refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) + refreshChatTextInputAttributes(context: self.context, textView: textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) - textInputNode.attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) + textInputNode.attributedText = textAttributedStringForStateText(context: self.context, stateText: self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) if textInputNode.textView.subviews.count > 1, animated { let containerView = textInputNode.textView.subviews[1] @@ -1349,7 +1357,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let textFont = Font.regular(baseFontSize) let accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor - let attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: false, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) + let attributedText = textAttributedStringForStateText(context: self.context, stateText: self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: false, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) let range = (attributedText.string as NSString).range(of: "\n") if range.location != NSNotFound { @@ -1715,7 +1725,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.inputMenu.back() self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in - return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote)), inputMode) + return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false)), inputMode) } } @@ -1723,7 +1733,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.inputMenu.back() self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in - return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: nil))), inputMode) + return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: nil), isCollapsed: false)), inputMode) } } @@ -1754,7 +1764,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) } - let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) + let cleanReplacementString = textAttributedStringForStateText(context: self.context, stateText: NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) string.replaceCharacters(in: range, with: cleanReplacementString) self.textInputNode?.attributedText = string self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0) @@ -1843,7 +1855,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS sendPressed(effectiveInputText) return } - self.sendMessage(.generic) + self.sendMessage(.generic, nil) } @objc func textInputBackgroundViewTap(_ recognizer: UITapGestureRecognizer) { diff --git a/submodules/AttachmentUI/BUILD b/submodules/AttachmentUI/BUILD index 3770639b734..aceb5e800bb 100644 --- a/submodules/AttachmentUI/BUILD +++ b/submodules/AttachmentUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", @@ -39,6 +39,8 @@ swift_library( "//submodules/TextFormat:TextFormat", "//submodules/TelegramUI/Components/LegacyMessageInputPanel", "//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView", + "//submodules/ReactionSelectionNode", + "//submodules/TelegramUI/Components/Chat/TopMessageReactions", ], visibility = [ "//visibility:public", diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index ace93ca1d42..6c4d3c4f857 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -473,9 +473,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { if let statusBarHeight = layout.statusBarHeight { containerTopInset += statusBarHeight } - - let effectiveStatusBarHeight: CGFloat? = nil - + var safeInsets = layout.safeInsets safeInsets.left += overflowInset safeInsets.right += overflowInset @@ -487,7 +485,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { var additionalInsets = layout.additionalInsets additionalInsets.bottom = topInset - containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width + overflowInset * 2.0, height: layout.size.height - containerTopInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: intrinsicInsets.left, bottom: intrinsicInsets.bottom, right: intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: safeInsets.left, bottom: safeInsets.bottom, right: safeInsets.right), additionalInsets: additionalInsets, statusBarHeight: effectiveStatusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) + containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width + overflowInset * 2.0, height: layout.size.height - containerTopInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: intrinsicInsets.left, bottom: intrinsicInsets.bottom, right: intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: safeInsets.left, bottom: safeInsets.bottom, right: safeInsets.right), additionalInsets: additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: containerLayout.size) let maxScale: CGFloat = (containerLayout.size.width - 16.0 * 2.0) / containerLayout.size.width containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index a343b776aba..4043453c91b 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -14,6 +14,7 @@ import MediaResources import LegacyMessageInputPanel import LegacyMessageInputPanelInputView import AttachmentTextInputPanelNode +import ChatSendMessageActionUI public enum AttachmentButtonType: Equatable { case gallery @@ -97,6 +98,7 @@ public protocol AttachmentContainable: ViewController { var isContainerExpanded: () -> Bool { get set } var isPanGestureEnabled: (() -> Bool)? { get } var mediaPickerContext: AttachmentMediaPickerContext? { get } + var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { get } func isContainerPanningUpdated(_ panning: Bool) @@ -131,6 +133,10 @@ public extension AttachmentContainable { var isPanGestureEnabled: (() -> Bool)? { return nil } + + var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { + return nil + } } public enum AttachmentMediaPickerSendMode { @@ -148,14 +154,18 @@ public protocol AttachmentMediaPickerContext { var selectionCount: Signal { get } var caption: Signal { get } + var hasCaption: Bool { get } + var captionIsAboveMedia: Signal { get } + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void + var loadingProgress: Signal { get } var mainButtonState: Signal { get } func mainButtonAction() func setCaption(_ caption: NSAttributedString) - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) - func schedule() + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) } private func generateShadowImage() -> UIImage? { @@ -243,7 +253,7 @@ public class AttachmentController: ViewController { private var selectionCount: Int = 0 - fileprivate var mediaPickerContext: AttachmentMediaPickerContext? { + var mediaPickerContext: AttachmentMediaPickerContext? { didSet { if let mediaPickerContext = self.mediaPickerContext { self.captionDisposable.set((mediaPickerContext.caption @@ -311,7 +321,7 @@ public class AttachmentController: ViewController { self.container = AttachmentContainer() self.container.canHaveKeyboardFocus = true - self.panel = AttachmentPanel(context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView) + self.panel = AttachmentPanel(controller: controller, context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView) self.panel.fromMenu = controller.fromMenu self.panel.isStandalone = controller.isStandalone @@ -417,17 +427,17 @@ public class AttachmentController: ViewController { } } - self.panel.sendMessagePressed = { [weak self] mode in + self.panel.sendMessagePressed = { [weak self] mode, parameters in if let strongSelf = self { switch mode { case .generic: - strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media) + strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media, parameters: parameters) case .silent: - strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media) + strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media, parameters: parameters) case .schedule: - strongSelf.mediaPickerContext?.schedule() + strongSelf.mediaPickerContext?.schedule(parameters: parameters) case .whenOnline: - strongSelf.mediaPickerContext?.send(mode: .whenOnline, attachmentMode: .media) + strongSelf.mediaPickerContext?.send(mode: .whenOnline, attachmentMode: .media, parameters: parameters) } } } @@ -455,6 +465,14 @@ public class AttachmentController: ViewController { strongSelf.controller?.presentInGlobalOverlay(c, with: nil) } } + + self.panel.getCurrentSendMessageContextMediaPreview = { [weak self] in + guard let self, let currentController = self.currentControllers.last else { + return nil + } + + return currentController.getCurrentSendMessageContextMediaPreview?() + } } deinit { @@ -691,7 +709,7 @@ public class AttachmentController: ViewController { } else { ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) - let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) + let targetPosition = self.container.position let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height) self.container.position = startPosition diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 9571982c4a7..927083c8220 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -21,6 +21,8 @@ import ShimmerEffect import TextFormat import LegacyMessageInputPanel import LegacyMessageInputPanelInputView +import ReactionSelectionNode +import TopMessageReactions private let buttonSize = CGSize(width: 88.0, height: 49.0) private let smallButtonWidth: CGFloat = 69.0 @@ -681,9 +683,11 @@ private final class MainButtonNode: HighlightTrackingButtonNode { } final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { + private weak var controller: AttachmentController? private let context: AccountContext private let isScheduledMessages: Bool private var presentationData: PresentationData + private var updatedPresentationData: (initial: PresentationData, signal: Signal)? private var presentationDataDisposable: Disposable? private var iconDisposables: [MediaId: Disposable] = [:] @@ -726,15 +730,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { var beganTextEditing: () -> Void = {} var textUpdated: (NSAttributedString) -> Void = { _ in } - var sendMessagePressed: (AttachmentTextInputPanelSendMode) -> Void = { _ in } + var sendMessagePressed: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void = { _, _ in } var requestLayout: () -> Void = {} var present: (ViewController) -> Void = { _ in } var presentInGlobalOverlay: (ViewController) -> Void = { _ in } + var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? + var mainButtonPressed: () -> Void = { } - init(context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { + init(controller: AttachmentController, context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { + self.controller = controller self.context = context + self.updatedPresentationData = updatedPresentationData self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.isScheduledMessages = isScheduledMessages @@ -929,9 +937,31 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { if case .media = strongSelf.presentationInterfaceState.inputMode { hasEntityKeyboard = true } - let _ = (strongSelf.context.account.viewTracker.peerView(peerId) - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peerView in + + let effectItems: Signal<[ReactionItem]?, NoError> + if strongSelf.presentationInterfaceState.chatLocation.peerId != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.chatLocation.peerId?.namespace == Namespaces.Peer.CloudUser { + effectItems = effectMessageReactions(context: strongSelf.context) + |> map(Optional.init) + } else { + effectItems = .single(nil) + } + + let availableMessageEffects = strongSelf.context.availableMessageEffects |> take(1) + let hasPremium = strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) + |> map { peer -> Bool in + guard case let .user(user) = peer else { + return false + } + return user.isPremium + } + + let _ = (combineLatest( + strongSelf.context.account.viewTracker.peerView(peerId) |> take(1), + effectItems, + availableMessageEffects, + hasPremium + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peerView, effectItems, availableMessageEffects, hasPremium in guard let strongSelf = self, let peer = peerViewMainPeer(peerView) else { return } @@ -946,21 +976,80 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { sendWhenOnlineAvailable = false } - let controller = ChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputNode.textView, attachment: true, canSendWhenOnline: sendWhenOnlineAvailable, completion: { - }, sendMessage: { [weak textInputPanelNode] mode in - switch mode { - case .generic: - textInputPanelNode?.sendMessage(.generic) - case .silently: - textInputPanelNode?.sendMessage(.silent) - case .whenOnline: - textInputPanelNode?.sendMessage(.whenOnline) + let mediaPreview = strongSelf.getCurrentSendMessageContextMediaPreview?() + let isReady: Signal + if let mediaPreview { + isReady = mediaPreview.isReady + |> filter { $0 } + |> take(1) + |> timeout(0.5, queue: .mainQueue(), alternate: .single(true)) + } else { + isReady = .single(true) + } + + var captionIsAboveMedia: Signal = .single(false) + if let controller = strongSelf.controller, let mediaPickerContext = controller.mediaPickerContext { + captionIsAboveMedia = mediaPickerContext.captionIsAboveMedia + } + + let _ = (combineLatest( + isReady, + captionIsAboveMedia |> take(1) + ) + |> deliverOnMainQueue).start(next: { [weak strongSelf] _, captionIsAboveMedia in + guard let strongSelf else { + return } - }, schedule: { [weak textInputPanelNode] in - textInputPanelNode?.sendMessage(.schedule) + + let controller = makeChatSendMessageActionSheetController( + context: strongSelf.context, + updatedPresentationData: strongSelf.updatedPresentationData, + peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, + params: .sendMessage(SendMessageActionSheetControllerParams.SendMessage( + isScheduledMessages: false, + mediaPreview: mediaPreview, + mediaCaptionIsAbove: (captionIsAboveMedia, { [weak strongSelf] value in + guard let strongSelf, let controller = strongSelf.controller, let mediaPickerContext = controller.mediaPickerContext else { + return + } + mediaPickerContext.setCaptionIsAboveMedia(value) + }), + attachment: true, + canSendWhenOnline: sendWhenOnlineAvailable, + forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [] + )), + hasEntityKeyboard: hasEntityKeyboard, + gesture: gesture, + sourceSendButton: node, + textInputView: textInputNode.textView, + emojiViewProvider: textInputPanelNode.emojiViewProvider, + completion: { + }, + sendMessage: { [weak textInputPanelNode] mode, messageEffect in + switch mode { + case .generic: + textInputPanelNode?.sendMessage(.generic, messageEffect) + case .silently: + textInputPanelNode?.sendMessage(.silent, messageEffect) + case .whenOnline: + textInputPanelNode?.sendMessage(.whenOnline, messageEffect) + } + }, + schedule: { [weak textInputPanelNode] messageEffect in + textInputPanelNode?.sendMessage(.schedule, messageEffect) + }, + openPremiumPaywall: { [weak self] c in + guard let self else { + return + } + self.controller?.push(c) + }, + reactionItems: effectItems, + availableMessageEffects: availableMessageEffects, + isPremium: hasPremium + ) + strongSelf.presentInGlobalOverlay(controller) }) - controller.emojiViewProvider = textInputPanelNode.emojiViewProvider - strongSelf.presentInGlobalOverlay(controller) }) }, openScheduledMessages: { }, openPeersNearby: { @@ -1229,9 +1318,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } }, makeEntityInputView: self.makeEntityInputView) textInputPanelNode.interfaceInteraction = self.interfaceInteraction - textInputPanelNode.sendMessage = { [weak self] mode in + textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in if let strongSelf = self { - strongSelf.sendMessagePressed(mode) + strongSelf.sendMessagePressed(mode, messageEffect) } } textInputPanelNode.focusUpdated = { [weak self] focus in diff --git a/submodules/AudioBlob/BUILD b/submodules/AudioBlob/BUILD index 72c69fa1aa0..425aef4797e 100644 --- a/submodules/AudioBlob/BUILD +++ b/submodules/AudioBlob/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/AudioWaveform/BUILD b/submodules/AudioWaveform/BUILD index 4ffbb1fa8da..c930fb69fae 100644 --- a/submodules/AudioWaveform/BUILD +++ b/submodules/AudioWaveform/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/AuthorizationUI/BUILD b/submodules/AuthorizationUI/BUILD index 38daa2eb030..7d6f1d352c3 100644 --- a/submodules/AuthorizationUI/BUILD +++ b/submodules/AuthorizationUI/BUILD @@ -12,7 +12,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift index 1904e9fb5fc..8fcb17daa2d 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift @@ -216,7 +216,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { self.textField.textField.textContentType = UITextContentType(rawValue: "") } - self.textField.textField.returnKeyType = .default + self.textField.textField.returnKeyType = .done self.textField.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance self.textField.textField.disableAutomaticKeyboardHandling = [.forward, .backward] self.textField.textField.tintColor = self.theme.list.itemAccentColor @@ -373,9 +373,9 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF @objc private func pastePressed() { if let text = UIPasteboard.general.string, !text.isEmpty { - if checkValidity(text: text) { + if checkValidity(text: text, isPaste: true) { self.textField.textField.text = text - self.updatePasteVisibility() + self.textDidChange() } } } @@ -1036,17 +1036,22 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF return false } + if string == "\n" { + self.proceedPressed() + return false + } + var updated = textField.text ?? "" updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string) if updated.isEmpty { return true } else { - return checkValidity(text: updated) && !updated.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + return checkValidity(text: updated, isPaste: false) && !updated.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } } - func checkValidity(text: String) -> Bool { + func checkValidity(text: String, isPaste: Bool) -> Bool { if let codeType = self.codeType { switch codeType { case let .word(startsWith): @@ -1060,7 +1065,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF if let startsWith, !text.isEmpty { let firstWord = text.components(separatedBy: " ").first ?? "" if !firstWord.isEmpty && !startsWith.hasPrefix(firstWord) { - if self.errorTextNode.alpha.isZero, text.count < 4 { + if self.errorTextNode.alpha.isZero, text.count < 4 || isPaste { self.animateError(text: self.strings.Login_WrongPhraseError) } return false diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index 8f7278fd4da..37584408483 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -1,3 +1,6 @@ +// MARK: Nicegram Analytics +import NGAnalytics +// import Foundation import UIKit import AsyncDisplayKit @@ -1181,6 +1184,30 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } private func updateState(state: InnerState) { + // MARK: Nicegram Analytics + let analyticsManager = AnalyticsContainer.shared.analyticsManager() + + if otherAccountPhoneNumbers.1.isEmpty { + switch state { + case .authorized: + analyticsManager.trackEvent("nicegram_tgauth_success") + case let .state(state): + switch state { + case .phoneEntry, .empty: + analyticsManager.trackEvent("nicegram_tgauth_enter_phone") + case .confirmationCodeEntry: + analyticsManager.trackEvent("nicegram_tgauth_enter_code") + default: + break + } + } + } + + if case .authorized = state { + analyticsManager.trackEvent("telegram_profile_added") + } + // + switch state { case .authorized: self.authorizationCompleted() diff --git a/submodules/AuthorizationUtils/BUILD b/submodules/AuthorizationUtils/BUILD index f1c084d619d..5c75bea9985 100644 --- a/submodules/AuthorizationUtils/BUILD +++ b/submodules/AuthorizationUtils/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/AvatarNode/BUILD b/submodules/AvatarNode/BUILD index fb3fe4bcecc..fbfef40b282 100644 --- a/submodules/AvatarNode/BUILD +++ b/submodules/AvatarNode/BUILD @@ -9,7 +9,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/AvatarVideoNode/BUILD b/submodules/AvatarVideoNode/BUILD index dd69602adc7..c68b3338cd1 100644 --- a/submodules/AvatarVideoNode/BUILD +++ b/submodules/AvatarVideoNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift index 6b17989a2e8..634438cafab 100644 --- a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift +++ b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift @@ -27,7 +27,7 @@ public final class AvatarVideoNode: ASDisplayNode { private var fileDisposable = MetaDisposable() private var animationFile: TelegramMediaFile? - private var itemLayer: EmojiPagerContentComponent.View.ItemLayer? + private var itemLayer: EmojiKeyboardItemLayer? private var useAnimationNode = false private var animationNode: AnimatedStickerNode? private let stickerFetchedDisposable = MetaDisposable() @@ -101,7 +101,7 @@ public final class AvatarVideoNode: ASDisplayNode { let itemNativeFitSize = self.internalSize.width > 100.0 ? CGSize(width: 192.0, height: 192.0) : CGSize(width: 64.0, height: 64.0) let animationData = EntityKeyboardAnimationData(file: animationFile) - let itemLayer = EmojiPagerContentComponent.View.ItemLayer( + let itemLayer = EmojiKeyboardItemLayer( item: EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), diff --git a/submodules/BotPaymentsUI/BUILD b/submodules/BotPaymentsUI/BUILD index 45cfed992ba..2ebdc67fae1 100644 --- a/submodules/BotPaymentsUI/BUILD +++ b/submodules/BotPaymentsUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift index 990bda668db..04b7ea9619c 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, @@ -28,13 +28,22 @@ public final class BotCheckoutController: ViewController { } public static func fetch(context: AccountContext, source: BotPaymentInvoiceSource) -> Signal { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let theme = context.sharedContext.currentPresentationData.with { $0 }.theme let themeParams: [String: Any] = [ - "bg_color": Int32(bitPattern: presentationData.theme.list.plainBackgroundColor.argb), - "text_color": Int32(bitPattern: presentationData.theme.list.itemPrimaryTextColor.argb), - "link_color": Int32(bitPattern: presentationData.theme.list.itemAccentColor.argb), - "button_color": Int32(bitPattern: presentationData.theme.list.itemCheckColors.fillColor.argb), - "button_text_color": Int32(bitPattern: presentationData.theme.list.itemCheckColors.foregroundColor.argb) + "bg_color": Int32(bitPattern: theme.list.plainBackgroundColor.rgb), + "secondary_bg_color": Int32(bitPattern: theme.list.blocksBackgroundColor.rgb), + "text_color": Int32(bitPattern: theme.list.itemPrimaryTextColor.rgb), + "hint_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), + "link_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), + "button_color": Int32(bitPattern: theme.list.itemCheckColors.fillColor.rgb), + "button_text_color": Int32(bitPattern: theme.list.itemCheckColors.foregroundColor.rgb), + "header_bg_color": Int32(bitPattern: theme.rootController.navigationBar.opaqueBackgroundColor.rgb), + "accent_text_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), + "section_bg_color": Int32(bitPattern: theme.list.itemBlocksBackgroundColor.rgb), + "section_header_text_color": Int32(bitPattern: theme.list.freeTextColor.rgb), + "subtitle_text_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), + "destructive_text_color": Int32(bitPattern: theme.list.itemDestructiveColor.rgb), + "section_separator_color": Int32(bitPattern: theme.list.itemBlocksSeparatorColor.rgb) ] return context.engine.payments.fetchBotPaymentForm(source: source, themeParams: themeParams) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift index 96fb118dab0..c66dec6529f 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift @@ -970,7 +970,13 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } else { var dismissImpl: (() -> Void)? - let controller = BotCheckoutWebInteractionController(context: context, url: customUrl ?? paymentForm.url, intent: .addPaymentMethod(customTitle: customTitle, completion: { [weak self] token in + let url: String + if let customUrl { + url = customUrl + } else { + url = paymentForm.url ?? "" + } + let controller = BotCheckoutWebInteractionController(context: context, url: url, intent: .addPaymentMethod(customTitle: customTitle, completion: { [weak self] token in dismissImpl?() guard let strongSelf = self else { @@ -1455,12 +1461,12 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz } if !liabilityNoticeAccepted { - let botPeer: Signal = context.engine.data.get( + let botPeer: Signal = self.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: paymentForm.paymentBotId) ) - let providerPeer: Signal = context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: paymentForm.providerId) - ) + let providerPeer: Signal = paymentForm.providerId.flatMap { + self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: $0)) + } ?? .single(nil) let _ = (combineLatest( ApplicationSpecificNotice.getBotPaymentLiability(accountManager: self.context.sharedContext.accountManager, peerId: paymentForm.paymentBotId), botPeer, diff --git a/submodules/BrowserUI/BUILD b/submodules/BrowserUI/BUILD index 2112ef8200f..3c304bdd02a 100644 --- a/submodules/BrowserUI/BUILD +++ b/submodules/BrowserUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h b/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h index 71be4924379..36d8cca428f 100644 --- a/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h +++ b/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h @@ -23,7 +23,7 @@ @property (nonatomic, readonly) bool isSiriEnabled; + (DeviceSpecificEncryptionParameters * _Nonnull)deviceSpecificEncryptionParameters:(NSString * _Nonnull)rootPath baseAppBundleId:(NSString * _Nonnull)baseAppBundleId; -- (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken signatureDict:(NSDictionary * _Nullable)signatureDict; +- (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken tokenType:(NSString * _Nullable)tokenType tokenEnvironment:(NSString * _Nullable)tokenEnvironment signatureDict:(NSDictionary * _Nullable)signatureDict; + (void)getHardwareEncryptionAvailableWithBaseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^ _Nonnull)(NSData * _Nullable))completion; + (void)encryptApplicationSecret:(NSData * _Nonnull)secret baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^ _Nonnull)(NSData * _Nullable, NSData * _Nullable))completion; diff --git a/submodules/BuildConfig/Sources/BuildConfig.m b/submodules/BuildConfig/Sources/BuildConfig.m index 902cab59a60..a9cc1a1b165 100644 --- a/submodules/BuildConfig/Sources/BuildConfig.m +++ b/submodules/BuildConfig/Sources/BuildConfig.m @@ -140,11 +140,16 @@ - (instancetype _Nonnull)initWithBaseAppBundleId:(NSString * _Nonnull)baseAppBun return self; } -- (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken signatureDict:(NSDictionary * _Nullable)signatureDict { +- (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken tokenType:(NSString * _Nullable)tokenType tokenEnvironment:(NSString * _Nullable)tokenEnvironment signatureDict:(NSDictionary * _Nullable)signatureDict { NSMutableDictionary *dataDict = [[NSMutableDictionary alloc] initWithDictionary:_dataDict]; if (appToken != nil) { dataDict[@"device_token"] = [appToken base64EncodedStringWithOptions:0]; - dataDict[@"device_token_type"] = @"voip"; + if (tokenType != nil) { + dataDict[@"device_token_type"] = tokenType; + } + if (tokenEnvironment != nil) { + dataDict[@"device_token_environment"] = tokenEnvironment; + } } float tzOffset = [[NSTimeZone systemTimeZone] secondsFromGMT]; dataDict[@"tz_offset"] = @((int)tzOffset); diff --git a/submodules/CalendarMessageScreen/BUILD b/submodules/CalendarMessageScreen/BUILD index db198e2a0ac..1f7b2577fd8 100644 --- a/submodules/CalendarMessageScreen/BUILD +++ b/submodules/CalendarMessageScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/CallListUI/BUILD b/submodules/CallListUI/BUILD index 636bbe2779f..01f8dc8c51b 100644 --- a/submodules/CallListUI/BUILD +++ b/submodules/CallListUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 98f1cec192a..45cd61a8947 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -389,7 +389,7 @@ public final class CallListController: TelegramBaseController { |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak controller, weak self] result in controller?.dismissSearch() - if let strongSelf = self, let (contactPeers, action, _, _, _) = result, let contactPeer = contactPeers.first, case let .peer(peer, _, _) = contactPeer { + if let strongSelf = self, let (contactPeers, action, _, _, _, _) = result, let contactPeer = contactPeers.first, case let .peer(peer, _, _) = contactPeer { strongSelf.call(peer.id, isVideo: action == .videoCall, began: { if let strongSelf = self { let _ = (strongSelf.context.sharedContext.hasOngoingCall.get() @@ -493,7 +493,7 @@ public final class CallListController: TelegramBaseController { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Calls_StartNewCall, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let strongSelf = self else { return } diff --git a/submodules/Camera/BUILD b/submodules/Camera/BUILD index 19fec724ada..dc8607190e0 100644 --- a/submodules/Camera/BUILD +++ b/submodules/Camera/BUILD @@ -51,7 +51,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":CameraBundle", diff --git a/submodules/ChatContextQuery/BUILD b/submodules/ChatContextQuery/BUILD index e7940d5e71c..a86c21d6474 100644 --- a/submodules/ChatContextQuery/BUILD +++ b/submodules/ChatContextQuery/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ChatImportUI/BUILD b/submodules/ChatImportUI/BUILD index 2ee541d7dae..4c5877dea15 100644 --- a/submodules/ChatImportUI/BUILD +++ b/submodules/ChatImportUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ChatInterfaceState/BUILD b/submodules/ChatInterfaceState/BUILD index 4d2c0bdd17a..c96a0cc07ac 100644 --- a/submodules/ChatInterfaceState/BUILD +++ b/submodules/ChatInterfaceState/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift index 99f311bd4d2..7fd19cb3b72 100644 --- a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift +++ b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift @@ -47,12 +47,14 @@ public struct ChatEditMessageState: Codable, Equatable { public var inputState: ChatTextInputState public var disableUrlPreviews: [String] public var inputTextMaxLength: Int32? + public var mediaCaptionIsAbove: Bool? - public init(messageId: EngineMessage.Id, inputState: ChatTextInputState, disableUrlPreviews: [String], inputTextMaxLength: Int32?) { + public init(messageId: EngineMessage.Id, inputState: ChatTextInputState, disableUrlPreviews: [String], inputTextMaxLength: Int32?, mediaCaptionIsAbove: Bool?) { self.messageId = messageId self.inputState = inputState self.disableUrlPreviews = disableUrlPreviews self.inputTextMaxLength = inputTextMaxLength + self.mediaCaptionIsAbove = mediaCaptionIsAbove } public init(from decoder: Decoder) throws { @@ -80,6 +82,8 @@ public struct ChatEditMessageState: Codable, Equatable { } } self.inputTextMaxLength = try? container.decodeIfPresent(Int32.self, forKey: "tl") + + self.mediaCaptionIsAbove = try? container.decodeIfPresent(Bool.self, forKey: "mediaCaptionIsAbove") } @@ -94,18 +98,20 @@ public struct ChatEditMessageState: Codable, Equatable { try container.encode(self.disableUrlPreviews, forKey: "dupl") try container.encodeIfPresent(self.inputTextMaxLength, forKey: "tl") + + try container.encodeIfPresent(self.mediaCaptionIsAbove, forKey: "mediaCaptionIsAbove") } public static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool { - return lhs.messageId == rhs.messageId && lhs.inputState == rhs.inputState && lhs.disableUrlPreviews == rhs.disableUrlPreviews && lhs.inputTextMaxLength == rhs.inputTextMaxLength + return lhs.messageId == rhs.messageId && lhs.inputState == rhs.inputState && lhs.disableUrlPreviews == rhs.disableUrlPreviews && lhs.inputTextMaxLength == rhs.inputTextMaxLength && lhs.mediaCaptionIsAbove == rhs.mediaCaptionIsAbove } public func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState { - return ChatEditMessageState(messageId: self.messageId, inputState: inputState, disableUrlPreviews: self.disableUrlPreviews, inputTextMaxLength: self.inputTextMaxLength) + return ChatEditMessageState(messageId: self.messageId, inputState: inputState, disableUrlPreviews: self.disableUrlPreviews, inputTextMaxLength: self.inputTextMaxLength, mediaCaptionIsAbove: self.mediaCaptionIsAbove) } public func withUpdatedDisableUrlPreviews(_ disableUrlPreviews: [String]) -> ChatEditMessageState { - return ChatEditMessageState(messageId: self.messageId, inputState: self.inputState, disableUrlPreviews: disableUrlPreviews, inputTextMaxLength: self.inputTextMaxLength) + return ChatEditMessageState(messageId: self.messageId, inputState: self.inputState, disableUrlPreviews: disableUrlPreviews, inputTextMaxLength: self.inputTextMaxLength, mediaCaptionIsAbove: self.mediaCaptionIsAbove) } } @@ -473,7 +479,8 @@ public final class ChatInterfaceState: Codable, Equatable { if self.composeInputState.inputText.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && self.replyMessageSubject == nil { return nil } else { - return SynchronizeableChatInputState(replySubject: self.replyMessageSubject?.subjectModel, text: self.composeInputState.inputText.string, entities: generateChatInputTextEntities(self.composeInputState.inputText), timestamp: self.timestamp, textSelection: self.composeInputState.selectionRange) + let sourceText = expandedInputStateAttributedString(self.composeInputState.inputText) + return SynchronizeableChatInputState(replySubject: self.replyMessageSubject?.subjectModel, text: sourceText.string, entities: generateChatInputTextEntities(sourceText), timestamp: self.timestamp, textSelection: self.composeInputState.selectionRange) } } diff --git a/submodules/ChatListFilterSettingsHeaderItem/BUILD b/submodules/ChatListFilterSettingsHeaderItem/BUILD index 0868cf13ab3..0967aa5feae 100644 --- a/submodules/ChatListFilterSettingsHeaderItem/BUILD +++ b/submodules/ChatListFilterSettingsHeaderItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ChatListSearchItemHeader/BUILD b/submodules/ChatListSearchItemHeader/BUILD index 8154e629b46..206cdc79bb6 100644 --- a/submodules/ChatListSearchItemHeader/BUILD +++ b/submodules/ChatListSearchItemHeader/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/ChatListSearchItemNode/BUILD b/submodules/ChatListSearchItemNode/BUILD index 173234c78e0..0f1724a5a54 100644 --- a/submodules/ChatListSearchItemNode/BUILD +++ b/submodules/ChatListSearchItemNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ChatListSearchRecentPeersNode/BUILD b/submodules/ChatListSearchRecentPeersNode/BUILD index d0c9096304c..d40d99268f4 100644 --- a/submodules/ChatListSearchRecentPeersNode/BUILD +++ b/submodules/ChatListSearchRecentPeersNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 12db43b4bcd..eeede04cfe6 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -24,7 +24,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index f92c7647523..c45ed74a81b 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -213,7 +213,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch return filters } |> deliverOnMainQueue).startStandalone(completed: { - c.dismiss(completion: { + c?.dismiss(completion: { chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatRemovedFromFolder(chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) @@ -279,7 +279,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let isPremium = limitsData.0?.isPremium ?? false let (_, limits, premiumLimits) = limitsData @@ -334,10 +334,10 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch updatedItems.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in - c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + c?.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) - c.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil, animated: true) + c?.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil, animated: true) }))) } } @@ -695,7 +695,7 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: /*subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.popItems() + c?.popItems() }))) subItems.append(.separator)*/ @@ -728,8 +728,7 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: } }))) - //c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) - c.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: nil, animated: true) + c?.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: nil, animated: true) }))) items.append(.separator) @@ -884,7 +883,7 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: ], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) }))) - c.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) + c?.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) } }))) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index e1b9638e1c2..e26e89ac858 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -352,7 +352,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController pressed: { Task { AssistantUITgHelper.presentAssistantModally( - source: .generic + source: .navigationBarIcon ) } } @@ -1113,7 +1113,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if case let .channel(channel) = peer, channel.flags.contains(.isForum), let threadId { - let _ = self.context.sharedContext.navigateToForumThread(context: self.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).startStandalone() + let _ = self.context.sharedContext.navigateToForumThread(context: self.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: scrollToEndIfExists, keepStack: .never).startStandalone() self.chatListDisplayNode.clearHighlightAnimated(true) } else { var navigationAnimationOptions: NavigationAnimationOptions = [] @@ -1260,7 +1260,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController navigationAnimationOptions = .removeOnMasterDetails } if case let .channel(channel) = actualPeer, channel.flags.contains(.isForum), let threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, keepStack: .never).startStandalone() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .never).startStandalone() } else { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), purposefulAction: { if deactivateOnAction { @@ -1293,7 +1293,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController navigationAnimationOptions = .removeOnMasterDetails } if case let .channel(channel) = peer, channel.flags.contains(.isForum), let threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).startStandalone() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .never).startStandalone() } else { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), purposefulAction: { [weak self] in self?.deactivateSearch(animated: false) @@ -1371,7 +1371,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: iconColor, iconFileId: fileId) |> deliverOnMainQueue).startStandalone(next: { topicId in - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, keepStack: .never).startStandalone() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, scrollToEndIfExists: false, keepStack: .never).startStandalone() }, error: { _ in controller?.isInProgress = false }) @@ -1651,7 +1651,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_EditFolder, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { guard let strongSelf = self else { return } @@ -1694,7 +1694,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_AddChatsToFolder, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { guard let strongSelf = self else { return } @@ -1787,7 +1787,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ReadAll, textColor: .primary, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReadAll"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { guard let strongSelf = self else { return } @@ -1802,7 +1802,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: filterPeersAreMuted.areMuted ? strongSelf.presentationData.strings.ChatList_ContextUnmuteAll : strongSelf.presentationData.strings.ChatList_ContextMuteAll, textColor: .primary, badge: nil, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: filterPeersAreMuted.areMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { }) guard let strongSelf = self else { @@ -1845,7 +1845,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ContextMenuShare, textColor: .primary, badge: data.hasSharedLinks ? nil : ContextMenuActionBadge(value: strongSelf.presentationData.strings.ChatList_ContextMenuBadgeNew, color: .accent, style: .label), icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { guard let strongSelf = self else { return } @@ -1866,7 +1866,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_RemoveFolder, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { guard let strongSelf = self else { return } @@ -1878,7 +1878,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_EditFolders, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { guard let strongSelf = self else { return } @@ -1892,7 +1892,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ReorderTabs, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { guard let strongSelf = self else { return } @@ -2148,6 +2148,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } + private weak var storyTooltip: TooltipScreen? fileprivate func maybeDisplayStoryTooltip() { let content = self.updateHeaderContent() if content.secondaryContent != nil { @@ -2202,6 +2203,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } ) self.present(tooltipScreen, in: .current) + self.storyTooltip = tooltipScreen #if !DEBUG let _ = ApplicationSpecificNotice.setDisplayChatListStoriesTooltip(accountManager: self.context.sharedContext.accountManager).startStandalone() @@ -2977,7 +2979,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextAddStory, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let self else { return } @@ -2989,7 +2991,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextSavedStories, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let self else { return } @@ -3001,7 +3003,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextArchivedStories, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let self else { return } @@ -3023,7 +3025,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: openTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: openIcon), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let self else { return } @@ -3092,7 +3094,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextOpenChat, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let self, let navigationController = self.navigationController as? NavigationController else { return } @@ -3104,7 +3106,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextOpenProfile, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let self else { return } @@ -3662,7 +3664,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: iconColor, iconFileId: fileId) |> deliverOnMainQueue).startStandalone(next: { topicId in if let navigationController = (sourceController.navigationController as? NavigationController) { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, keepStack: .never).startStandalone() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, scrollToEndIfExists: false, keepStack: .never).startStandalone() } }, error: { _ in controller?.isInProgress = false @@ -4471,7 +4473,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.activateSearch(filter: filter, query: query) } + private var previousSearchToggleTimestamp: Double? func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil, skipScrolling: Bool = false, searchContentNode: NavigationBarSearchContentNode) { + let currentTimestamp = CACurrentMediaTime() + if let previousSearchActivationTimestamp = self.previousSearchToggleTimestamp, currentTimestamp < previousSearchActivationTimestamp + 0.6 { + return + } + self.previousSearchToggleTimestamp = currentTimestamp + + if let storyTooltip = self.storyTooltip { + storyTooltip.dismiss() + } + var filter = filter if case .forum = self.chatListDisplayNode.effectiveContainerNode.location { filter = .topics @@ -4546,46 +4559,53 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } public func deactivateSearch(animated: Bool) { - if !self.displayNavigationBar { - var completion: (() -> Void)? - - self.searchTabsNode = nil - - var searchContentNode: NavigationBarSearchContentNode? - if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View { - searchContentNode = navigationBarView.searchContentNode - } - - if let searchContentNode { - let previousFrame = searchContentNode.placeholderNode.frame - if case .chatList(.root) = self.location { - searchContentNode.placeholderNode.frame = previousFrame.offsetBy(dx: 0.0, dy: 79.0) - } - completion = self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated) - searchContentNode.placeholderNode.frame = previousFrame + guard !self.displayNavigationBar else { + return + } + let currentTimestamp = CACurrentMediaTime() + if let previousSearchActivationTimestamp = self.previousSearchToggleTimestamp, currentTimestamp < previousSearchActivationTimestamp + 0.6 { + return + } + self.previousSearchToggleTimestamp = currentTimestamp + + var completion: (() -> Void)? + + self.searchTabsNode = nil + + var searchContentNode: NavigationBarSearchContentNode? + if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View { + searchContentNode = navigationBarView.searchContentNode + } + + if let searchContentNode { + let previousFrame = searchContentNode.placeholderNode.frame + if case .chatList(.root) = self.location { + searchContentNode.placeholderNode.frame = previousFrame.offsetBy(dx: 0.0, dy: 79.0) } - - self.chatListDisplayNode.tempAllowAvatarExpansion = true - self.requestLayout(transition: .animated(duration: 0.5, curve: .spring)) - self.chatListDisplayNode.tempAllowAvatarExpansion = false - - //TODO:swap tabs - - let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate - //transition.updateAlpha(node: self.tabContainerNode, alpha: tabsIsEmpty ? 0.0 : 1.0) - self.setDisplayNavigationBar(true, transition: transition) - - completion?() - - (self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring)) - - self.isSearchActive = false - if let navigationController = self.navigationController as? NavigationController { - for controller in navigationController.globalOverlayControllers { - if let controller = controller as? VoiceChatOverlayController { - controller.updateVisibility() - break - } + completion = self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated) + searchContentNode.placeholderNode.frame = previousFrame + } + + self.chatListDisplayNode.tempAllowAvatarExpansion = true + self.requestLayout(transition: .animated(duration: 0.5, curve: .spring)) + self.chatListDisplayNode.tempAllowAvatarExpansion = false + + //TODO:swap tabs + + let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate + //transition.updateAlpha(node: self.tabContainerNode, alpha: tabsIsEmpty ? 0.0 : 1.0) + self.setDisplayNavigationBar(true, transition: transition) + + completion?() + + (self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring)) + + self.isSearchActive = false + if let navigationController = self.navigationController as? NavigationController { + for controller in navigationController.globalOverlayControllers { + if let controller = controller as? VoiceChatOverlayController { + controller.updateVisibility() + break } } } @@ -5870,7 +5890,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController items.append(.action(ContextMenuActionItem(text: presetList.isEmpty ? strongSelf.presentationData.strings.ChatList_AddFolder : strongSelf.presentationData.strings.ChatList_EditFolders, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: presetList.isEmpty ? "Chat/Context Menu/Add" : "Chat/Context Menu/ItemList"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { guard let strongSelf = self else { return } diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index a0df8aa5989..081f1d4d6ba 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -203,8 +203,10 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }, openResolved: { [weak self] resolved in context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peerId, navigation in - }, sendFile: nil, + }, + sendFile: nil, sendSticker: nil, + sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { c, a in @@ -1053,7 +1055,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), nil, message.id, false) }) }))) @@ -1063,7 +1065,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo items.append(.separator) } items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self { strongSelf.dismissInput() @@ -1114,7 +1116,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo if let linkForCopying = linkForCopying { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: {}) + c?.dismiss(completion: {}) UIPasteboard.general.string = linkForCopying let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -1124,7 +1126,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo if !message._asMessage().isCopyProtected() { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in if let strongSelf = self { strongSelf.forwardMessages(messageIds: Set([message.id])) } @@ -1132,14 +1134,14 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }))) } items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.threadId, message.id, false) }) }))) items.append(.separator) items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self { strongSelf.dismissInput() @@ -1178,7 +1180,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo var items: [ContextMenuItem] = [] items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.threadId, message.id, false) }) }))) @@ -1187,7 +1189,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } else { items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self { strongSelf.forwardMessages(messageIds: [message.id]) } @@ -1367,7 +1369,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: messages.values.filter { messageIds.contains($0.id) }) let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true, selectForumThreads: true)) - peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in + peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in guard let strongSelf = self, let strongController = peerSelectionController else { return } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 3e197aa17b9..1ab62f620fe 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -100,7 +100,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, - peerSelected: @escaping (EnginePeer, Int64?) -> Void, + peerSelected: @escaping (EnginePeer, Int64?, Bool) -> Void, disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, @@ -114,7 +114,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { switch self { case let .topPeers(peers, theme, strings): return ChatListRecentPeersListItem(theme: theme, strings: strings, context: context, peers: peers, peerSelected: { peer in - peerSelected(peer, nil) + peerSelected(peer, nil, false) }, peerContextAction: { peer, node, gesture, location in if let peerContextAction = peerContextAction { peerContextAction(peer, .recentPeers, node, gesture, location) @@ -267,7 +267,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { header: header, action: { _ in if let chatPeer = peer.peer.peers[peer.peer.peerId] { - peerSelected(EnginePeer(chatPeer), nil) + peerSelected(EnginePeer(chatPeer), nil, section == .recommendedChannels) } }, disabledAction: { _ in @@ -969,7 +969,7 @@ private func chatListSearchContainerPreparedRecentTransition( presentationData: ChatListPresentationData, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, - peerSelected: @escaping (EnginePeer, Int64?) -> Void, + peerSelected: @escaping (EnginePeer, Int64?, Bool) -> Void, disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, clearRecentlySearchedPeers: @escaping () -> Void, @@ -1790,7 +1790,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { |> then( context.engine.contacts.searchRemotePeers(query: query) |> map { ($0.0, $0.1, false) } - |> delay(0.2, queue: Queue.concurrentDefaultQueue()) + |> delay(0.4, queue: Queue.concurrentDefaultQueue()) ) ) } else if let query = query, case .channels = key { @@ -1799,7 +1799,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { |> then( context.engine.contacts.searchRemotePeers(query: query, scope: .channels) |> map { ($0.0, $0.1, false) } - |> delay(0.2, queue: Queue.concurrentDefaultQueue()) + |> delay(0.4, queue: Queue.concurrentDefaultQueue()) ) ) } else { @@ -3183,19 +3183,19 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } - let transition = chatListSearchContainerPreparedRecentTransition(from: previousRecentItems?.entries ?? [], to: recentItems.entries, forceUpdateAll: forceUpdateAll, context: context, presentationData: presentationData, filter: peersFilter, key: key, peerSelected: { peer, threadId in + let transition = chatListSearchContainerPreparedRecentTransition(from: previousRecentItems?.entries ?? [], to: recentItems.entries, forceUpdateAll: forceUpdateAll, context: context, presentationData: presentationData, filter: peersFilter, key: key, peerSelected: { peer, threadId, isRecommended in guard let self else { return } if case .channels = key { if let navigationController = self.navigationController { - var customChatNavigationStack: [EnginePeer.Id] = [] - if let entries = previousRecentItemsValue.with({ $0 })?.entries { - for entry in entries { - if case let .peer(_, peer, _, _, _, _, _, _, _, _, _) = entry { - customChatNavigationStack.append(peer.peer.peerId) - } + var customChatNavigationStack: [EnginePeer.Id]? + if isRecommended { + if let recommendedChannelOrder = previousRecentItemsValue.with({ $0 })?.recommendedChannelOrder { + var customChatNavigationStackValue: [EnginePeer.Id] = [] + customChatNavigationStackValue.append(contentsOf: recommendedChannelOrder) + customChatNavigationStack = customChatNavigationStackValue } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 0ae0d262aab..aa09f82870f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -3131,9 +3131,11 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { isFirstForumThreadSelectable = forumThread.isUnread forumThreads.append((id: forumThread.id, title: NSAttributedString(string: forumThread.title, font: textFont, textColor: forumThread.isUnread || isSearching ? theme.authorNameColor : theme.messageTextColor), iconId: forumThread.iconId, iconColor: forumThread.iconColor)) } - for item in topForumTopicItems { - if forumThread?.id != item.id { - forumThreads.append((id: item.id, title: NSAttributedString(string: item.title, font: textFont, textColor: item.isUnread || isSearching ? theme.authorNameColor : theme.messageTextColor), iconId: item.iconFileId, iconColor: item.iconColor)) + for topicItem in topForumTopicItems { + if case let .peer(peer) = item.content, peer.peer.peerId.id._internalGetInt64Value() == topicItem.id { + + } else if forumThread?.id != topicItem.id { + forumThreads.append((id: topicItem.id, title: NSAttributedString(string: topicItem.title, font: textFont, textColor: topicItem.isUnread || isSearching ? theme.authorNameColor : theme.messageTextColor), iconId: topicItem.iconFileId, iconColor: topicItem.iconColor)) } } @@ -4153,7 +4155,6 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.textNode.textNode.alpha = 0.0 strongSelf.authorNode.alpha = 0.0 strongSelf.compoundHighlightingNode?.alpha = 0.0 - strongSelf.dustNode?.alpha = 0.0 strongSelf.forwardedIconNode.alpha = 0.0 if animated || animateContent { @@ -4165,13 +4166,13 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.forwardedIconNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) } } + strongSelf.dustNode?.alpha = 0.0 } else { if !strongSelf.inputActivitiesNode.alpha.isZero { strongSelf.inputActivitiesNode.alpha = 0.0 strongSelf.textNode.textNode.alpha = 1.0 strongSelf.authorNode.alpha = 1.0 strongSelf.compoundHighlightingNode?.alpha = 1.0 - strongSelf.dustNode?.alpha = 1.0 strongSelf.forwardedIconNode.alpha = 1.0 if animated || animateContent { strongSelf.inputActivitiesNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { value in @@ -4188,6 +4189,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.inputActivitiesNode.removeFromSupernode() } } + strongSelf.dustNode?.alpha = 1.0 } if let inputActivitiesSize = inputActivitiesSize { let inputActivitiesFrame = CGRect(origin: CGPoint(x: contentRect.minX, y: authorNodeFrame.minY + UIScreenPixel), size: inputActivitiesSize) diff --git a/submodules/ChatMessageBackground/BUILD b/submodules/ChatMessageBackground/BUILD index 84fd9a50743..05b2a066d84 100644 --- a/submodules/ChatMessageBackground/BUILD +++ b/submodules/ChatMessageBackground/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift index 59bd0ac957f..be74c8e3b12 100644 --- a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift +++ b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift @@ -492,7 +492,7 @@ public func bubbleMaskForType(_ type: ChatMessageBackgroundType, graphics: Princ } public final class ChatMessageBubbleBackdrop: ASDisplayNode { - private var backgroundContent: WallpaperBubbleBackgroundNode? + public private(set) var backgroundContent: WallpaperBubbleBackgroundNode? private var currentType: ChatMessageBackgroundType? private var currentMaskMode: Bool? diff --git a/submodules/ChatMessageInteractiveMediaBadge/BUILD b/submodules/ChatMessageInteractiveMediaBadge/BUILD index 4fe306285a6..0417e04246a 100644 --- a/submodules/ChatMessageInteractiveMediaBadge/BUILD +++ b/submodules/ChatMessageInteractiveMediaBadge/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/ChatPresentationInterfaceState/BUILD b/submodules/ChatPresentationInterfaceState/BUILD index 566e775d539..c5df1a88c3b 100644 --- a/submodules/ChatPresentationInterfaceState/BUILD +++ b/submodules/ChatPresentationInterfaceState/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift index f123a516d2a..6c76dab4ed1 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift @@ -59,7 +59,7 @@ public func chatTextInputAddFormattingAttribute(_ state: ChatTextInputState, att for (key, value) in attributes { if let value = value as? ChatTextInputTextQuoteAttribute { result.removeAttribute(key, range: range) - result.addAttribute(key, value: ChatTextInputTextQuoteAttribute(kind: value.kind), range: range) + result.addAttribute(key, value: ChatTextInputTextQuoteAttribute(kind: value.kind, isCollapsed: value.isCollapsed), range: range) } } } @@ -72,7 +72,7 @@ public func chatTextInputAddFormattingAttribute(_ state: ChatTextInputState, att if addAttribute { if attribute == ChatTextInputAttributes.block { - result.addAttribute(attribute, value: value ?? ChatTextInputTextQuoteAttribute(kind: .quote), range: nsRange) + result.addAttribute(attribute, value: value ?? ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false), range: nsRange) var selectionIndex = nsRange.upperBound if nsRange.upperBound != result.length && (result.string as NSString).character(at: nsRange.upperBound) != 0x0a { result.insert(NSAttributedString(string: "\n"), at: nsRange.upperBound) @@ -197,6 +197,6 @@ public func chatTextInputAddQuoteAttribute(_ state: ChatTextInputState, selectio for (attribute, range) in attributesToRemove { result.removeAttribute(attribute, range: range) } - result.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: kind), range: nsRange) + result.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: kind, isCollapsed: false), range: nsRange) return ChatTextInputState(inputText: result, selectionRange: selectionRange) } diff --git a/submodules/ChatSendMessageActionUI/BUILD b/submodules/ChatSendMessageActionUI/BUILD index 772c15f75c2..0fed2b4d6e7 100644 --- a/submodules/ChatSendMessageActionUI/BUILD +++ b/submodules/ChatSendMessageActionUI/BUILD @@ -11,13 +11,14 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", "//submodules/TelegramCore:TelegramCore", + "//submodules/Postbox", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/ContextUI:ContextUI", @@ -25,6 +26,22 @@ swift_library( "//submodules/TextFormat:TextFormat", "//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView", "//submodules/TelegramUI/Components/Chat/ChatInputTextNode", + "//submodules/TelegramUI/Components/EntityKeyboard", + "//submodules/ReactionSelectionNode", + "//submodules/Components/ReactionButtonListComponent", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/ComponentFlow", + "//submodules/ChatMessageBackground", + "//submodules/WallpaperBackgroundNode", + "//submodules/Components/MultilineTextWithEntitiesComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/LottieMetal", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/ActivityIndicator", + "//submodules/UndoUI", + "//submodules/RadialStatusNode", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift index 099ecbeadad..5cc4709e417 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift @@ -1,6 +1,3 @@ -// MARK: Nicegram Imports -import NGTranslate -// import Foundation import UIKit import Display @@ -11,174 +8,93 @@ import AccountContext import ContextUI import TelegramCore import TextFormat +import ReactionSelectionNode +import WallpaperBackgroundNode -public final class ChatSendMessageActionSheetController: ViewController { - public enum SendMode { - case generic - case silently - case whenOnline - } - private var controllerNode: ChatSendMessageActionSheetControllerNode { - return self.displayNode as! ChatSendMessageActionSheetControllerNode - } - - // MARK: Nicegram TranslateEnteredMessage - private let canTranslate: Bool - private let translate: () -> Void - private let chooseLanguage: () -> Void - // - - private let context: AccountContext - - private let peerId: EnginePeer.Id? - private let isScheduledMessages: Bool - private let forwardMessageIds: [EngineMessage.Id]? - private let hasEntityKeyboard: Bool - - private let gesture: ContextGesture - private let sourceSendButton: ASDisplayNode - private let textInputView: UITextView - private let attachment: Bool - private let canSendWhenOnline: Bool - private let completion: () -> Void - private let sendMessage: (SendMode) -> Void - private let schedule: () -> Void - - private var presentationData: PresentationData - private var presentationDataDisposable: Disposable? - - private var didPlayPresentationAnimation = false - - private var validLayout: ContainerViewLayout? - - private let hapticFeedback = HapticFeedback() - - public var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? - - // MARK: Nicegram TranslateEnteredMessage, add (canTranslate, translate, chooseLanguage) - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id?, isScheduledMessages: Bool = false, forwardMessageIds: [EngineMessage.Id]?, hasEntityKeyboard: Bool, gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputView: UITextView, attachment: Bool = false, canSendWhenOnline: Bool, completion: @escaping () -> Void, sendMessage: @escaping (SendMode) -> Void, canTranslate: Bool = false, translate: @escaping () -> Void = {}, chooseLanguage: @escaping () -> Void = {}, schedule: @escaping () -> Void) { - self.context = context - self.peerId = peerId - self.isScheduledMessages = isScheduledMessages - self.forwardMessageIds = forwardMessageIds - self.hasEntityKeyboard = hasEntityKeyboard - self.gesture = gesture - self.sourceSendButton = sourceSendButton - self.textInputView = textInputView - self.attachment = attachment - self.canSendWhenOnline = canSendWhenOnline - self.completion = completion - self.sendMessage = sendMessage - // MARK: Nicegram TranslateEnteredMessage - self.canTranslate = canTranslate - self.translate = translate - self.chooseLanguage = chooseLanguage - // - self.schedule = schedule - - self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - - super.init(navigationBarPresentationData: nil) +public enum SendMessageActionSheetControllerParams { + public final class SendMessage { + public let isScheduledMessages: Bool + public let mediaPreview: ChatSendMessageContextScreenMediaPreview? + public let mediaCaptionIsAbove: (Bool, (Bool) -> Void)? + public let attachment: Bool + public let canSendWhenOnline: Bool + public let forwardMessageIds: [EngineMessage.Id] - self.blocksBackgroundWhenInOverlay = true - - self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) - |> deliverOnMainQueue).startStrict(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.presentationData = presentationData - if strongSelf.isNodeLoaded { - strongSelf.controllerNode.updatePresentationData(presentationData) - } - } - }).strict() - - self.statusBar.statusBarStyle = .Hide - self.statusBar.ignoreInCall = true - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDataDisposable?.dispose() - } - - override public func loadDisplayNode() { - var forwardedCount: Int? - if let forwardMessageIds = self.forwardMessageIds, forwardMessageIds.count > 0 { - forwardedCount = forwardMessageIds.count - } - - var reminders = false - var isSecret = false - var canSchedule = false - if let peerId = self.peerId { - reminders = peerId == context.account.peerId - isSecret = peerId.namespace == Namespaces.Peer.SecretChat - canSchedule = !isSecret - } - if self.isScheduledMessages { - canSchedule = false + public init( + isScheduledMessages: Bool, + mediaPreview: ChatSendMessageContextScreenMediaPreview?, + mediaCaptionIsAbove: (Bool, (Bool) -> Void)?, + attachment: Bool, + canSendWhenOnline: Bool, + forwardMessageIds: [EngineMessage.Id] + ) { + self.isScheduledMessages = isScheduledMessages + self.mediaPreview = mediaPreview + self.mediaCaptionIsAbove = mediaCaptionIsAbove + self.attachment = attachment + self.canSendWhenOnline = canSendWhenOnline + self.forwardMessageIds = forwardMessageIds } - - // MARK: Nicegram TranslateEnteredMessage - let interlocutorLangCode = getCachedLanguageCode(forChatWith: peerId) - // - // MARK: Nicegram TranslateEnteredMessage, change (interlocutorLangCode + translate + chooseLanguage) - self.displayNode = ChatSendMessageActionSheetControllerNode(context: self.context, presentationData: self.presentationData, reminders: reminders, gesture: gesture, sourceSendButton: self.sourceSendButton, textInputView: self.textInputView, attachment: self.attachment, canSendWhenOnline: self.canSendWhenOnline, forwardedCount: forwardedCount, hasEntityKeyboard: self.hasEntityKeyboard, emojiViewProvider: self.emojiViewProvider, send: { [weak self] in - self?.sendMessage(.generic) - self?.dismiss(cancel: false) - }, sendSilently: { [weak self] in - self?.sendMessage(.silently) - self?.dismiss(cancel: false) - }, sendWhenOnline: { [weak self] in - self?.sendMessage(.whenOnline) - self?.dismiss(cancel: false) - }, schedule: !canSchedule ? nil : { [weak self] in - self?.schedule() - self?.dismiss(cancel: false) - }, canTranslate: self.canTranslate, interlocutorLangCode: interlocutorLangCode, translate: { [weak self] in - self?.translate() - self?.dismiss(cancel: false) - }, chooseLanguage: { [weak self] in - self?.chooseLanguage() - self?.dismiss(cancel: false) - }, cancel: { [weak self] in - self?.dismiss(cancel: true) - }) - self.displayNodeDidLoad() } - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) + public final class EditMessage { + public let messages: [EngineMessage] + public let mediaPreview: ChatSendMessageContextScreenMediaPreview? + public let mediaCaptionIsAbove: (Bool, (Bool) -> Void)? - if !self.didPlayPresentationAnimation { - self.didPlayPresentationAnimation = true - - self.hapticFeedback.impact() - self.controllerNode.animateIn() + public init(messages: [EngineMessage], mediaPreview: ChatSendMessageContextScreenMediaPreview?, mediaCaptionIsAbove: (Bool, (Bool) -> Void)?) { + self.messages = messages + self.mediaPreview = mediaPreview + self.mediaCaptionIsAbove = mediaCaptionIsAbove } } - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.validLayout = layout - - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, transition: transition) - } - - override public func dismiss(completion: (() -> Void)? = nil) { - self.dismiss(cancel: true) - } - - private func dismiss(cancel: Bool) { - self.statusBar.statusBarStyle = .Ignore - self.controllerNode.animateOut(cancel: cancel, completion: { [weak self] in - self?.completion() - self?.didPlayPresentationAnimation = false - self?.presentingViewController?.dismiss(animated: false, completion: nil) - }) - } + case sendMessage(SendMessage) + case editMessage(EditMessage) +} + +public func makeChatSendMessageActionSheetController( + // MARK: Nicegram TranslateEnteredMessage + nicegramData: ChatSendMessageContextNicegramData = .empty, + // + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + peerId: EnginePeer.Id?, + params: SendMessageActionSheetControllerParams, + hasEntityKeyboard: Bool, + gesture: ContextGesture, + sourceSendButton: ASDisplayNode, + textInputView: UITextView, + emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, + wallpaperBackgroundNode: WallpaperBackgroundNode? = nil, + completion: @escaping () -> Void, + sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, + schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, + openPremiumPaywall: @escaping (ViewController) -> Void, + reactionItems: [ReactionItem]? = nil, + availableMessageEffects: AvailableMessageEffects? = nil, + isPremium: Bool = false +) -> ChatSendMessageActionSheetController { + return ChatSendMessageContextScreen( + // MARK: Nicegram TranslateEnteredMessage + nicegramData: nicegramData, + // + context: context, + updatedPresentationData: updatedPresentationData, + peerId: peerId, + params: params, + hasEntityKeyboard: hasEntityKeyboard, + gesture: gesture, + sourceSendButton: sourceSendButton, + textInputView: textInputView, + emojiViewProvider: emojiViewProvider, + wallpaperBackgroundNode: wallpaperBackgroundNode, + completion: completion, + sendMessage: sendMessage, + schedule: schedule, + openPremiumPaywall: openPremiumPaywall, + reactionItems: reactionItems, + availableMessageEffects: availableMessageEffects, + isPremium: isPremium + ) } diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift index 623ad5581ba..8b137891791 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift @@ -1,910 +1 @@ -// MARK: Nicegram Imports -import NGStrings -// -import Foundation -import UIKit -import AsyncDisplayKit -import SwiftSignalKit -import Display -import TelegramCore -import TelegramPresentationData -import AccountContext -import AppBundle -import ContextUI -import TextFormat -import EmojiTextAttachmentView -import ChatInputTextNode -private let leftInset: CGFloat = 16.0 -private let rightInset: CGFloat = 16.0 - -private enum ChatSendMessageActionIcon { - // MARK: Nicegram TranslateEnteredMessage - case translate - case language - // - case sendWithoutSound - case sendWhenOnline - case schedule - - func image(theme: PresentationTheme) -> UIImage? { - let imageName: String - switch self { - // MARK: Nicegram TranslateEnteredMessage - case .translate: - imageName = "Chat/Context Menu/Translate" - case .language: - imageName = "" - // - case .sendWithoutSound: - imageName = "Chat/Input/Menu/SilentIcon" - case .sendWhenOnline: - imageName = "Chat/Input/Menu/WhenOnlineIcon" - case .schedule: - imageName = "Chat/Input/Menu/ScheduleIcon" - } - return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor) - } -} - -private final class ActionSheetItemNode: ASDisplayNode { - private let title: String - private let icon: ChatSendMessageActionIcon - let action: () -> Void - - private let separatorNode: ASDisplayNode - private let backgroundNode: ASDisplayNode - private let highlightedBackgroundNode: ASDisplayNode - private let buttonNode: HighlightTrackingButtonNode - private let iconNode: ASImageNode - private let titleNode: ImmediateTextNode - - private var maxWidth: CGFloat? - - init(theme: PresentationTheme, title: String, icon: ChatSendMessageActionIcon, hasSeparator: Bool, action: @escaping () -> Void) { - self.title = title - self.icon = icon - self.action = action - - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = theme.contextMenu.itemSeparatorColor - - self.backgroundNode = ASDisplayNode() - self.backgroundNode.isAccessibilityElement = false - self.backgroundNode.backgroundColor = theme.contextMenu.itemBackgroundColor - - self.highlightedBackgroundNode = ASDisplayNode() - self.highlightedBackgroundNode.isAccessibilityElement = false - self.highlightedBackgroundNode.backgroundColor = theme.contextMenu.itemHighlightedBackgroundColor - self.highlightedBackgroundNode.alpha = 0.0 - - self.buttonNode = HighlightTrackingButtonNode() - self.buttonNode.isAccessibilityElement = true - self.buttonNode.accessibilityLabel = title - - self.titleNode = ImmediateTextNode() - self.titleNode.isAccessibilityElement = false - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: theme.contextMenu.primaryColor) - self.titleNode.isUserInteractionEnabled = false - self.titleNode.displaysAsynchronously = false - - self.iconNode = ASImageNode() - self.iconNode.image = icon.image(theme: theme) - self.iconNode.contentMode = .center - self.iconNode.isAccessibilityElement = false - self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true - self.iconNode.isUserInteractionEnabled = false - - super.init() - - self.addSubnode(self.highlightedBackgroundNode) - self.addSubnode(self.titleNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.buttonNode) - if hasSeparator { - self.addSubnode(self.separatorNode) - } - - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - self.buttonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - strongSelf.setHighlighted(highlighted, animated: true) - } - } - } - - func setHighlighted(_ highlighted: Bool, animated: Bool) { - if highlighted == (self.highlightedBackgroundNode.alpha == 1.0) { - return - } - - if highlighted { - self.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity") - self.highlightedBackgroundNode.alpha = 1.0 - } else { - self.highlightedBackgroundNode.alpha = 0.0 - if animated { - self.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - } - } - - func updateTheme(_ theme: PresentationTheme) { - self.separatorNode.backgroundColor = theme.contextMenu.itemSeparatorColor - self.backgroundNode.backgroundColor = theme.contextMenu.itemBackgroundColor - self.highlightedBackgroundNode.backgroundColor = theme.contextMenu.itemHighlightedBackgroundColor - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.regular(17.0), textColor: theme.contextMenu.primaryColor) - self.iconNode.image = self.icon.image(theme: theme) - - if let maxWidth = self.maxWidth { - let _ = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude)) - } - } - - func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) { - self.maxWidth = maxWidth - - let titleSize = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude)) - let height: CGFloat = 44.0 - - return (titleSize.width + leftInset + rightInset, height, { width in - self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - - if let image = self.iconNode.image { - self.iconNode.frame = CGRect(origin: CGPoint(x: width - image.size.width - 12.0, y: floor((height - image.size.height) / 2.0)), size: image.size) - } - - self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: height - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)) - self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) - self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) - }) - } - - @objc private func buttonPressed() { - self.buttonNode.isUserInteractionEnabled = false - self.action() - } -} - -final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, ASScrollViewDelegate { - private let context: AccountContext - private var presentationData: PresentationData - private let sourceSendButton: ASDisplayNode - private let textFieldFrame: CGRect - private let textInputView: UITextView - private let attachment: Bool - private let forwardedCount: Int? - private let hasEntityKeyboard: Bool - - private let send: (() -> Void)? - private let cancel: (() -> Void)? - - private let effectView: UIVisualEffectView - private let dimNode: ASDisplayNode - - private let contentContainerNode: ASDisplayNode - private let contentNodes: [ActionSheetItemNode] - private let sendButtonNode: HighlightableButtonNode - - private let messageClipNode: ASDisplayNode - private let messageBackgroundNode: ASImageNode - private let fromMessageTextScrollView: UIScrollView - private let fromMessageTextNode: ChatInputTextNode - private let toMessageTextScrollView: UIScrollView - private let toMessageTextNode: ChatInputTextNode - private let scrollNode: ASScrollNode - - private var fromCustomEmojiContainerView: CustomEmojiContainerView? - private var toCustomEmojiContainerView: CustomEmojiContainerView? - - private var validLayout: ContainerViewLayout? - - private var sendButtonFrame: CGRect { - return self.sourceSendButton.view.convert(self.sourceSendButton.bounds, to: nil) - } - - private var animateInputField = false - - private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? - - // MARK: Nicegram TranslateEnteredMessage, change (canTranslate + interlocutorLangCode + translate + chooseLanguage) - init(context: AccountContext, presentationData: PresentationData, reminders: Bool, gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputView: UITextView, attachment: Bool, canSendWhenOnline: Bool, forwardedCount: Int?, hasEntityKeyboard: Bool, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, send: (() -> Void)?, sendSilently: (() -> Void)?, sendWhenOnline: (() -> Void)?, schedule: (() -> Void)?, canTranslate: Bool, interlocutorLangCode: String?, translate: (() -> Void)?, chooseLanguage: (() -> ())?, cancel: (() -> Void)?) { - self.context = context - self.presentationData = presentationData - self.sourceSendButton = sourceSendButton - self.textFieldFrame = textInputView.convert(textInputView.bounds, to: nil) - self.textInputView = textInputView - self.attachment = attachment - self.forwardedCount = forwardedCount - self.hasEntityKeyboard = hasEntityKeyboard - self.emojiViewProvider = emojiViewProvider - - self.send = send - self.cancel = cancel - - self.effectView = UIVisualEffectView() - - self.dimNode = ASDisplayNode() - self.dimNode.alpha = 1.0 - self.dimNode.backgroundColor = self.presentationData.theme.contextMenu.dimColor - - self.sendButtonNode = HighlightableButtonNode() - self.sendButtonNode.imageNode.displayWithoutProcessing = false - self.sendButtonNode.imageNode.displaysAsynchronously = false - self.sendButtonNode.accessibilityLabel = self.presentationData.strings.MediaPicker_Send - - self.messageClipNode = ASDisplayNode() - self.messageClipNode.clipsToBounds = true - self.messageClipNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) - self.messageBackgroundNode = ASImageNode() - self.messageBackgroundNode.isUserInteractionEnabled = true - self.fromMessageTextNode = ChatInputTextNode(disableTiling: true) - self.fromMessageTextNode.textView.isScrollEnabled = false - self.fromMessageTextNode.isUserInteractionEnabled = false - self.fromMessageTextScrollView = UIScrollView() - self.fromMessageTextScrollView.isUserInteractionEnabled = false - self.toMessageTextNode = ChatInputTextNode(disableTiling: true) - self.toMessageTextNode.textView.isScrollEnabled = false - self.toMessageTextNode.isUserInteractionEnabled = false - self.toMessageTextScrollView = UIScrollView() - self.toMessageTextScrollView.alpha = 0.0 - self.toMessageTextScrollView.isUserInteractionEnabled = false - - self.scrollNode = ASScrollNode() - self.scrollNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) - - self.contentContainerNode = ASDisplayNode() - self.contentContainerNode.backgroundColor = self.presentationData.theme.contextMenu.backgroundColor - self.contentContainerNode.cornerRadius = 14.0 - self.contentContainerNode.clipsToBounds = true - - var contentNodes: [ActionSheetItemNode] = [] - if !reminders { - contentNodes.append(ActionSheetItemNode(theme: self.presentationData.theme, title: self.presentationData.strings.Conversation_SendMessage_SendSilently, icon: .sendWithoutSound, hasSeparator: true, action: { - sendSilently?() - })) - if canSendWhenOnline && schedule != nil { - contentNodes.append(ActionSheetItemNode(theme: self.presentationData.theme, title: self.presentationData.strings.Conversation_SendMessage_SendWhenOnline, icon: .sendWhenOnline, hasSeparator: true, action: { - sendWhenOnline?() - })) - } - } - if let _ = schedule { - // MARK: Nicegram TranslateEnteredMessage, change (hasSeparator: canTranslate) - contentNodes.append(ActionSheetItemNode(theme: self.presentationData.theme, title: reminders ? self.presentationData.strings.Conversation_SendMessage_SetReminder: self.presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: .schedule, hasSeparator: canTranslate, action: { - schedule?() - })) - } - // MARK: Nicegram TranslateEnteredMessage - if canTranslate { - contentNodes.append(ActionSheetItemNode(theme: self.presentationData.theme, title: self.presentationData.strings.Conversation_ContextMenuTranslate, icon: .translate, hasSeparator: false, action: { - translate?() - })) - let title: String - if let interlocutorLangCode = interlocutorLangCode { - title = l("Messages.ToLanguage.WithCode", with: interlocutorLangCode.uppercased()) - } else { - title = l("Messages.ToLanguage") - } - contentNodes.append(ActionSheetItemNode(theme: self.presentationData.theme, title: title, icon: .language, hasSeparator: false, action: { - chooseLanguage?() - })) - } - // - self.contentNodes = contentNodes - - super.init() - - self.sendButtonNode.addTarget(self, action: #selector(self.sendButtonPressed), forControlEvents: .touchUpInside) - - if let attributedText = textInputView.attributedText, !attributedText.string.isEmpty { - self.animateInputField = true - if let textInputView = self.textInputView as? ChatInputTextView { - if let textTheme = textInputView.theme { - self.fromMessageTextNode.textView.theme = textTheme - - let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor - let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle - switch textTheme.quote.lineStyle { - case .solid: - mappedLineStyle = .solid(color: mainColor) - case .doubleDashed: - mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear) - case .tripleDashed: - mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear) - } - - self.toMessageTextNode.textView.theme = ChatInputTextView.Theme( - quote: ChatInputTextView.Theme.Quote( - background: mainColor.withMultipliedAlpha(0.1), - foreground: mainColor, - lineStyle: mappedLineStyle, - codeBackground: mainColor.withMultipliedAlpha(0.1), - codeForeground: mainColor - ) - ) - } - } - self.fromMessageTextNode.attributedText = attributedText - - if let toAttributedText = self.fromMessageTextNode.attributedText?.mutableCopy() as? NSMutableAttributedString { - toAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: self.presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (toAttributedText.string as NSString).length)) - self.toMessageTextNode.attributedText = toAttributedText - } - } else { - if let _ = forwardedCount { - self.animateInputField = true - } - self.fromMessageTextNode.attributedText = NSAttributedString(string: self.attachment ? self.presentationData.strings.MediaPicker_AddCaption : self.presentationData.strings.Conversation_InputTextPlaceholder, attributes: [NSAttributedString.Key.foregroundColor: self.presentationData.theme.chat.inputPanel.inputPlaceholderColor, NSAttributedString.Key.font: Font.regular(self.presentationData.chatFontSize.baseDisplaySize)]) - - self.toMessageTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.ForwardedMessages(Int32(forwardedCount ?? 0)), attributes: [NSAttributedString.Key.foregroundColor: self.presentationData.theme.chat.message.outgoing.primaryTextColor, NSAttributedString.Key.font: Font.regular(self.presentationData.chatFontSize.baseDisplaySize)]) - } - self.messageBackgroundNode.contentMode = .scaleToFill - - let outgoing: PresentationThemeBubbleColorComponents = self.presentationData.chatWallpaper.isEmpty ? self.presentationData.theme.chat.message.outgoing.bubble.withoutWallpaper : self.presentationData.theme.chat.message.outgoing.bubble.withWallpaper - - let maxCornerRadius = self.presentationData.chatBubbleCorners.mainRadius - self.messageBackgroundNode.image = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: maxCornerRadius, incoming: false, fillColor: outgoing.fill.last ?? outgoing.fill[0], strokeColor: outgoing.fill.count > 1 ? outgoing.stroke : .clear, neighbors: .none, theme: self.presentationData.theme.chat, wallpaper: self.presentationData.chatWallpaper, knockout: false) - - self.view.addSubview(self.effectView) - self.addSubnode(self.dimNode) - - self.addSubnode(self.contentContainerNode) - self.addSubnode(self.scrollNode) - - self.addSubnode(self.sendButtonNode) - self.scrollNode.addSubnode(self.messageClipNode) - self.messageClipNode.addSubnode(self.messageBackgroundNode) - self.messageClipNode.view.addSubview(self.fromMessageTextScrollView) - self.fromMessageTextScrollView.addSubview(self.fromMessageTextNode.view) - self.messageClipNode.view.addSubview(self.toMessageTextScrollView) - self.toMessageTextScrollView.addSubview(self.toMessageTextNode.view) - - self.contentNodes.forEach(self.contentContainerNode.addSubnode) - - gesture.externalUpdated = { [weak self] view, location in - guard let strongSelf = self else { - return - } - for contentNode in strongSelf.contentNodes { - let localPoint = contentNode.view.convert(location, from: view) - if contentNode.bounds.contains(localPoint) { - contentNode.setHighlighted(true, animated: false) - } else { - contentNode.setHighlighted(false, animated: false) - } - } - } - - gesture.externalEnded = { [weak self] viewAndLocation in - guard let strongSelf = self else { - return - } - for contentNode in strongSelf.contentNodes { - if let (view, location) = viewAndLocation { - let localPoint = contentNode.view.convert(location, from: view) - if contentNode.bounds.contains(localPoint) { - contentNode.action() - } else { - contentNode.setHighlighted(false, animated: false) - } - } else { - contentNode.setHighlighted(false, animated: false) - } - } - } - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - let result = super.hitTest(point, with: event) - if result != self.scrollNode.view { - return result - } else { - return self.dimNode.view - } - } - - override func didLoad() { - super.didLoad() - - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - - self.scrollNode.view.showsVerticalScrollIndicator = false - self.scrollNode.view.delaysContentTouches = false - self.scrollNode.view.delegate = self.wrappedScrollViewDelegate - self.scrollNode.view.alwaysBounceVertical = true - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.scrollNode.view.contentInsetAdjustmentBehavior = .never - } - - self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) - - if let snapshotView = self.sourceSendButton.view.snapshotView(afterScreenUpdates: false) { - self.sendButtonNode.view.addSubview(snapshotView) - } - } - - func updateTextContents() { - var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = [] - - let textInputNode = self.fromMessageTextNode - if let attributedText = textInputNode.attributedText { - let beginning = textInputNode.textView.beginningOfDocument - attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in - if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { - if let start = textInputNode.textView.position(from: beginning, offset: range.location), let end = textInputNode.textView.position(from: start, offset: range.length), let textRange = textInputNode.textView.textRange(from: start, to: end) { - let textRects = textInputNode.textView.selectionRects(for: textRange) - for textRect in textRects { - customEmojiRects.append((textRect.rect, value)) - break - } - } - } - }) - } - - self.updateTextContents(rects: customEmojiRects, textInputNode: self.fromMessageTextNode, from: true) - self.updateTextContents(rects: customEmojiRects, textInputNode: self.toMessageTextNode, from: false) - } - - func updateTextContents(rects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)], textInputNode: ChatInputTextNode, from: Bool) { - if !rects.isEmpty { - let customEmojiContainerView: CustomEmojiContainerView - if from, let current = self.fromCustomEmojiContainerView { - customEmojiContainerView = current - } else if !from, let current = self.toCustomEmojiContainerView { - customEmojiContainerView = current - } else { - customEmojiContainerView = CustomEmojiContainerView(emojiViewProvider: { [weak self] emoji in - guard let strongSelf = self, let emojiViewProvider = strongSelf.emojiViewProvider else { - return nil - } - return emojiViewProvider(emoji) - }) - customEmojiContainerView.isUserInteractionEnabled = false - textInputNode.textView.addSubview(customEmojiContainerView) - if from { - self.fromCustomEmojiContainerView = customEmojiContainerView - } else { - self.toCustomEmojiContainerView = customEmojiContainerView - } - } - - customEmojiContainerView.update(emojiRects: rects) - } else { - if from, let customEmojiContainerView = self.fromCustomEmojiContainerView { - customEmojiContainerView.removeFromSuperview() - self.fromCustomEmojiContainerView = nil - } else if !from, let customEmojiContainerView = self.toCustomEmojiContainerView { - customEmojiContainerView.removeFromSuperview() - self.fromCustomEmojiContainerView = nil - } - } - } - - func updatePresentationData(_ presentationData: PresentationData) { - guard presentationData.theme !== self.presentationData.theme else { - return - } - self.presentationData = presentationData - - self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) - - self.dimNode.backgroundColor = presentationData.theme.contextMenu.dimColor - - self.contentContainerNode.backgroundColor = self.presentationData.theme.contextMenu.backgroundColor - - if let toAttributedText = self.textInputView.attributedText?.mutableCopy() as? NSMutableAttributedString { - toAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: self.presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (toAttributedText.string as NSString).length)) - self.toMessageTextNode.attributedText = toAttributedText - } - - let outgoing: PresentationThemeBubbleColorComponents = self.presentationData.chatWallpaper.isEmpty ? self.presentationData.theme.chat.message.outgoing.bubble.withoutWallpaper : self.presentationData.theme.chat.message.outgoing.bubble.withWallpaper - let maxCornerRadius = self.presentationData.chatBubbleCorners.mainRadius - self.messageBackgroundNode.image = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: maxCornerRadius, incoming: false, fillColor: outgoing.fill.last ?? outgoing.fill[0], strokeColor: outgoing.fill.count > 1 ? outgoing.stroke : .clear, neighbors: .none, theme: self.presentationData.theme.chat, wallpaper: self.presentationData.chatWallpaper, knockout: false) - - for node in self.contentNodes { - node.updateTheme(presentationData.theme) - } - } - - func animateIn() { - guard let layout = self.validLayout else { - return - } - - self.textInputView.setContentOffset(self.textInputView.contentOffset, animated: false) - - self.effectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.messageBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - - self.sourceSendButton.isHidden = true - if self.animateInputField { - self.fromMessageTextScrollView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) - self.toMessageTextScrollView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: false) - } else { - self.messageBackgroundNode.isHidden = true - self.fromMessageTextScrollView.isHidden = true - self.toMessageTextScrollView.isHidden = true - } - - let duration = 0.4 - self.sendButtonNode.layer.animateScale(from: 0.75, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue) - self.sendButtonNode.layer.animatePosition(from: self.sendButtonFrame.center, to: self.sendButtonNode.position, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) - - var initialWidth = self.textFieldFrame.width + 32.0 - if self.textInputView.attributedText.string.isEmpty { - initialWidth = ceil(layout.size.width - self.textFieldFrame.origin.x - self.sendButtonFrame.width - layout.safeInsets.left - layout.safeInsets.right + 21.0) - } - - let fromFrame = CGRect(origin: CGPoint(), size: CGSize(width: initialWidth, height: self.textFieldFrame.height + 2.0)) - let delta = (fromFrame.height - self.messageClipNode.bounds.height) / 2.0 - - var inputHeight = layout.inputHeight ?? 0.0 - if self.hasEntityKeyboard { - inputHeight = layout.standardInputHeight - } - - var clipDelta = delta - if inputHeight < 70.0 || layout.isNonExclusive { - clipDelta -= self.contentContainerNode.frame.height + 16.0 - } - - self.messageClipNode.layer.animateBounds(from: fromFrame, to: self.messageClipNode.bounds, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) - self.messageClipNode.layer.animatePosition(from: CGPoint(x: (self.messageClipNode.bounds.width - initialWidth) / 2.0, y: clipDelta), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true, completion: { [weak self] _ in - if let strongSelf = self { - strongSelf.insertSubnode(strongSelf.contentContainerNode, aboveSubnode: strongSelf.scrollNode) - } - }) - - self.messageBackgroundNode.layer.animateBounds(from: fromFrame, to: self.messageBackgroundNode.bounds, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) - self.messageBackgroundNode.layer.animatePosition(from: CGPoint(x: (initialWidth - self.messageClipNode.bounds.width) / 2.0, y: delta), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - - var textXOffset: CGFloat = 0.0 - let textYOffset = self.textInputView.contentSize.height - self.textInputView.contentOffset.y - self.textInputView.frame.height - if self.textInputView.numberOfLines == 1 && self.textInputView.isRTL { - textXOffset = initialWidth - self.messageClipNode.bounds.width - } - self.fromMessageTextScrollView.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.toMessageTextScrollView.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - - let contentOffset = CGPoint(x: self.sendButtonFrame.midX - self.contentContainerNode.frame.midX, y: self.sendButtonFrame.midY - self.contentContainerNode.frame.midY) - - let springDuration: Double = 0.42 - let springDamping: CGFloat = 104.0 - self.contentContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping) - self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true) - - Queue.mainQueue().after(0.01, { - if self.animateInputField { - self.textInputView.isHidden = true - } - self.updateTextContents() - }) - } - - func animateOut(cancel: Bool, completion: @escaping () -> Void) { - guard let layout = self.validLayout else { - return - } - - self.isUserInteractionEnabled = false - - self.scrollNode.view.setContentOffset(self.scrollNode.view.contentOffset, animated: false) - - var completedEffect = false - var completedButton = false - var completedBubble = false - var completedAlpha = false - - var completed = false - let intermediateCompletion: () -> Void = { [weak self] in - if completedEffect && completedButton && completedBubble && completedAlpha && !completed { - completed = true - self?.textInputView.isHidden = false - self?.sourceSendButton.isHidden = false - completion() - } - } - - self.effectView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in - completedEffect = true - intermediateCompletion() - }) - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in }) - - Queue.mainQueue().after(0.45) { - if !completed { - completed = true - self.textInputView.isHidden = false - self.sourceSendButton.isHidden = false - completion() - } - } - - if self.animateInputField { - if cancel { - self.fromMessageTextScrollView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: 0.15, removeOnCompletion: false) - self.toMessageTextScrollView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false) - self.messageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false, completion: { _ in - completedAlpha = true - intermediateCompletion() - }) - } else { - self.textInputView.isHidden = false - self.messageClipNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in - completedAlpha = true - intermediateCompletion() - }) - } - } else { - completedAlpha = true - } - - let duration = 0.4 - self.sendButtonNode.layer.animatePosition(from: self.sendButtonNode.position, to: self.sendButtonFrame.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - completedButton = true - intermediateCompletion() - }) - - if !cancel { - self.sourceSendButton.isHidden = false - self.sendButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false) - self.sendButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false) - } - - var initialWidth = self.textFieldFrame.width + 32.0 - if self.textInputView.attributedText.string.isEmpty { - initialWidth = ceil(layout.size.width - self.textFieldFrame.origin.x - self.sendButtonFrame.width - layout.safeInsets.left - layout.safeInsets.right + 21.0) - } - - let toFrame = CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: initialWidth, height: self.textFieldFrame.height + 2.0)) - let delta = (toFrame.height - self.messageClipNode.bounds.height) / 2.0 - - if cancel && self.animateInputField { - var inputHeight = layout.inputHeight ?? 0.0 - if self.hasEntityKeyboard { - inputHeight = layout.standardInputHeight - } - - var clipDelta = delta - if inputHeight < 70.0 || layout.isNonExclusive { - clipDelta -= self.contentContainerNode.frame.height + 16.0 - } - - self.messageClipNode.layer.animateBounds(from: self.messageClipNode.bounds, to: toFrame.offsetBy(dx: 0.0, dy: 1.0), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - completedBubble = true - intermediateCompletion() - }) - self.messageClipNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: (self.messageClipNode.bounds.width - initialWidth) / 2.0, y: clipDelta + self.scrollNode.view.contentOffset.y), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) - - self.messageBackgroundNode.layer.animateBounds(from: self.messageBackgroundNode.bounds, to: toFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - self.messageBackgroundNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: (initialWidth - self.messageClipNode.bounds.width) / 2.0, y: delta), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) - - var textXOffset: CGFloat = 0.0 - let textYOffset = self.textInputView.contentSize.height - self.textInputView.contentOffset.y - self.textInputView.frame.height - if self.textInputView.numberOfLines == 1 && self.textInputView.isRTL { - textXOffset = initialWidth - self.messageClipNode.bounds.width - } - self.fromMessageTextScrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) - self.toMessageTextScrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) - } else { - completedBubble = true - } - - let contentOffset = CGPoint(x: self.sendButtonFrame.midX - self.contentContainerNode.frame.midX, y: self.sendButtonFrame.midY - self.contentContainerNode.frame.midY) - - self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentOffset, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) - self.contentContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if let layout = self.validLayout { - self.containerLayoutUpdated(layout, transition: .immediate) - } - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.validLayout = layout - - transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: layout.size)) - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - let sideInset: CGFloat = self.sendButtonFrame.width - 1.0 - - var contentSize = CGSize() - contentSize.width = min(layout.size.width - 40.0, 250.0) - var applyNodes: [(ASDisplayNode, CGFloat, (CGFloat) -> Void)] = [] - for itemNode in self.contentNodes { - let (width, height, apply) = itemNode.updateLayout(maxWidth: layout.size.width - 16.0 * 2.0) - applyNodes.append((itemNode, height, apply)) - contentSize.width = max(contentSize.width, width) - contentSize.height += height - } - - let menuHeightWithInset = contentSize.height + 16.0 - - var insets = layout.insets(options: [.statusBar, .input]) - var inputHeight = layout.inputHeight ?? 0.0 - if self.hasEntityKeyboard { - insets.bottom = max(insets.bottom, layout.standardInputHeight) - inputHeight = layout.standardInputHeight - } - - let contentOffset = self.scrollNode.view.contentOffset.y - let initialSendButtonFrame = self.sendButtonFrame - - var contentOrigin: CGPoint - if initialSendButtonFrame.width > initialSendButtonFrame.height * 1.2 { - contentOrigin = CGPoint(x: layout.size.width - contentSize.width - layout.safeInsets.right - 5.0, y: initialSendButtonFrame.minY - contentSize.height) - } else { - contentOrigin = CGPoint(x: layout.size.width - sideInset - contentSize.width - layout.safeInsets.right, y: layout.size.height - 6.0 - insets.bottom - contentSize.height) - } - if inputHeight > 70.0 && !layout.isNonExclusive && self.animateInputField { - contentOrigin.y += menuHeightWithInset - } - contentOrigin.y = min(contentOrigin.y + contentOffset, layout.size.height - 6.0 - layout.intrinsicInsets.bottom - contentSize.height) - - transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: contentOrigin, size: contentSize)) - var nextY: CGFloat = 0.0 - for (itemNode, height, apply) in applyNodes { - transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: nextY), size: CGSize(width: contentSize.width, height: height))) - apply(contentSize.width) - nextY += height - } - - var sendButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - initialSendButtonFrame.width + 1.0 - UIScreenPixel - layout.safeInsets.right, y: layout.size.height - insets.bottom - initialSendButtonFrame.height), size: initialSendButtonFrame.size) - if (inputHeight < 70.0 || layout.isNonExclusive) && self.animateInputField { - sendButtonFrame.origin.y -= menuHeightWithInset - } - sendButtonFrame.origin.y = min(sendButtonFrame.origin.y + contentOffset, layout.size.height - layout.intrinsicInsets.bottom - initialSendButtonFrame.height) - transition.updateFrameAsPositionAndBounds(node: self.sendButtonNode, frame: sendButtonFrame) - - var messageFrame = self.textFieldFrame - messageFrame.size.width += 32.0 - messageFrame.origin.x -= 13.0 - messageFrame.origin.y = layout.size.height - messageFrame.origin.y - messageFrame.size.height - 1.0 - - let messageHeightAddition: CGFloat = max(0.0, 35.0 - messageFrame.size.height) - - if inputHeight < 70.0 || layout.isNonExclusive { - messageFrame.origin.y += menuHeightWithInset - } - - if self.textInputView.attributedText.string.isEmpty { - messageFrame.size.width = ceil(layout.size.width - messageFrame.origin.x - sendButtonFrame.width - layout.safeInsets.left - layout.safeInsets.right + 8.0) - } - - var messageOriginDelta: CGFloat = 0.0 - if self.textInputView.numberOfLines == 1 || self.textInputView.attributedText.string.isEmpty { - let textWidth = min(self.toMessageTextNode.textView.sizeThatFits(layout.size).width + 36.0, messageFrame.width) - messageOriginDelta = messageFrame.width - textWidth - messageFrame.origin.x += messageOriginDelta - messageFrame.size.width = textWidth - } - - let messageHeight = max(messageFrame.size.height, self.textInputView.contentSize.height + 2.0) - messageFrame.size.height = messageHeight - - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - var scrollContentSize = CGSize(width: layout.size.width, height: messageHeight + max(0.0, messageFrame.origin.y)) - if messageHeight > layout.size.height - messageFrame.origin.y { - scrollContentSize.height += insets.top + 16.0 - } - self.scrollNode.view.contentSize = scrollContentSize - - let clipFrame = messageFrame - transition.updateFrame(node: self.messageClipNode, frame: clipFrame) - - var backgroundFrame = CGRect(origin: CGPoint(), size: messageFrame.size) - backgroundFrame.origin.y -= messageHeightAddition * 0.5 - backgroundFrame.size.height += messageHeightAddition - transition.updateFrame(node: self.messageBackgroundNode, frame: backgroundFrame) - - var textFrame = self.textFieldFrame - textFrame.origin = CGPoint(x: 13.0, y: 6.0 - UIScreenPixel) - textFrame.size.height = self.textInputView.contentSize.height - - if let textInputView = self.textInputView as? ChatInputTextView { - textFrame.origin.y -= 5.0 - - self.fromMessageTextNode.textView.defaultTextContainerInset = textInputView.defaultTextContainerInset - self.toMessageTextNode.textView.defaultTextContainerInset = textInputView.defaultTextContainerInset - } - /*if let textInputView = self.textInputView as? ChatInputTextView { - textFrame.size.width -= textInputView.defaultTextContainerInset.right - } else { - textFrame.size.width -= self.textInputView.textContainerInset.right - }*/ - - if self.textInputView.isRTL { - textFrame.origin.x -= messageOriginDelta - } - - self.fromMessageTextScrollView.frame = textFrame - self.fromMessageTextNode.frame = CGRect(origin: CGPoint(), size: textFrame.size) - self.fromMessageTextNode.updateLayout(size: textFrame.size) - - self.toMessageTextScrollView.frame = textFrame - self.toMessageTextNode.frame = CGRect(origin: CGPoint(), size: textFrame.size) - self.toMessageTextNode.updateLayout(size: textFrame.size) - } - - @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.cancel?() - } - } - - @objc private func sendButtonPressed() { - self.sendButtonNode.isUserInteractionEnabled = false - self.send?() - } -} - -final class CustomEmojiContainerView: UIView { - private let emojiViewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView? - - private var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:] - - init(emojiViewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView?) { - self.emojiViewProvider = emojiViewProvider - - super.init(frame: CGRect()) - } - - required init(coder: NSCoder) { - preconditionFailure() - } - - func update(emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)]) { - var nextIndexById: [Int64: Int] = [:] - - var validKeys = Set() - for (rect, emoji) in emojiRects { - let index: Int - if let nextIndex = nextIndexById[emoji.fileId] { - index = nextIndex - } else { - index = 0 - } - nextIndexById[emoji.fileId] = index + 1 - - let key = InlineStickerItemLayer.Key(id: emoji.fileId, index: index) - - let view: UIView - if let current = self.emojiLayers[key] { - view = current - } else if let newView = self.emojiViewProvider(emoji) { - view = newView - self.addSubview(newView) - self.emojiLayers[key] = view - } else { - continue - } - - let size = CGSize(width: 24.0, height: 24.0) - - view.frame = CGRect(origin: CGPoint(x: floor(rect.midX - size.width / 2.0), y: floor(rect.midY - size.height / 2.0)), size: size) - - validKeys.insert(key) - } - - var removeKeys: [InlineStickerItemLayer.Key] = [] - for (key, view) in self.emojiLayers { - if !validKeys.contains(key) { - removeKeys.append(key) - view.removeFromSuperview() - } - } - for key in removeKeys { - self.emojiLayers.removeValue(forKey: key) - } - } -} diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift new file mode 100644 index 00000000000..95abfb1f301 --- /dev/null +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -0,0 +1,1531 @@ +// MARK: Nicegram TranslateEnteredMessage +import NGStrings +import NGTranslate +// +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import AccountContext +import ContextUI +import TelegramCore +import Postbox +import TextFormat +import ReactionSelectionNode +import ViewControllerComponent +import ComponentFlow +import ComponentDisplayAdapters +import WallpaperBackgroundNode +import ReactionSelectionNode +import EntityKeyboard +import LottieMetal +import TelegramAnimatedStickerNode +import AnimatedStickerNode +import ChatInputTextNode +import UndoUI + +func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> CGRect { + let sourceWindowFrame = fromView.convert(frame, to: nil) + var targetWindowFrame = toView.convert(sourceWindowFrame, from: nil) + + if let fromWindow = fromView.window, let toWindow = toView.window { + targetWindowFrame.origin.x += toWindow.bounds.width - fromWindow.bounds.width + } + return targetWindowFrame +} + +public enum ChatSendMessageContextScreenMediaPreviewLayoutType { + case message + case media + case videoMessage +} + +public protocol ChatSendMessageContextScreenMediaPreview: AnyObject { + var isReady: Signal { get } + var view: UIView { get } + var globalClippingRect: CGRect? { get } + var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { get } + + func animateIn(transition: Transition) + func animateOut(transition: Transition) + func animateOutOnSend(transition: Transition) + func update(containerSize: CGSize, transition: Transition) -> CGSize +} + +// MARK: Nicegram TranslateEnteredMessage +public struct ChatSendMessageContextNicegramData { + let canTranslate: Bool + let translate: () -> Void + let chooseLanguage: () -> Void + + public init(canTranslate: Bool, translate: @escaping () -> Void, chooseLanguage: @escaping () -> Void) { + self.canTranslate = canTranslate + self.translate = translate + self.chooseLanguage = chooseLanguage + } + + public static var empty: ChatSendMessageContextNicegramData { + ChatSendMessageContextNicegramData( + canTranslate: false, + translate: {}, + chooseLanguage: {} + ) + } +} +// + +final class ChatSendMessageContextScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + // MARK: Nicegram TranslateEnteredMessage + let nicegramData: ChatSendMessageContextNicegramData + let interlocutorLangCode: String? + // + + let context: AccountContext + let updatedPresentationData: (initial: PresentationData, signal: Signal)? + let peerId: EnginePeer.Id? + let params: SendMessageActionSheetControllerParams + let hasEntityKeyboard: Bool + let gesture: ContextGesture + let sourceSendButton: ASDisplayNode + let textInputView: UITextView + let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? + let wallpaperBackgroundNode: WallpaperBackgroundNode? + let completion: () -> Void + let sendMessage: (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void + let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void + let openPremiumPaywall: (ViewController) -> Void + let reactionItems: [ReactionItem]? + let availableMessageEffects: AvailableMessageEffects? + let isPremium: Bool + + init( + // MARK: Nicegram TranslateEnteredMessage + nicegramData: ChatSendMessageContextNicegramData, + // + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)?, + peerId: EnginePeer.Id?, + params: SendMessageActionSheetControllerParams, + hasEntityKeyboard: Bool, + gesture: ContextGesture, + sourceSendButton: ASDisplayNode, + textInputView: UITextView, + emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, + wallpaperBackgroundNode: WallpaperBackgroundNode?, + completion: @escaping () -> Void, + sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, + schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, + openPremiumPaywall: @escaping (ViewController) -> Void, + reactionItems: [ReactionItem]?, + availableMessageEffects: AvailableMessageEffects?, + isPremium: Bool + ) { + // MARK: Nicegram TranslateEnteredMessage + self.nicegramData = nicegramData + self.interlocutorLangCode = getCachedLanguageCode(forChatWith: peerId) + // + self.context = context + self.updatedPresentationData = updatedPresentationData + self.peerId = peerId + self.params = params + self.hasEntityKeyboard = hasEntityKeyboard + self.gesture = gesture + self.sourceSendButton = sourceSendButton + self.textInputView = textInputView + self.emojiViewProvider = emojiViewProvider + self.wallpaperBackgroundNode = wallpaperBackgroundNode + self.completion = completion + self.sendMessage = sendMessage + self.schedule = schedule + self.openPremiumPaywall = openPremiumPaywall + self.reactionItems = reactionItems + self.availableMessageEffects = availableMessageEffects + self.isPremium = isPremium + } + + static func ==(lhs: ChatSendMessageContextScreenComponent, rhs: ChatSendMessageContextScreenComponent) -> Bool { + return true + } + + enum PresentationAnimationState { + enum Key { + case initial + case animatedIn + case animatedOut + } + + case initial + case animatedIn + case animatedOut(completion: () -> Void) + + var key: Key { + switch self { + case .initial: + return .initial + case .animatedIn: + return .animatedIn + case .animatedOut: + return .animatedOut + } + } + } + + final class View: UIView { + private let backgroundView: BlurredBackgroundView + + private var sendButton: SendButton? + private var messageItemView: MessageItemView? + private var internalWallpaperBackgroundNode: WallpaperBackgroundNode? + private var actionsStackNode: ContextControllerActionsStackNode? + private var reactionContextNode: ReactionContextNode? + + private let scrollView: UIScrollView + + private var component: ChatSendMessageContextScreenComponent? + private var environment: EnvironmentType? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + + private var mediaCaptionIsAbove: Bool = false + + private let messageEffectDisposable = MetaDisposable() + private var selectedMessageEffect: AvailableMessageEffects.MessageEffect? + private var standaloneReactionAnimation: AnimatedStickerNode? + + private var isLoadingEffectAnimation: Bool = false + private var isLoadingEffectAnimationTimerDisposable: Disposable? + private var loadEffectAnimationDisposable: Disposable? + + private var animateInTimestamp: Double? + private var performedActionsOnAnimateOut: Bool = false + private var presentationAnimationState: PresentationAnimationState = .initial + private var appliedAnimationState: PresentationAnimationState = .initial + private var animateOutToEmpty: Bool = false + + private var initializationDisplayLink: SharedDisplayLinkDriver.Link? + private var updateSourcePositionsDisplayLink: SharedDisplayLinkDriver.Link? + + private var stableSourceSendButtonFrame: CGRect? + + override init(frame: CGRect) { + self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) + + self.scrollView = UIScrollView() + self.scrollView.showsVerticalScrollIndicator = true + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.scrollsToTop = false + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + self.scrollView.alwaysBounceVertical = true + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + + self.addSubview(self.scrollView) + + self.backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onBackgroundTap(_:)))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.messageEffectDisposable.dispose() + self.loadEffectAnimationDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable?.dispose() + } + + @objc private func onBackgroundTap(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.environment?.controller()?.dismiss() + } + } + + @objc private func onSendButtonPressed() { + guard let component = self.component else { + return + } + self.animateOutToEmpty = true + + self.environment?.controller()?.dismiss() + + let sendParameters = ChatSendMessageActionSheetController.SendParameters( + effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }), + textIsAboveMedia: self.mediaCaptionIsAbove + ) + component.sendMessage(.generic, sendParameters) + } + + func animateIn() { + if case .initial = self.presentationAnimationState { + HapticFeedback().impact() + + self.animateInTimestamp = CFAbsoluteTimeGetCurrent() + + self.presentationAnimationState = .animatedIn + self.state?.updated(transition: .spring(duration: 0.42)) + } + } + + func animateOut(completion: @escaping () -> Void) { + if let controller = self.environment?.controller() { + controller.forEachController { c in + if let c = c as? UndoOverlayController { + c.dismiss() + } + return true + } + } + + if case .animatedOut = self.presentationAnimationState { + } else { + self.presentationAnimationState = .animatedOut(completion: completion) + self.state?.updated(transition: .spring(duration: 0.4)) + } + } + + private func requestUpdateOverlayWantsToBeBelowKeyboard(transition: ContainedViewLayoutTransition) { + guard let controller = self.environment?.controller() as? ChatSendMessageContextScreen else { + return + } + controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition) + } + + func wantsToBeBelowKeyboard() -> Bool { + if let reactionContextNode = self.reactionContextNode { + return reactionContextNode.wantsDisplayBelowKeyboard() + } + return false + } + + func update(component: ChatSendMessageContextScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + let environment = environment[EnvironmentType.self].value + + if let previousEnvironment = self.environment, previousEnvironment.inputHeight != 0.0, environment.inputHeight == 0.0 { + DispatchQueue.main.async { [weak self] in + guard let self, let component = self.component else { + return + } + let stableSourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) + if self.stableSourceSendButtonFrame != stableSourceSendButtonFrame { + self.stableSourceSendButtonFrame = stableSourceSendButtonFrame + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.35)) + } + } + } + } + + var transition = transition + + var transitionIsImmediate = transition.animation.isImmediate + if case let .curve(duration, _) = transition.animation, duration == 0.0 { + transitionIsImmediate = true + } + if transitionIsImmediate, let previousEnvironment = self.environment, previousEnvironment.inputHeight != 0.0, environment.inputHeight != 0.0, previousEnvironment.inputHeight != environment.inputHeight { + transition = .spring(duration: 0.4) + } + + let previousAnimationState = self.appliedAnimationState + self.appliedAnimationState = self.presentationAnimationState + + let messageActionsSpacing: CGFloat = 7.0 + + let alphaTransition: Transition + if transition.animation.isImmediate { + alphaTransition = .immediate + } else { + alphaTransition = .easeInOut(duration: 0.25) + } + let _ = alphaTransition + + let themeUpdated = environment.theme !== self.environment?.theme + + if self.component == nil { + switch component.params { + case let .sendMessage(sendMessage): + self.mediaCaptionIsAbove = sendMessage.mediaCaptionIsAbove?.0 ?? false + case let .editMessage(editMessage): + self.mediaCaptionIsAbove = editMessage.mediaCaptionIsAbove?.0 ?? false + } + + component.gesture.externalUpdated = { [weak self] view, location in + guard let self, let actionsStackNode = self.actionsStackNode else { + return + } + guard let animateInTimestamp = self.animateInTimestamp, animateInTimestamp < CFAbsoluteTimeGetCurrent() - 0.35 else { + return + } + + actionsStackNode.highlightGestureMoved(location: actionsStackNode.view.convert(location, from: view)) + } + component.gesture.externalEnded = { [weak self] viewAndLocation in + guard let self, let actionsStackNode = self.actionsStackNode else { + return + } + guard let animateInTimestamp = self.animateInTimestamp, animateInTimestamp < CFAbsoluteTimeGetCurrent() - 0.35 else { + actionsStackNode.highlightGestureFinished(performAction: false) + return + } + if let (view, location) = viewAndLocation { + actionsStackNode.highlightGestureMoved(location: actionsStackNode.view.convert(location, from: view)) + actionsStackNode.highlightGestureFinished(performAction: true) + } else { + actionsStackNode.highlightGestureFinished(performAction: false) + } + } + } + + self.component = component + self.environment = environment + self.state = state + + let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 }) + + if themeUpdated { + self.backgroundView.updateColor( + color: environment.theme.contextMenu.dimColor, + enableBlur: true, + forceKeepBlur: true, + transition: .immediate + ) + } + + var mediaPreview: ChatSendMessageContextScreenMediaPreview? + switch component.params { + case let .sendMessage(sendMessage): + mediaPreview = sendMessage.mediaPreview + case let .editMessage(editMessage): + mediaPreview = editMessage.mediaPreview + } + + var isMessageVisible: Bool = mediaPreview != nil + + let textString: NSAttributedString + if let attributedText = component.textInputView.attributedText { + textString = attributedText + if textString.length != 0 { + isMessageVisible = true + } + } else { + textString = NSAttributedString(string: " ", font: Font.regular(17.0), textColor: .black) + } + + let sendButton: SendButton + if let current = self.sendButton { + sendButton = current + } else { + let sendButtonKind: SendButton.Kind + switch component.params { + case .sendMessage: + sendButtonKind = .send + case .editMessage: + sendButtonKind = .edit + } + sendButton = SendButton(kind: sendButtonKind) + sendButton.accessibilityLabel = environment.strings.MediaPicker_Send + sendButton.addTarget(self, action: #selector(self.onSendButtonPressed), for: .touchUpInside) + /*if let snapshotView = component.sourceSendButton.view.snapshotView(afterScreenUpdates: false) { + snapshotView.isUserInteractionEnabled = false + sendButton.addSubview(snapshotView) + }*/ + self.sendButton = sendButton + self.addSubview(sendButton) + } + + let sourceSendButtonFrame: CGRect + switch self.presentationAnimationState { + case .animatedOut: + sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) + self.stableSourceSendButtonFrame = sourceSendButtonFrame + default: + if let stableSourceSendButtonFrame = self.stableSourceSendButtonFrame { + sourceSendButtonFrame = stableSourceSendButtonFrame + } else { + sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) + self.stableSourceSendButtonFrame = sourceSendButtonFrame + } + } + + let sendButtonScale: CGFloat + switch self.presentationAnimationState { + case .initial: + sendButtonScale = 0.75 + default: + sendButtonScale = 1.0 + } + + var reminders = false + var isSecret = false + var canSchedule = false + switch component.params { + case let .sendMessage(sendMessage): + if let peerId = component.peerId { + reminders = peerId == component.context.account.peerId + isSecret = peerId.namespace == Namespaces.Peer.SecretChat + canSchedule = !isSecret + } + if sendMessage.isScheduledMessages { + canSchedule = false + } + case .editMessage: + break + } + + var items: [ContextMenuItem] = [] + + let canAdjustMediaCaptionPosition: Bool + switch component.params { + case let .sendMessage(sendMessage): + if case .media = mediaPreview?.layoutType { + canAdjustMediaCaptionPosition = sendMessage.mediaCaptionIsAbove != nil + } else { + canAdjustMediaCaptionPosition = false + } + case .editMessage: + if case .media = mediaPreview?.layoutType { + canAdjustMediaCaptionPosition = textString.length != 0 + } else { + canAdjustMediaCaptionPosition = false + } + } + + if canAdjustMediaCaptionPosition, textString.length != 0 { + let mediaCaptionIsAbove = self.mediaCaptionIsAbove + items.append(.action(ContextMenuActionItem( + id: AnyHashable("captionPosition"), + text: mediaCaptionIsAbove ? presentationData.strings.Chat_SendMessageMenu_MoveCaptionDown : presentationData.strings.Chat_SendMessageMenu_MoveCaptionUp, + icon: { _ in + return nil + }, iconAnimation: ContextMenuActionItem.IconAnimation( + name: !mediaCaptionIsAbove ? "message_preview_sort_above" : "message_preview_sort_below" + ), action: { [weak self] _, _ in + guard let self, let component = self.component else { + return + } + self.mediaCaptionIsAbove = !self.mediaCaptionIsAbove + switch component.params { + case let .sendMessage(sendMessage): + sendMessage.mediaCaptionIsAbove?.1(self.mediaCaptionIsAbove) + case let .editMessage(editMessage): + editMessage.mediaCaptionIsAbove?.1(self.mediaCaptionIsAbove) + } + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.35)) + } + } + ))) + + items.append(.separator) + } + switch component.params { + case let.sendMessage(sendMessage): + if !reminders { + items.append(.action(ContextMenuActionItem( + id: AnyHashable("silent"), + text: environment.strings.Conversation_SendMessage_SendSilently, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, _ in + guard let self, let component = self.component else { + return + } + self.animateOutToEmpty = true + + let sendParameters = ChatSendMessageActionSheetController.SendParameters( + effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }), + textIsAboveMedia: self.mediaCaptionIsAbove + ) + + component.sendMessage(.silently, sendParameters) + self.environment?.controller()?.dismiss() + } + ))) + + if sendMessage.canSendWhenOnline && canSchedule { + items.append(.action(ContextMenuActionItem( + id: AnyHashable("whenOnline"), + text: environment.strings.Conversation_SendMessage_SendWhenOnline, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/WhenOnlineIcon"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, _ in + guard let self, let component = self.component else { + return + } + self.animateOutToEmpty = true + + let sendParameters = ChatSendMessageActionSheetController.SendParameters( + effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }), + textIsAboveMedia: self.mediaCaptionIsAbove + ) + + component.sendMessage(.whenOnline, sendParameters) + self.environment?.controller()?.dismiss() + } + ))) + } + } + if canSchedule { + items.append(.action(ContextMenuActionItem( + id: AnyHashable("schedule"), + text: reminders ? environment.strings.Conversation_SendMessage_SetReminder: environment.strings.Conversation_SendMessage_ScheduleMessage, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, _ in + guard let self, let component = self.component else { + return + } + self.animateOutToEmpty = true + + let sendParameters = ChatSendMessageActionSheetController.SendParameters( + effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }), + textIsAboveMedia: self.mediaCaptionIsAbove + ) + + component.schedule(sendParameters) + self.environment?.controller()?.dismiss() + } + ))) + } + case .editMessage: + items.append(.action(ContextMenuActionItem( + id: AnyHashable("silent"), + text: environment.strings.Chat_SendMessageMenu_EditMessage, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, _ in + guard let self, let component = self.component else { + return + } + self.animateOutToEmpty = true + + let sendParameters = ChatSendMessageActionSheetController.SendParameters( + effect: nil, + textIsAboveMedia: self.mediaCaptionIsAbove + ) + + component.sendMessage(.generic, sendParameters) + self.environment?.controller()?.dismiss() + } + ))) + } + + // MARK: Nicegram TranslateEnteredMessage + if component.nicegramData.canTranslate { + if case .separator = items.last {} else { + items.append(.separator) + } + + items.append(.action(ContextMenuActionItem( + id: AnyHashable("translate"), + text: environment.strings.Conversation_ContextMenuTranslate, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, _ in + guard let self, let component = self.component else { + return + } + self.animateOutToEmpty = true + + component.nicegramData.translate() + self.environment?.controller()?.dismiss() + } + ))) + + let title: String + if let interlocutorLangCode = component.interlocutorLangCode { + title = l("Messages.ToLanguage.WithCode", with: interlocutorLangCode.uppercased()) + } else { + title = l("Messages.ToLanguage") + } + items.append(.action(ContextMenuActionItem( + id: AnyHashable("toLanguage"), + text: title, + icon: { _ in nil }, + action: { [weak self] _, _ in + guard let self, let component = self.component else { + return + } + self.animateOutToEmpty = true + + component.nicegramData.chooseLanguage() + self.environment?.controller()?.dismiss() + } + ))) + } + // + + if case .separator = items.last { + items.removeLast() + } + + let actionsStackNode: ContextControllerActionsStackNode + if let current = self.actionsStackNode { + actionsStackNode = current + + actionsStackNode.replace(item: ContextControllerActionsListStackItem( + id: AnyHashable("items"), + items: items, + reactionItems: nil, + tip: nil, + tipSignal: .single(nil), + dismissed: nil + ), animated: !transition.animation.isImmediate) + } else { + actionsStackNode = ContextControllerActionsStackNode( + getController: { + return nil + }, + requestDismiss: { _ in + }, + requestUpdate: { [weak self] transition in + guard let self else { + return + } + if !self.isUpdating { + self.state?.updated(transition: Transition(transition)) + } + } + ) + if isMessageVisible { + actionsStackNode.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0) + } + + actionsStackNode.push( + item: ContextControllerActionsListStackItem( + id: AnyHashable("items"), + items: items, + reactionItems: nil, + tip: nil, + tipSignal: .single(nil), + dismissed: nil + ), + currentScrollingState: nil, + positionLock: nil, + animated: false + ) + self.actionsStackNode = actionsStackNode + self.addSubview(actionsStackNode.view) + } + let actionsStackSize = actionsStackNode.update( + presentationData: presentationData, + constrainedSize: availableSize, + presentation: .modal, + transition: transition.containedViewLayoutTransition + ) + + let messageItemView: MessageItemView + if let current = self.messageItemView { + messageItemView = current + } else { + messageItemView = MessageItemView(frame: CGRect()) + self.messageItemView = messageItemView + self.addSubview(messageItemView) + } + + let wallpaperBackgroundNode: WallpaperBackgroundNode + if let externalWallpaperBackgroundNode = component.wallpaperBackgroundNode { + wallpaperBackgroundNode = externalWallpaperBackgroundNode + } else if let current = self.internalWallpaperBackgroundNode { + wallpaperBackgroundNode = current + wallpaperBackgroundNode.frame = CGRect(origin: CGPoint(), size: availableSize) + wallpaperBackgroundNode.updateLayout(size: availableSize, displayMode: .aspectFill, transition: .immediate) + } else { + wallpaperBackgroundNode = createWallpaperBackgroundNode(context: component.context, forChatDisplay: true, useSharedAnimationPhase: false) + wallpaperBackgroundNode.frame = CGRect(origin: CGPoint(), size: availableSize) + wallpaperBackgroundNode.updateLayout(size: availableSize, displayMode: .aspectFill, transition: .immediate) + wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) + wallpaperBackgroundNode.update(wallpaper: presentationData.chatWallpaper, animated: false) + self.internalWallpaperBackgroundNode = wallpaperBackgroundNode + self.insertSubview(wallpaperBackgroundNode.view, at: 0) + wallpaperBackgroundNode.alpha = 0.0 + } + + let localSourceTextInputViewFrame = convertFrame(component.textInputView.bounds, from: component.textInputView, to: self) + + let sourceMessageTextInsets = UIEdgeInsets(top: 7.0, left: 12.0, bottom: 6.0, right: 20.0) + let sourceBackgroundSize = CGSize(width: localSourceTextInputViewFrame.width + 32.0, height: localSourceTextInputViewFrame.height + 4.0) + let explicitMessageBackgroundSize: CGSize? + switch self.presentationAnimationState { + case .initial: + explicitMessageBackgroundSize = sourceBackgroundSize + case .animatedOut: + if self.animateOutToEmpty { + explicitMessageBackgroundSize = nil + } else { + explicitMessageBackgroundSize = sourceBackgroundSize + } + case .animatedIn: + explicitMessageBackgroundSize = nil + } + + let messageTextInsets = sourceMessageTextInsets + + let messageItemViewContainerSize: CGSize + if let mediaPreview { + switch mediaPreview.layoutType { + case .message, .media: + messageItemViewContainerSize = CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height) + case .videoMessage: + messageItemViewContainerSize = CGSize(width: availableSize.width, height: availableSize.height) + } + } else { + messageItemViewContainerSize = CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height) + } + + var isEditMessage = false + if case .editMessage = component.params { + isEditMessage = true + } + + let messageItemSize = messageItemView.update( + context: component.context, + presentationData: presentationData, + backgroundNode: wallpaperBackgroundNode, + textString: textString, + sourceTextInputView: component.textInputView as? ChatInputTextView, + emojiViewProvider: component.emojiViewProvider, + sourceMediaPreview: mediaPreview, + mediaCaptionIsAbove: self.mediaCaptionIsAbove, + textInsets: messageTextInsets, + explicitBackgroundSize: explicitMessageBackgroundSize, + maxTextWidth: localSourceTextInputViewFrame.width, + maxTextHeight: 20000.0, + containerSize: messageItemViewContainerSize, + effect: self.presentationAnimationState.key == .animatedIn ? self.selectedMessageEffect : nil, + isEditMessage: isEditMessage, + transition: transition + ) + let sourceMessageItemFrame = CGRect(origin: CGPoint(x: localSourceTextInputViewFrame.minX - sourceMessageTextInsets.left, y: localSourceTextInputViewFrame.minY - 2.0), size: messageItemSize) + + if let reactionItems = component.reactionItems, !reactionItems.isEmpty { + let reactionContextNode: ReactionContextNode + if let current = self.reactionContextNode { + reactionContextNode = current + } else { + reactionContextNode = ReactionContextNode( + context: component.context, + animationCache: component.context.animationCache, + presentationData: presentationData, + items: reactionItems.map { item in + var icon: EmojiPagerContentComponent.Item.Icon = .none + if !component.isPremium, case let .custom(sourceEffectId) = item.reaction.rawValue, let availableMessageEffects = component.availableMessageEffects { + for messageEffect in availableMessageEffects.messageEffects { + if messageEffect.id == sourceEffectId || messageEffect.effectSticker.fileId.id == sourceEffectId { + if messageEffect.isPremium { + icon = .locked + } + break + } + } + } + + return ReactionContextItem.reaction(item: item, icon: icon) + }, + selectedItems: Set(), + title: presentationData.strings.Chat_MessageEffectMenu_TitleAddEffect, + reactionsLocked: false, + alwaysAllowPremiumReactions: false, + allPresetReactionsAreAvailable: true, + getEmojiContent: { animationCache, animationRenderer in + return EmojiPagerContentComponent.messageEffectsInputData( + context: component.context, + animationCache: animationCache, + animationRenderer: animationRenderer, + hasSearch: true, + hideBackground: false + ) + }, + isExpandedUpdated: { [weak self] transition in + guard let self else { + return + } + if !self.isUpdating { + self.state?.updated(transition: Transition(transition)) + } + }, + requestLayout: { [weak self] transition in + guard let self else { + return + } + if !self.isUpdating { + self.state?.updated(transition: Transition(transition)) + } + }, + requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in + guard let self else { + return + } + self.requestUpdateOverlayWantsToBeBelowKeyboard(transition: transition) + } + ) + reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in + guard let self, let component = self.component, let reactionContextNode = self.reactionContextNode else { + return + } + + guard case let .custom(sourceEffectId, _) = updateReaction else { + return + } + + let messageEffect: Signal + messageEffect = component.context.engine.stickers.availableMessageEffects() + |> take(1) + |> map { availableMessageEffects -> AvailableMessageEffects.MessageEffect? in + guard let availableMessageEffects else { + return nil + } + for messageEffect in availableMessageEffects.messageEffects { + if messageEffect.id == sourceEffectId || messageEffect.effectSticker.fileId.id == sourceEffectId { + return messageEffect + } + } + return nil + } + + self.messageEffectDisposable.set((messageEffect + |> deliverOnMainQueue).startStrict(next: { [weak self] messageEffect in + guard let self, let component = self.component else { + return + } + guard let messageEffect else { + return + } + let effectId = messageEffect.id + + if let selectedMessageEffect = self.selectedMessageEffect { + if selectedMessageEffect.id == effectId { + self.selectedMessageEffect = nil + reactionContextNode.selectedItems = Set([]) + self.loadEffectAnimationDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable = nil + self.isLoadingEffectAnimation = false + + if let standaloneReactionAnimation = self.standaloneReactionAnimation { + self.standaloneReactionAnimation = nil + standaloneReactionAnimation.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak standaloneReactionAnimation] _ in + standaloneReactionAnimation?.removeFromSupernode() + }) + } + + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + return + } else { + self.selectedMessageEffect = messageEffect + reactionContextNode.selectedItems = Set([AnyHashable(updateReaction.reaction)]) + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + + HapticFeedback().tap() + } + } else { + self.selectedMessageEffect = messageEffect + reactionContextNode.selectedItems = Set([AnyHashable(updateReaction.reaction)]) + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + + HapticFeedback().tap() + } + + self.loadEffectAnimationDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable = (Signal.complete() |> delay(0.2, queue: .mainQueue()) |> deliverOnMainQueue).startStrict(completed: { [weak self] in + guard let self else { + return + } + self.isLoadingEffectAnimation = true + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + }) + + if let standaloneReactionAnimation = self.standaloneReactionAnimation { + self.standaloneReactionAnimation = nil + standaloneReactionAnimation.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak standaloneReactionAnimation] _ in + standaloneReactionAnimation?.removeFromSupernode() + }) + } + + var customEffectResource: (FileMediaReference, MediaResource)? + if let effectAnimation = messageEffect.effectAnimation { + customEffectResource = (FileMediaReference.standalone(media: effectAnimation), effectAnimation.resource) + } else { + let effectSticker = messageEffect.effectSticker + if let effectFile = effectSticker.videoThumbnails.first { + customEffectResource = (FileMediaReference.standalone(media: effectSticker), effectFile.resource) + } + } + guard let (customEffectResourceFileReference, customEffectResource) = customEffectResource else { + return + } + + let context = component.context + var loadEffectAnimationSignal: Signal + loadEffectAnimationSignal = Signal { subscriber in + let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: customEffectResourceFileReference, resource: customEffectResource).start() + + let dataDisposabke = (context.account.postbox.mediaBox.resourceStatus(customEffectResource) + |> filter { status in + if status == .Local { + return true + } else { + return false + } + } + |> take(1)).start(next: { _ in + subscriber.putCompletion() + }) + + return ActionDisposable { + fetchDisposable.dispose() + dataDisposabke.dispose() + } + } + /*#if DEBUG + loadEffectAnimationSignal = loadEffectAnimationSignal |> delay(1.0, queue: .mainQueue()) + #endif*/ + + self.loadEffectAnimationDisposable = (loadEffectAnimationSignal + |> deliverOnMainQueue).start(completed: { [weak self] in + guard let self, let component = self.component else { + return + } + + self.isLoadingEffectAnimationTimerDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable = nil + self.isLoadingEffectAnimation = false + + guard let targetView = self.messageItemView?.effectIconView else { + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + return + } + + let standaloneReactionAnimation: AnimatedStickerNode + var effectiveScale: CGFloat = 1.0 + #if targetEnvironment(simulator) + standaloneReactionAnimation = DirectAnimatedStickerNode() + effectiveScale = 1.4 + #else + if "".isEmpty { + standaloneReactionAnimation = DirectAnimatedStickerNode() + effectiveScale = 1.4 + } else { + standaloneReactionAnimation = LottieMetalAnimatedStickerNode() + } + #endif + + standaloneReactionAnimation.isUserInteractionEnabled = false + let effectSize = CGSize(width: 380.0, height: 380.0) + var effectFrame = effectSize.centered(around: targetView.convert(targetView.bounds.center, to: self)) + effectFrame.origin.x -= effectFrame.width * 0.3 + self.standaloneReactionAnimation = standaloneReactionAnimation + standaloneReactionAnimation.frame = effectFrame + standaloneReactionAnimation.updateLayout(size: effectFrame.size) + self.addSubnode(standaloneReactionAnimation) + + let pathPrefix = component.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(customEffectResource.id) + let source = AnimatedStickerResourceSource(account: component.context.account, resource: customEffectResource, fitzModifier: nil) + standaloneReactionAnimation.setup(source: source, width: Int(effectSize.width * effectiveScale), height: Int(effectSize.height * effectiveScale), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) + standaloneReactionAnimation.completed = { [weak self, weak standaloneReactionAnimation] _ in + guard let self else { + return + } + if let standaloneReactionAnimation { + standaloneReactionAnimation.removeFromSupernode() + if self.standaloneReactionAnimation === standaloneReactionAnimation { + self.standaloneReactionAnimation = nil + } + } + } + standaloneReactionAnimation.visibility = true + + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + + self.endEditing(true) + }) + })) + } + reactionContextNode.premiumReactionsSelected = { [weak self] _ in + guard let self, let component = self.component else { + return + } + + if let controller = self.environment?.controller() { + controller.forEachController { c in + if let c = c as? UndoOverlayController { + c.dismiss() + } + return true + } + } + + let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 }) + self.environment?.controller()?.present(UndoOverlayController( + presentationData: presentationData, + content: .premiumPaywall( + title: nil, + text: presentationData.strings.Chat_SendMessageMenu_ToastPremiumRequired_Text, + customUndoText: nil, + timeout: nil, + linkAction: nil + ), + elevatedLayout: false, + action: { [weak self] action in + guard let self, let component = self.component else { + return false + } + if case .info = action { + self.window?.endEditing(true) + self.animateOutToEmpty = true + self.environment?.controller()?.dismiss() + + //TODO:localize + let premiumController = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .animatedEmoji, forceDark: false, dismissed: nil) + component.openPremiumPaywall(premiumController) + } + return false + } + ), in: .current) + } + reactionContextNode.displayTail = true + reactionContextNode.forceTailToRight = false + reactionContextNode.forceDark = false + reactionContextNode.isMessageEffects = true + self.reactionContextNode = reactionContextNode + self.addSubview(reactionContextNode.view) + } + } + + let sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 44.0), height: sourceSendButtonFrame.height) + var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.maxX - sendButtonSize.width, y: sourceSendButtonFrame.minY), size: sendButtonSize) + + var sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize) + if !isMessageVisible { + sourceActionsStackFrame.origin.y = sourceSendButtonFrame.maxY - sourceActionsStackFrame.height - 5.0 + } + + var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 8.0 - messageItemSize.width, y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize) + if let mediaPreview { + switch mediaPreview.layoutType { + case .message, .media: + break + case .videoMessage: + readyMessageItemFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - messageItemSize.width) * 0.5), y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize) + } + } + + var readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize) + if !isMessageVisible { + readyActionsStackFrame.origin.y = readySendButtonFrame.maxY - readyActionsStackFrame.height - 5.0 + } + + let bottomOverflow = readyActionsStackFrame.maxY - (availableSize.height - environment.safeInsets.bottom) + if bottomOverflow > 0.0 { + readyMessageItemFrame.origin.y -= bottomOverflow + readyActionsStackFrame.origin.y -= bottomOverflow + readySendButtonFrame.origin.y -= bottomOverflow + } + + let inputCoverOverflow = readyMessageItemFrame.maxY + 7.0 - (availableSize.height - environment.inputHeight) + if inputCoverOverflow > 0.0 { + readyMessageItemFrame.origin.y -= inputCoverOverflow + readyActionsStackFrame.origin.y -= inputCoverOverflow + readySendButtonFrame.origin.y -= inputCoverOverflow + } + + if let mediaPreview { + switch mediaPreview.layoutType { + case .message, .media: + break + case .videoMessage: + let buttonActionsOffset: CGFloat = 5.0 + + let actionsStackAdjustmentY = sourceSendButtonFrame.maxY - buttonActionsOffset - readyActionsStackFrame.maxY + if abs(actionsStackAdjustmentY) < 10.0 { + readyActionsStackFrame.origin.y += actionsStackAdjustmentY + readyMessageItemFrame.origin.y += actionsStackAdjustmentY + } + + readySendButtonFrame.origin.y = readyActionsStackFrame.maxY + buttonActionsOffset - readySendButtonFrame.height + } + } + + let messageItemFrame: CGRect + let actionsStackFrame: CGRect + let sendButtonFrame: CGRect + switch self.presentationAnimationState { + case .initial: + if mediaPreview != nil { + messageItemFrame = readyMessageItemFrame + actionsStackFrame = readyActionsStackFrame + } else { + messageItemFrame = sourceMessageItemFrame + actionsStackFrame = sourceActionsStackFrame + } + sendButtonFrame = sourceSendButtonFrame + case .animatedOut: + if self.animateOutToEmpty { + messageItemFrame = readyMessageItemFrame + actionsStackFrame = readyActionsStackFrame + sendButtonFrame = readySendButtonFrame + } else { + if mediaPreview != nil { + messageItemFrame = readyMessageItemFrame + actionsStackFrame = readyActionsStackFrame + } else { + messageItemFrame = sourceMessageItemFrame + actionsStackFrame = sourceActionsStackFrame + } + sendButtonFrame = sourceSendButtonFrame + } + case .animatedIn: + messageItemFrame = readyMessageItemFrame + actionsStackFrame = readyActionsStackFrame + sendButtonFrame = readySendButtonFrame + } + + transition.setFrame(view: messageItemView, frame: messageItemFrame) + transition.setAlpha(view: messageItemView, alpha: isMessageVisible ? 1.0 : 0.0) + messageItemView.updateClippingRect( + sourceMediaPreview: mediaPreview, + isAnimatedIn: self.presentationAnimationState.key == .animatedIn, + localFrame: messageItemFrame, + containerSize: availableSize, + transition: transition + ) + + transition.setPosition(view: actionsStackNode.view, position: CGPoint(x: actionsStackFrame.minX + actionsStackNode.layer.anchorPoint.x * actionsStackFrame.width, y: actionsStackFrame.minY + actionsStackNode.layer.anchorPoint.y * actionsStackFrame.height)) + transition.setBounds(view: actionsStackNode.view, bounds: CGRect(origin: CGPoint(), size: actionsStackFrame.size)) + if !transition.animation.isImmediate && previousAnimationState.key != self.presentationAnimationState.key { + switch self.presentationAnimationState { + case .initial: + break + case .animatedIn: + transition.setAlpha(view: actionsStackNode.view, alpha: 1.0) + Transition.immediate.setScale(view: actionsStackNode.view, scale: 1.0) + actionsStackNode.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.42, damping: 104.0) + + messageItemView.animateIn( + sourceTextInputView: component.textInputView as? ChatInputTextView, + isEditMessage: isEditMessage, + transition: transition + ) + case .animatedOut: + transition.setAlpha(view: actionsStackNode.view, alpha: 0.0) + transition.setScale(view: actionsStackNode.view, scale: 0.001) + + messageItemView.animateOut( + sourceTextInputView: component.textInputView as? ChatInputTextView, + toEmpty: self.animateOutToEmpty, + isEditMessage: isEditMessage, + transition: transition + ) + } + } else { + switch self.presentationAnimationState { + case .animatedIn: + transition.setAlpha(view: actionsStackNode.view, alpha: 1.0) + transition.setScale(view: actionsStackNode.view, scale: 1.0) + case .animatedOut, .initial: + transition.setAlpha(view: actionsStackNode.view, alpha: 0.0) + transition.setScale(view: actionsStackNode.view, scale: 0.001) + } + } + + if let standaloneReactionAnimation, let targetView = messageItemView.effectIconView { + let effectSize = CGSize(width: 380.0, height: 380.0) + var effectFrame = effectSize.centered(around: targetView.convert(targetView.bounds.center, to: self)) + effectFrame.origin.x -= effectFrame.width * 0.3 + transition.setFrame(view: standaloneReactionAnimation.view, frame: effectFrame) + } + + if let reactionContextNode = self.reactionContextNode { + let reactionContextY = environment.statusBarHeight + let size = availableSize + var reactionsAnchorRect = messageItemFrame + if let mediaPreview { + switch mediaPreview.layoutType { + case .message, .media: + reactionsAnchorRect.size.width += 100.0 + reactionsAnchorRect.origin.x -= 4.0 + case .videoMessage: + reactionsAnchorRect.size.width += 100.0 + reactionsAnchorRect.origin.x = reactionsAnchorRect.midX - 130.0 + } + } + reactionsAnchorRect.origin.y -= reactionContextY + var isIntersectingContent = false + if reactionContextNode.isExpanded { + if messageItemFrame.minY <= reactionContextY + 50.0 + 4.0 { + isIntersectingContent = true + } + } else { + if messageItemFrame.minY <= reactionContextY + 300.0 + 4.0 { + isIntersectingContent = true + } + } + transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: reactionContextY), size: CGSize(width: availableSize.width, height: availableSize.height - reactionContextY))) + reactionContextNode.updateLayout(size: size, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: false, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition) + reactionContextNode.updateIsIntersectingContent(isIntersectingContent: isIntersectingContent, transition: transition.containedViewLayoutTransition) + if self.presentationAnimationState.key == .animatedIn && previousAnimationState.key == .initial { + reactionContextNode.animateIn(from: reactionsAnchorRect) + } else if self.presentationAnimationState.key == .animatedOut && previousAnimationState.key == .animatedIn { + reactionContextNode.animateOut(to: nil, animatingOutToReaction: false) + } + } + if case .animatedOut = self.presentationAnimationState { + if let standaloneReactionAnimation = self.standaloneReactionAnimation { + self.standaloneReactionAnimation = nil + standaloneReactionAnimation.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak standaloneReactionAnimation] _ in + standaloneReactionAnimation?.removeFromSupernode() + }) + } + } + + sendButton.update( + context: component.context, + presentationData: presentationData, + backgroundNode: component.wallpaperBackgroundNode, + sourceSendButton: component.sourceSendButton, + isAnimatedIn: self.presentationAnimationState.key == .animatedIn, + isLoadingEffectAnimation: self.isLoadingEffectAnimation, + size: sendButtonFrame.size, + transition: transition + ) + transition.setPosition(view: sendButton, position: sendButtonFrame.center) + transition.setBounds(view: sendButton, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size)) + transition.setScale(view: sendButton, scale: sendButtonScale) + sendButton.updateGlobalRect(rect: sendButtonFrame, within: availableSize, transition: transition) + + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: availableSize)) + self.backgroundView.update(size: availableSize, transition: transition.containedViewLayoutTransition) + let backgroundAlpha: CGFloat + switch self.presentationAnimationState { + case .animatedIn: + if previousAnimationState.key == .initial { + if environment.inputHeight != 0.0 { + if self.initializationDisplayLink == nil { + self.initializationDisplayLink = SharedDisplayLinkDriver.shared.add({ [weak self] _ in + guard let self else { + return + } + + self.initializationDisplayLink?.invalidate() + self.initializationDisplayLink = nil + + guard let component = self.component else { + return + } + if mediaPreview == nil { + component.textInputView.isHidden = true + } + component.sourceSendButton.isHidden = true + }) + } + } else { + if mediaPreview == nil { + component.textInputView.isHidden = true + } + component.sourceSendButton.isHidden = true + } + } + + backgroundAlpha = 1.0 + case .animatedOut: + backgroundAlpha = 0.0 + + if self.animateOutToEmpty { + if mediaPreview == nil { + component.textInputView.isHidden = false + } + component.sourceSendButton.isHidden = false + + transition.setAlpha(view: sendButton, alpha: 0.0) + if let messageItemView = self.messageItemView, isMessageVisible { + transition.setAlpha(view: messageItemView, alpha: 0.0) + } + } + default: + backgroundAlpha = 0.0 + } + + transition.setAlpha(view: self.backgroundView, alpha: backgroundAlpha, completion: { [weak self] _ in + guard let self else { + return + } + if case let .animatedOut(completion) = self.presentationAnimationState { + if !self.performedActionsOnAnimateOut { + self.performedActionsOnAnimateOut = true + if let component = self.component, !self.animateOutToEmpty { + if mediaPreview == nil { + component.textInputView.isHidden = false + } + component.sourceSendButton.isHidden = false + } + completion() + } + } + }) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + 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 class ChatSendMessageContextScreen: ViewControllerComponentContainer, ChatSendMessageActionSheetController { + private let context: AccountContext + + private var processedDidAppear: Bool = false + private var processedDidDisappear: Bool = false + + private var isActiveDisposable: Disposable? + + override public var overlayWantsToBeBelowKeyboard: Bool { + if let componentView = self.node.hostView.componentView as? ChatSendMessageContextScreenComponent.View { + return componentView.wantsToBeBelowKeyboard() + } else { + return false + } + } + + public init( + // MARK: Nicegram TranslateEnteredMessage + nicegramData: ChatSendMessageContextNicegramData, + // + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)?, + peerId: EnginePeer.Id?, + params: SendMessageActionSheetControllerParams, + hasEntityKeyboard: Bool, + gesture: ContextGesture, + sourceSendButton: ASDisplayNode, + textInputView: UITextView, + emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, + wallpaperBackgroundNode: WallpaperBackgroundNode?, + completion: @escaping () -> Void, + sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, + schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, + openPremiumPaywall: @escaping (ViewController) -> Void, + reactionItems: [ReactionItem]?, + availableMessageEffects: AvailableMessageEffects?, + isPremium: Bool + ) { + self.context = context + + super.init( + context: context, + component: ChatSendMessageContextScreenComponent( + // MARK: Nicegram TranslateEnteredMessage + nicegramData: nicegramData, + // + context: context, + updatedPresentationData: updatedPresentationData, + peerId: peerId, + params: params, + hasEntityKeyboard: hasEntityKeyboard, + gesture: gesture, + sourceSendButton: sourceSendButton, + textInputView: textInputView, + emojiViewProvider: emojiViewProvider, + wallpaperBackgroundNode: wallpaperBackgroundNode, + completion: completion, + sendMessage: sendMessage, + schedule: schedule, + openPremiumPaywall: openPremiumPaywall, + reactionItems: reactionItems, + availableMessageEffects: availableMessageEffects, + isPremium: isPremium + ), + navigationBarAppearance: .none, + statusBarStyle: .none, + presentationMode: .default, + updatedPresentationData: updatedPresentationData + ) + + self.lockOrientation = true + self.blocksBackgroundWhenInOverlay = true + + self.isActiveDisposable = (context.sharedContext.applicationBindings.applicationInForeground + |> filter { !$0 } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] _ in + guard let self else { + return + } + self.dismiss() + }) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.isActiveDisposable?.dispose() + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !self.processedDidAppear { + self.processedDidAppear = true + if let componentView = self.node.hostView.componentView as? ChatSendMessageContextScreenComponent.View { + componentView.animateIn() + } + } + } + + private func superDismiss() { + super.dismiss() + } + + override public func dismiss(completion: (() -> Void)? = nil) { + if !self.processedDidDisappear { + self.processedDidDisappear = true + + if let componentView = self.node.hostView.componentView as? ChatSendMessageContextScreenComponent.View { + componentView.animateOut(completion: { [weak self] in + if let self { + self.superDismiss() + } + completion?() + }) + } else { + super.dismiss(completion: completion) + } + } + } +} diff --git a/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift b/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift new file mode 100644 index 00000000000..cf48ea62ba0 --- /dev/null +++ b/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift @@ -0,0 +1,828 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import AccountContext +import ContextUI +import TelegramCore +import TextFormat +import ReactionSelectionNode +import ViewControllerComponent +import ComponentFlow +import ComponentDisplayAdapters +import ChatMessageBackground +import WallpaperBackgroundNode +import MultilineTextWithEntitiesComponent +import ReactionButtonListComponent +import MultilineTextComponent +import ChatInputTextNode +import EmojiTextAttachmentView + +private final class EffectIcon: Component { + enum Content: Equatable { + case file(TelegramMediaFile) + case text(String) + } + + let context: AccountContext + let content: Content + + init( + context: AccountContext, + content: Content + ) { + self.context = context + self.content = content + } + + static func ==(lhs: EffectIcon, rhs: EffectIcon) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.content != rhs.content { + return false + } + return true + } + + final class View: UIView { + private var fileView: ReactionIconView? + private var textView: ComponentView? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: EffectIcon, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + if case let .file(file) = component.content { + let fileView: ReactionIconView + if let current = self.fileView { + fileView = current + } else { + fileView = ReactionIconView() + self.fileView = fileView + self.addSubview(fileView) + } + fileView.update( + size: availableSize, + context: component.context, + file: file, + fileId: file.fileId.id, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + tintColor: nil, + placeholderColor: UIColor(white: 0.0, alpha: 0.1), + animateIdle: false, + reaction: .custom(file.fileId.id), + transition: .immediate + ) + fileView.frame = CGRect(origin: CGPoint(), size: availableSize) + } else { + if let fileView = self.fileView { + self.fileView = nil + fileView.removeFromSuperview() + } + } + + if case let .text(text) = component.content { + let textView: ComponentView + if let current = self.textView { + textView = current + } else { + textView = ComponentView() + self.textView = textView + } + let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0) + let textSize = textView.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: text, font: Font.regular(10.0), textColor: .black)), + insets: textInsets + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - textSize.width) * 0.5), y: floorToScreenPixels((availableSize.height - textSize.height) * 0.5)), size: textSize) + if let textComponentView = textView.view { + if textComponentView.superview == nil { + self.addSubview(textComponentView) + } + textComponentView.frame = textFrame + } + } else { + if let textView = self.textView { + self.textView = nil + textView.view?.removeFromSuperview() + } + } + + 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 CustomEmojiContainerView: UIView { + private let emojiViewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView? + + private var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:] + + init(emojiViewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView?) { + self.emojiViewProvider = emojiViewProvider + + super.init(frame: CGRect()) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func update(emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)]) { + var nextIndexById: [Int64: Int] = [:] + + var validKeys = Set() + for (rect, emoji) in emojiRects { + let index: Int + if let nextIndex = nextIndexById[emoji.fileId] { + index = nextIndex + } else { + index = 0 + } + nextIndexById[emoji.fileId] = index + 1 + + let key = InlineStickerItemLayer.Key(id: emoji.fileId, index: index) + + let view: UIView + if let current = self.emojiLayers[key] { + view = current + } else if let newView = self.emojiViewProvider(emoji) { + view = newView + self.addSubview(newView) + self.emojiLayers[key] = view + } else { + continue + } + + let size = CGSize(width: 24.0, height: 24.0) + + view.frame = CGRect(origin: CGPoint(x: floor(rect.midX - size.width / 2.0), y: floor(rect.midY - size.height / 2.0)), size: size) + + validKeys.insert(key) + } + + var removeKeys: [InlineStickerItemLayer.Key] = [] + for (key, view) in self.emojiLayers { + if !validKeys.contains(key) { + removeKeys.append(key) + view.removeFromSuperview() + } + } + for key in removeKeys { + self.emojiLayers.removeValue(forKey: key) + } + } +} + + +final class MessageItemView: UIView { + private let backgroundWallpaperNode: ChatMessageBubbleBackdrop + private let backgroundNode: ChatMessageBackground + + private let textClippingContainer: UIView + private var textNode: ChatInputTextNode? + private var customEmojiContainerView: CustomEmojiContainerView? + private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? + + private var mediaPreviewClippingView: UIView? + private var mediaPreview: ChatSendMessageContextScreenMediaPreview? + + private var effectIcon: ComponentView? + var effectIconView: UIView? { + return self.effectIcon?.view + } + + private var effectIconBackgroundView: UIImageView? + + private var chatTheme: ChatPresentationThemeData? + private var currentSize: CGSize? + private var currentMediaCaptionIsAbove: Bool = false + + override init(frame: CGRect) { + self.backgroundWallpaperNode = ChatMessageBubbleBackdrop() + self.backgroundNode = ChatMessageBackground() + self.backgroundNode.backdropNode = self.backgroundWallpaperNode + + self.textClippingContainer = UIView() + self.textClippingContainer.layer.anchorPoint = CGPoint() + self.textClippingContainer.clipsToBounds = true + + super.init(frame: frame) + + self.isUserInteractionEnabled = false + + self.addSubview(self.backgroundWallpaperNode.view) + self.addSubview(self.backgroundNode.view) + + self.addSubview(self.textClippingContainer) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func animateIn( + sourceTextInputView: ChatInputTextView?, + isEditMessage: Bool, + transition: Transition + ) { + if isEditMessage { + transition.animateScale(view: self, from: 0.001, to: 1.0) + transition.animateAlpha(view: self, from: 0.0, to: 1.0) + } else { + if let mediaPreview = self.mediaPreview { + mediaPreview.animateIn(transition: transition) + } + } + } + + func animateOut( + sourceTextInputView: ChatInputTextView?, + toEmpty: Bool, + isEditMessage: Bool, + transition: Transition + ) { + if isEditMessage { + transition.setScale(view: self, scale: 0.001) + transition.setAlpha(view: self, alpha: 0.0) + } else { + if let mediaPreview = self.mediaPreview { + if toEmpty { + mediaPreview.animateOutOnSend(transition: transition) + } else { + mediaPreview.animateOut(transition: transition) + } + } + } + } + + func update( + context: AccountContext, + presentationData: PresentationData, + backgroundNode: WallpaperBackgroundNode?, + textString: NSAttributedString, + sourceTextInputView: ChatInputTextView?, + emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, + sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?, + mediaCaptionIsAbove: Bool, + textInsets: UIEdgeInsets, + explicitBackgroundSize: CGSize?, + maxTextWidth: CGFloat, + maxTextHeight: CGFloat, + containerSize: CGSize, + effect: AvailableMessageEffects.MessageEffect?, + isEditMessage: Bool, + transition: Transition + ) -> CGSize { + self.emojiViewProvider = emojiViewProvider + + var effectIconSize: CGSize? + if let effect { + let effectIcon: ComponentView + if let current = self.effectIcon { + effectIcon = current + } else { + effectIcon = ComponentView() + self.effectIcon = effectIcon + } + let effectIconContent: EffectIcon.Content + if let staticIcon = effect.staticIcon { + effectIconContent = .file(staticIcon) + } else { + effectIconContent = .text(effect.emoticon) + } + effectIconSize = effectIcon.update( + transition: .immediate, + component: AnyComponent(EffectIcon( + context: context, + content: effectIconContent + )), + environment: {}, + containerSize: CGSize(width: 8.0, height: 8.0) + ) + } + + let chatTheme: ChatPresentationThemeData + if let current = self.chatTheme, current.theme === presentationData.theme { + chatTheme = current + } else { + chatTheme = ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper) + self.chatTheme = chatTheme + } + + let themeGraphics = PresentationResourcesChat.principalGraphics(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper, bubbleCorners: presentationData.chatBubbleCorners) + self.backgroundWallpaperNode.setType( + type: .outgoing(.None), + theme: chatTheme, + essentialGraphics: themeGraphics, + maskMode: true, + backgroundNode: backgroundNode + ) + + self.backgroundNode.setType( + type: .outgoing(.None), + highlighted: false, + graphics: themeGraphics, + maskMode: true, + hasWallpaper: true, + transition: transition.containedViewLayoutTransition, + backgroundNode: backgroundNode + ) + + let alphaTransition: Transition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25) + + if let sourceMediaPreview { + let mediaPreviewClippingView: UIView + if let current = self.mediaPreviewClippingView { + mediaPreviewClippingView = current + } else { + mediaPreviewClippingView = UIView() + mediaPreviewClippingView.layer.anchorPoint = CGPoint() + mediaPreviewClippingView.clipsToBounds = true + mediaPreviewClippingView.isUserInteractionEnabled = false + self.mediaPreviewClippingView = mediaPreviewClippingView + self.addSubview(mediaPreviewClippingView) + } + + if self.mediaPreview !== sourceMediaPreview { + self.mediaPreview?.view.removeFromSuperview() + self.mediaPreview = nil + + self.mediaPreview = sourceMediaPreview + if let mediaPreview = self.mediaPreview { + mediaPreviewClippingView.addSubview(mediaPreview.view) + } + } + + let mediaPreviewSize = sourceMediaPreview.update(containerSize: containerSize, transition: transition) + + var backgroundSize = CGSize(width: mediaPreviewSize.width, height: mediaPreviewSize.height) + var mediaPreviewFrame: CGRect + switch sourceMediaPreview.layoutType { + case .message, .media: + backgroundSize.width += 7.0 + mediaPreviewFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mediaPreviewSize) + case .videoMessage: + mediaPreviewFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mediaPreviewSize) + } + + let backgroundAlpha: CGFloat + switch sourceMediaPreview.layoutType { + case .media: + if textString.length != 0 { + backgroundAlpha = explicitBackgroundSize != nil ? 0.0 : 1.0 + } else { + backgroundAlpha = 0.0 + } + case .message, .videoMessage: + backgroundAlpha = 0.0 + } + + let backgroundScale: CGFloat = 1.0 + + var backgroundFrame = mediaPreviewFrame.insetBy(dx: -2.0, dy: -2.0) + backgroundFrame.size.width += 6.0 + + if textString.length != 0, case .media = sourceMediaPreview.layoutType { + let textNode: ChatInputTextNode + if let current = self.textNode { + textNode = current + } else { + textNode = ChatInputTextNode(disableTiling: true) + textNode.textView.isScrollEnabled = false + textNode.isUserInteractionEnabled = false + self.textNode = textNode + self.textClippingContainer.addSubview(textNode.view) + + if let sourceTextInputView { + var textContainerInset = sourceTextInputView.defaultTextContainerInset + textContainerInset.right = 0.0 + textNode.textView.defaultTextContainerInset = textContainerInset + } + + let messageAttributedText = NSMutableAttributedString(attributedString: textString) + textNode.attributedText = messageAttributedText + } + + let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor + let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle + if let sourceTextInputView, let textTheme = sourceTextInputView.theme { + switch textTheme.quote.lineStyle { + case .solid: + mappedLineStyle = .solid(color: mainColor) + case .doubleDashed: + mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear) + case .tripleDashed: + mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear) + } + } else { + mappedLineStyle = .solid(color: mainColor) + } + + textNode.textView.theme = ChatInputTextView.Theme( + quote: ChatInputTextView.Theme.Quote( + background: mainColor.withMultipliedAlpha(0.1), + foreground: mainColor, + lineStyle: mappedLineStyle, + codeBackground: mainColor.withMultipliedAlpha(0.1), + codeForeground: mainColor + ) + ) + + let maxTextWidth = mediaPreviewFrame.width + + let textPositioningInsets = UIEdgeInsets(top: -5.0, left: 0.0, bottom: -4.0, right: -4.0) + + let currentRightInset: CGFloat = 0.0 + let textHeight = textNode.textHeightForWidth(maxTextWidth, rightInset: currentRightInset) + textNode.updateLayout(size: CGSize(width: maxTextWidth, height: textHeight)) + + let textBoundingRect = textNode.textView.currentTextBoundingRect().integral + let lastLineBoundingRect = textNode.textView.lastLineBoundingRect().integral + + let textWidth = textBoundingRect.width + let textSize = CGSize(width: textWidth, height: textHeight) + + var positionedTextSize = CGSize(width: textSize.width + textPositioningInsets.left + textPositioningInsets.right, height: textSize.height + textPositioningInsets.top + textPositioningInsets.bottom) + + let effectInset: CGFloat = 12.0 + if effect != nil, lastLineBoundingRect.width > textSize.width - effectInset { + if lastLineBoundingRect != textBoundingRect { + positionedTextSize.height += 11.0 + } else { + positionedTextSize.width += effectInset + } + } + let unclippedPositionedTextHeight = positionedTextSize.height - (textPositioningInsets.top + textPositioningInsets.bottom) + + positionedTextSize.height = min(positionedTextSize.height, maxTextHeight) + + let size = CGSize(width: positionedTextSize.width + textInsets.left + textInsets.right, height: positionedTextSize.height + textInsets.top + textInsets.bottom) + + var textFrame = CGRect(origin: CGPoint(x: textInsets.left - 6.0, y: backgroundFrame.height - 4.0 + textInsets.top), size: positionedTextSize) + if mediaCaptionIsAbove { + textFrame.origin.y = 5.0 + } + + backgroundFrame.size.height += textSize.height + 2.0 + if mediaCaptionIsAbove { + mediaPreviewFrame.origin.y += textSize.height + 2.0 + } + + let backgroundSize = explicitBackgroundSize ?? size + + let previousSize = self.currentSize + self.currentSize = backgroundFrame.size + let _ = previousSize + + let textClippingContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + 1.0, y: backgroundFrame.minY + 1.0), size: CGSize(width: backgroundFrame.width - 1.0 - 7.0, height: backgroundFrame.height - 1.0 - 1.0)) + + var textClippingContainerBounds = CGRect(origin: CGPoint(), size: textClippingContainerFrame.size) + if explicitBackgroundSize != nil, let sourceTextInputView { + textClippingContainerBounds.origin.y = sourceTextInputView.contentOffset.y + } else { + textClippingContainerBounds.origin.y = unclippedPositionedTextHeight - backgroundSize.height + 4.0 + textClippingContainerBounds.origin.y = max(0.0, textClippingContainerBounds.origin.y) + } + + transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.origin) + transition.setBounds(view: self.textClippingContainer, bounds: textClippingContainerBounds) + + alphaTransition.setAlpha(view: textNode.view, alpha: backgroundAlpha) + transition.setFrame(view: textNode.view, frame: CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight))) + self.updateTextContents() + } + + transition.setFrame(view: sourceMediaPreview.view, frame: mediaPreviewFrame) + + transition.setPosition(view: self.backgroundWallpaperNode.view, position: CGRect(origin: CGPoint(), size: backgroundFrame.size).center) + transition.setBounds(view: self.backgroundWallpaperNode.view, bounds: CGRect(origin: CGPoint(), size: backgroundFrame.size)) + alphaTransition.setAlpha(view: self.backgroundWallpaperNode.view, alpha: backgroundAlpha) + transition.setScale(view: self.backgroundWallpaperNode.view, scale: backgroundScale) + self.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: transition.containedViewLayoutTransition) + transition.setPosition(view: self.backgroundNode.view, position: backgroundFrame.center) + transition.setBounds(view: self.backgroundNode.view, bounds: CGRect(origin: CGPoint(), size: backgroundFrame.size)) + transition.setScale(view: self.backgroundNode.view, scale: backgroundScale) + alphaTransition.setAlpha(view: self.backgroundNode.view, alpha: backgroundAlpha) + self.backgroundNode.updateLayout(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition) + + if let effectIcon = self.effectIcon, let effectIconSize { + if let effectIconView = effectIcon.view { + var animateIn = false + if effectIconView.superview == nil { + animateIn = true + self.addSubview(effectIconView) + } + + let effectIconBackgroundView: UIImageView + if let current = self.effectIconBackgroundView { + effectIconBackgroundView = current + } else { + effectIconBackgroundView = UIImageView() + self.effectIconBackgroundView = effectIconBackgroundView + self.insertSubview(effectIconBackgroundView, belowSubview: effectIconView) + } + + let effectIconBackgroundSize = CGSize(width: effectIconSize.width + 8.0 * 2.0, height: 18.0) + + let effectIconBackgroundFrame: CGRect + switch sourceMediaPreview.layoutType { + case .message: + effectIconBackgroundFrame = CGRect(origin: CGPoint(x: mediaPreviewFrame.maxX - effectIconBackgroundSize.width - 3.0, y: mediaPreviewFrame.maxY - effectIconBackgroundSize.height - 4.0), size: effectIconBackgroundSize) + effectIconBackgroundView.backgroundColor = nil + case .media: + effectIconBackgroundFrame = CGRect(origin: CGPoint(x: mediaPreviewFrame.maxX - effectIconBackgroundSize.width - 6.0, y: mediaPreviewFrame.maxY - effectIconBackgroundSize.height - 6.0), size: effectIconBackgroundSize) + effectIconBackgroundView.backgroundColor = presentationData.theme.chat.message.mediaDateAndStatusFillColor + case .videoMessage: + effectIconBackgroundFrame = CGRect(origin: CGPoint(x: mediaPreviewFrame.maxX - effectIconBackgroundSize.width - 34.0, y: mediaPreviewFrame.maxY - effectIconBackgroundSize.height - 6.0), size: effectIconBackgroundSize) + + let serviceMessageColors = serviceMessageColorComponents(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper) + + effectIconBackgroundView.backgroundColor = serviceMessageColors.dateFillStatic + } + + let effectIconFrame = CGRect(origin: CGPoint(x: effectIconBackgroundFrame.minX + floor((effectIconBackgroundFrame.width - effectIconSize.width) * 0.5), y: effectIconBackgroundFrame.minY + floor((effectIconBackgroundFrame.height - effectIconSize.height) * 0.5)), size: effectIconSize) + + if animateIn { + effectIconView.frame = effectIconFrame + + effectIconBackgroundView.frame = effectIconBackgroundFrame + effectIconBackgroundView.layer.cornerRadius = effectIconBackgroundFrame.height * 0.5 + + transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0) + if !transition.animation.isImmediate { + effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) + } + + transition.animateAlpha(view: effectIconBackgroundView, from: 0.0, to: 1.0) + } + + transition.setFrame(view: effectIconView, frame: effectIconFrame) + + transition.setFrame(view: effectIconBackgroundView, frame: effectIconBackgroundFrame) + transition.setCornerRadius(layer: effectIconBackgroundView.layer, cornerRadius: effectIconBackgroundFrame.height * 0.5) + } + } else { + if let effectIcon = self.effectIcon { + self.effectIcon = nil + + if let effectIconView = effectIcon.view { + transition.setScale(view: effectIconView, scale: 0.001) + transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in + effectIconView?.removeFromSuperview() + }) + } + } + if let effectIconBackgroundView = self.effectIconBackgroundView { + self.effectIconBackgroundView = nil + transition.setAlpha(view: effectIconBackgroundView, alpha: 0.0, completion: { [weak effectIconBackgroundView] _ in + effectIconBackgroundView?.removeFromSuperview() + }) + } + } + + return backgroundFrame.size + } else { + let textNode: ChatInputTextNode + if let current = self.textNode { + textNode = current + } else { + textNode = ChatInputTextNode(disableTiling: true) + textNode.textView.isScrollEnabled = false + textNode.isUserInteractionEnabled = false + self.textNode = textNode + self.textClippingContainer.addSubview(textNode.view) + + if let sourceTextInputView { + textNode.textView.defaultTextContainerInset = sourceTextInputView.defaultTextContainerInset + } + + let messageAttributedText = NSMutableAttributedString(attributedString: textString) + textNode.attributedText = messageAttributedText + } + + let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor + let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle + if let sourceTextInputView, let textTheme = sourceTextInputView.theme { + switch textTheme.quote.lineStyle { + case .solid: + mappedLineStyle = .solid(color: mainColor) + case .doubleDashed: + mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear) + case .tripleDashed: + mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear) + } + } else { + mappedLineStyle = .solid(color: mainColor) + } + + textNode.textView.theme = ChatInputTextView.Theme( + quote: ChatInputTextView.Theme.Quote( + background: mainColor.withMultipliedAlpha(0.1), + foreground: mainColor, + lineStyle: mappedLineStyle, + codeBackground: mainColor.withMultipliedAlpha(0.1), + codeForeground: mainColor + ) + ) + + let textPositioningInsets = UIEdgeInsets(top: -5.0, left: 0.0, bottom: -4.0, right: -4.0) + + var currentRightInset: CGFloat = 0.0 + if let sourceTextInputView { + currentRightInset = sourceTextInputView.currentRightInset + } + let textHeight = textNode.textHeightForWidth(maxTextWidth, rightInset: currentRightInset) + textNode.updateLayout(size: CGSize(width: maxTextWidth, height: textHeight)) + + let textBoundingRect = textNode.textView.currentTextBoundingRect().integral + let lastLineBoundingRect = textNode.textView.lastLineBoundingRect().integral + + let textWidth = textBoundingRect.width + let textSize = CGSize(width: textWidth, height: textHeight) + + var positionedTextSize = CGSize(width: textSize.width + textPositioningInsets.left + textPositioningInsets.right, height: textSize.height + textPositioningInsets.top + textPositioningInsets.bottom) + + let effectInset: CGFloat = 12.0 + if effect != nil, lastLineBoundingRect.width > textSize.width - effectInset { + if lastLineBoundingRect != textBoundingRect { + positionedTextSize.height += 11.0 + } else { + positionedTextSize.width += effectInset + } + } + let unclippedPositionedTextHeight = positionedTextSize.height - (textPositioningInsets.top + textPositioningInsets.bottom) + + positionedTextSize.height = min(positionedTextSize.height, maxTextHeight) + + let size = CGSize(width: positionedTextSize.width + textInsets.left + textInsets.right, height: positionedTextSize.height + textInsets.top + textInsets.bottom) + + let textFrame = CGRect(origin: CGPoint(x: textInsets.left, y: textInsets.top), size: positionedTextSize) + + let backgroundSize = explicitBackgroundSize ?? size + + let previousSize = self.currentSize + self.currentSize = backgroundSize + + let textClippingContainerFrame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: backgroundSize.width - 1.0 - 7.0, height: backgroundSize.height - 1.0 - 1.0)) + + var textClippingContainerBounds = CGRect(origin: CGPoint(), size: textClippingContainerFrame.size) + if explicitBackgroundSize != nil, let sourceTextInputView { + textClippingContainerBounds.origin.y = sourceTextInputView.contentOffset.y + } else { + textClippingContainerBounds.origin.y = unclippedPositionedTextHeight - backgroundSize.height + 4.0 + textClippingContainerBounds.origin.y = max(0.0, textClippingContainerBounds.origin.y) + } + + transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.origin) + transition.setBounds(view: self.textClippingContainer, bounds: textClippingContainerBounds) + + textNode.view.frame = CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight)) + self.updateTextContents() + + if let effectIcon = self.effectIcon, let effectIconSize { + if let effectIconView = effectIcon.view { + var animateIn = false + if effectIconView.superview == nil { + animateIn = true + self.addSubview(effectIconView) + } + let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right + 2.0 - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize) + if animateIn { + if let previousSize { + let previousEffectIconFrame = CGRect(origin: CGPoint(x: previousSize.width - textInsets.right + 2.0 - effectIconSize.width, y: previousSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize) + effectIconView.frame = previousEffectIconFrame + } else { + effectIconView.frame = effectIconFrame + } + transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0) + if !transition.animation.isImmediate { + effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) + } + } + + transition.setFrame(view: effectIconView, frame: effectIconFrame) + } + } else { + if let effectIcon = self.effectIcon { + self.effectIcon = nil + + if let effectIconView = effectIcon.view { + let effectIconSize = effectIconView.bounds.size + let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - effectIconSize.height), size: effectIconSize) + transition.setFrame(view: effectIconView, frame: effectIconFrame) + transition.setScale(view: effectIconView, scale: 0.001) + transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in + effectIconView?.removeFromSuperview() + }) + } + } + } + + let backgroundAlpha: CGFloat + if explicitBackgroundSize != nil { + backgroundAlpha = 0.0 + } else { + backgroundAlpha = 1.0 + } + + transition.setFrame(view: self.backgroundWallpaperNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize)) + transition.setAlpha(view: self.backgroundWallpaperNode.view, alpha: backgroundAlpha) + self.backgroundWallpaperNode.updateFrame(CGRect(origin: CGPoint(), size: backgroundSize), transition: transition.containedViewLayoutTransition) + transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize)) + transition.setAlpha(view: self.backgroundNode.view, alpha: backgroundAlpha) + self.backgroundNode.updateLayout(size: backgroundSize, transition: transition.containedViewLayoutTransition) + + return backgroundSize + } + } + + func updateClippingRect( + sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?, + isAnimatedIn: Bool, + localFrame: CGRect, + containerSize: CGSize, + transition: Transition + ) { + if let mediaPreviewClippingView = self.mediaPreviewClippingView, let sourceMediaPreview { + let clippingFrame: CGRect + if !isAnimatedIn, let globalClippingRect = sourceMediaPreview.globalClippingRect { + clippingFrame = self.convert(globalClippingRect, from: nil) + } else { + clippingFrame = CGRect(origin: CGPoint(x: -localFrame.minX, y: -localFrame.minY), size: containerSize) + } + + transition.setPosition(view: mediaPreviewClippingView, position: clippingFrame.origin) + transition.setBounds(view: mediaPreviewClippingView, bounds: CGRect(origin: CGPoint(x: clippingFrame.minX, y: clippingFrame.minY), size: clippingFrame.size)) + } + } + + private func updateTextContents() { + guard let textInputNode = self.textNode else { + return + } + + var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = [] + + if let attributedText = textInputNode.attributedText { + let beginning = textInputNode.textView.beginningOfDocument + attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in + if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { + if let start = textInputNode.textView.position(from: beginning, offset: range.location), let end = textInputNode.textView.position(from: start, offset: range.length), let textRange = textInputNode.textView.textRange(from: start, to: end) { + let textRects = textInputNode.textView.selectionRects(for: textRange) + for textRect in textRects { + customEmojiRects.append((textRect.rect, value)) + break + } + } + } + }) + } + + if !customEmojiRects.isEmpty { + let customEmojiContainerView: CustomEmojiContainerView + if let current = self.customEmojiContainerView { + customEmojiContainerView = current + } else { + customEmojiContainerView = CustomEmojiContainerView(emojiViewProvider: { [weak self] emoji in + guard let self, let emojiViewProvider = self.emojiViewProvider else { + return nil + } + return emojiViewProvider(emoji) + }) + customEmojiContainerView.isUserInteractionEnabled = false + textInputNode.textView.addSubview(customEmojiContainerView) + self.customEmojiContainerView = customEmojiContainerView + } + + customEmojiContainerView.update(emojiRects: customEmojiRects) + } else { + if let customEmojiContainerView = self.customEmojiContainerView { + customEmojiContainerView.removeFromSuperview() + self.customEmojiContainerView = nil + } + } + } +} diff --git a/submodules/ChatSendMessageActionUI/Sources/SendButton.swift b/submodules/ChatSendMessageActionUI/Sources/SendButton.swift new file mode 100644 index 00000000000..6d637852d19 --- /dev/null +++ b/submodules/ChatSendMessageActionUI/Sources/SendButton.swift @@ -0,0 +1,191 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import AccountContext +import ContextUI +import TelegramCore +import TextFormat +import ReactionSelectionNode +import ViewControllerComponent +import ComponentFlow +import ComponentDisplayAdapters +import ChatMessageBackground +import WallpaperBackgroundNode +import AppBundle +import ActivityIndicator +import RadialStatusNode + +final class SendButton: HighlightTrackingButton { + enum Kind { + case send + case edit + } + + private let kind: Kind + + private let containerView: UIView + private var backgroundContent: WallpaperBubbleBackgroundNode? + private let backgroundLayer: SimpleLayer + private let iconView: UIImageView + private var activityIndicator: RadialStatusNode? + + private var didProcessSourceCustomContent: Bool = false + private var sourceCustomContentView: UIView? + + init(kind: Kind) { + self.kind = kind + + self.containerView = UIView() + self.containerView.isUserInteractionEnabled = false + + self.backgroundLayer = SimpleLayer() + + self.iconView = UIImageView() + self.iconView.isUserInteractionEnabled = false + + super.init(frame: CGRect()) + + self.containerView.clipsToBounds = true + self.addSubview(self.containerView) + + self.containerView.layer.addSublayer(self.backgroundLayer) + self.containerView.addSubview(self.iconView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update( + context: AccountContext, + presentationData: PresentationData, + backgroundNode: WallpaperBackgroundNode?, + sourceSendButton: ASDisplayNode, + isAnimatedIn: Bool, + isLoadingEffectAnimation: Bool, + size: CGSize, + transition: Transition + ) { + let innerSize = CGSize(width: size.width - 5.5 * 2.0, height: 33.0) + transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - innerSize.width) * 0.5), y: floorToScreenPixels((size.height - innerSize.height) * 0.5)), size: innerSize)) + transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: innerSize.height * 0.5) + + if self.window != nil { + if self.backgroundContent == nil, let backgroundNode = backgroundNode as? WallpaperBackgroundNodeImpl { + if let backgroundContent = backgroundNode.makeLegacyBubbleBackground(for: .outgoing) { + self.backgroundContent = backgroundContent + self.containerView.insertSubview(backgroundContent.view, at: 0) + } + } + } + + if let backgroundContent = self.backgroundContent { + transition.setFrame(view: backgroundContent.view, frame: CGRect(origin: CGPoint(), size: innerSize)) + } + + if backgroundNode != nil && [.day, .night].contains(presentationData.theme.referenceTheme.baseTheme) && !presentationData.theme.chat.message.outgoing.bubble.withWallpaper.hasSingleFillColor { + self.backgroundContent?.isHidden = false + self.backgroundLayer.isHidden = true + } else { + self.backgroundContent?.isHidden = true + self.backgroundLayer.isHidden = false + } + + self.backgroundLayer.backgroundColor = presentationData.theme.chat.inputPanel.actionControlFillColor.cgColor + transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: innerSize)) + + if !self.didProcessSourceCustomContent { + self.didProcessSourceCustomContent = true + + if let sourceSendButton = sourceSendButton as? ChatSendMessageActionSheetControllerSourceSendButtonNode { + if let sourceCustomContentView = sourceSendButton.makeCustomContents() { + self.sourceCustomContentView = sourceCustomContentView + self.iconView.superview?.insertSubview(sourceCustomContentView, belowSubview: self.iconView) + } + } + } + + if self.iconView.image == nil { + switch self.kind { + case .send: + self.iconView.image = PresentationResourcesChat.chatInputPanelSendIconImage(presentationData.theme) + case .edit: + self.iconView.image = PresentationResourcesChat.chatInputPanelApplyIconImage(presentationData.theme) + } + } + + if let sourceCustomContentView = self.sourceCustomContentView { + let sourceCustomContentSize = sourceCustomContentView.bounds.size + let sourceCustomContentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - sourceCustomContentSize.width) * 0.5) + UIScreenPixel, y: floorToScreenPixels((innerSize.height - sourceCustomContentSize.height) * 0.5)), size: sourceCustomContentSize) + transition.setPosition(view: sourceCustomContentView, position: sourceCustomContentFrame.center) + transition.setBounds(view: sourceCustomContentView, bounds: CGRect(origin: CGPoint(), size: sourceCustomContentFrame.size)) + transition.setAlpha(view: sourceCustomContentView, alpha: isAnimatedIn ? 0.0 : 1.0) + } + + if let icon = self.iconView.image { + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - icon.size.width) * 0.5) - UIScreenPixel, y: floorToScreenPixels((innerSize.height - icon.size.height) * 0.5)), size: icon.size) + transition.setPosition(view: self.iconView, position: iconFrame.center) + transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + + let iconViewAlpha: CGFloat + if (self.sourceCustomContentView != nil && !isAnimatedIn) || isLoadingEffectAnimation { + iconViewAlpha = 0.0 + } else { + iconViewAlpha = 1.0 + } + transition.setAlpha(view: self.iconView, alpha: iconViewAlpha) + transition.setScale(view: self.iconView, scale: isLoadingEffectAnimation ? 0.001 : 1.0) + } + + if isLoadingEffectAnimation { + var animateIn = false + let activityIndicator: RadialStatusNode + if let current = self.activityIndicator { + activityIndicator = current + } else { + animateIn = true + activityIndicator = RadialStatusNode( + backgroundNodeColor: .clear, + enableBlur: false, + isPreview: false + ) + activityIndicator.transitionToState(.progress( + color: presentationData.theme.list.itemCheckColors.foregroundColor, + lineWidth: 2.0, + value: nil, + cancelEnabled: false, + animateRotation: true + )) + self.activityIndicator = activityIndicator + self.containerView.addSubview(activityIndicator.view) + } + + let activityIndicatorSize = CGSize(width: 18.0, height: 18.0) + let activityIndicatorFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - activityIndicatorSize.width) * 0.5), y: floor((innerSize.height - activityIndicatorSize.height) * 0.5) + UIScreenPixel), size: activityIndicatorSize) + if animateIn { + activityIndicator.view.frame = activityIndicatorFrame + transition.animateAlpha(view: activityIndicator.view, from: 0.0, to: 1.0) + transition.animateScale(view: activityIndicator.view, from: 0.001, to: 1.0) + } else { + transition.setFrame(view: activityIndicator.view, frame: activityIndicatorFrame) + } + } else { + if let activityIndicator = self.activityIndicator { + self.activityIndicator = nil + transition.setAlpha(view: activityIndicator.view, alpha: 0.0, completion: { [weak activityIndicator] _ in + activityIndicator?.view.removeFromSuperview() + }) + transition.setScale(view: activityIndicator.view, scale: 0.001) + } + } + } + + func updateGlobalRect(rect: CGRect, within containerSize: CGSize, transition: Transition) { + if let backgroundContent = self.backgroundContent { + backgroundContent.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.containerView.frame.minX, y: rect.minY + self.containerView.frame.minY), size: backgroundContent.bounds.size), within: containerSize, transition: transition.containedViewLayoutTransition) + } + } +} diff --git a/submodules/ChatTextLinkEditUI/BUILD b/submodules/ChatTextLinkEditUI/BUILD index e48e5676059..370f29c4ed5 100644 --- a/submodules/ChatTextLinkEditUI/BUILD +++ b/submodules/ChatTextLinkEditUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ChatTitleActivityNode/BUILD b/submodules/ChatTitleActivityNode/BUILD index e258d481497..de11bc9510b 100644 --- a/submodules/ChatTitleActivityNode/BUILD +++ b/submodules/ChatTitleActivityNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/CheckNode/BUILD b/submodules/CheckNode/BUILD index 5fd684a8d25..c4a60c7d616 100644 --- a/submodules/CheckNode/BUILD +++ b/submodules/CheckNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/CloudData/BUILD b/submodules/CloudData/BUILD index 874cb8a64f5..c7b5401efaf 100644 --- a/submodules/CloudData/BUILD +++ b/submodules/CloudData/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/CodeInputView/BUILD b/submodules/CodeInputView/BUILD index 072955c32d5..5c35b03565a 100644 --- a/submodules/CodeInputView/BUILD +++ b/submodules/CodeInputView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/ComponentFlow/BUILD b/submodules/ComponentFlow/BUILD index 92f3ebdd2a4..067aa5de32b 100644 --- a/submodules/ComponentFlow/BUILD +++ b/submodules/ComponentFlow/BUILD @@ -7,7 +7,7 @@ swift_library( "Source/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display" diff --git a/submodules/Components/ActivityIndicatorComponent/BUILD b/submodules/Components/ActivityIndicatorComponent/BUILD index 3f3ab06cbf0..e482f321d9a 100644 --- a/submodules/Components/ActivityIndicatorComponent/BUILD +++ b/submodules/Components/ActivityIndicatorComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/Components/AnimatedStickerComponent/BUILD b/submodules/Components/AnimatedStickerComponent/BUILD index 939314aa4e9..0da87c72d10 100644 --- a/submodules/Components/AnimatedStickerComponent/BUILD +++ b/submodules/Components/AnimatedStickerComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/Components/BalancedTextComponent/BUILD b/submodules/Components/BalancedTextComponent/BUILD index 35ba9d18325..f01b6e90a31 100644 --- a/submodules/Components/BalancedTextComponent/BUILD +++ b/submodules/Components/BalancedTextComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/BlurredBackgroundComponent/BUILD b/submodules/Components/BlurredBackgroundComponent/BUILD index 5474f19bf9f..414911a50fc 100644 --- a/submodules/Components/BlurredBackgroundComponent/BUILD +++ b/submodules/Components/BlurredBackgroundComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/BundleIconComponent/BUILD b/submodules/Components/BundleIconComponent/BUILD index 284b18fac65..575d5592a02 100644 --- a/submodules/Components/BundleIconComponent/BUILD +++ b/submodules/Components/BundleIconComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/Components/ComponentDisplayAdapters/BUILD b/submodules/Components/ComponentDisplayAdapters/BUILD index 50101fc7ad6..77ae9a52293 100644 --- a/submodules/Components/ComponentDisplayAdapters/BUILD +++ b/submodules/Components/ComponentDisplayAdapters/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/Forms/CreditCardInputComponent/BUILD b/submodules/Components/Forms/CreditCardInputComponent/BUILD index 3f2063bdbe8..be11341b6df 100644 --- a/submodules/Components/Forms/CreditCardInputComponent/BUILD +++ b/submodules/Components/Forms/CreditCardInputComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/Forms/PrefixSectionGroupComponent/BUILD b/submodules/Components/Forms/PrefixSectionGroupComponent/BUILD index 14dd8f6f1f3..1b17fd505e9 100644 --- a/submodules/Components/Forms/PrefixSectionGroupComponent/BUILD +++ b/submodules/Components/Forms/PrefixSectionGroupComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/Forms/TextInputComponent/BUILD b/submodules/Components/Forms/TextInputComponent/BUILD index 36410527b99..f9b226828a9 100644 --- a/submodules/Components/Forms/TextInputComponent/BUILD +++ b/submodules/Components/Forms/TextInputComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/HierarchyTrackingLayer/BUILD b/submodules/Components/HierarchyTrackingLayer/BUILD index 76bae62254d..b9277d99aab 100644 --- a/submodules/Components/HierarchyTrackingLayer/BUILD +++ b/submodules/Components/HierarchyTrackingLayer/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ ], diff --git a/submodules/Components/LottieAnimationComponent/BUILD b/submodules/Components/LottieAnimationComponent/BUILD index b5829424115..74e2b043c50 100644 --- a/submodules/Components/LottieAnimationComponent/BUILD +++ b/submodules/Components/LottieAnimationComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/Components/MetalImageView/BUILD b/submodules/Components/MetalImageView/BUILD index a9d08899a77..1008401547d 100644 --- a/submodules/Components/MetalImageView/BUILD +++ b/submodules/Components/MetalImageView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/MultilineTextComponent/BUILD b/submodules/Components/MultilineTextComponent/BUILD index 6d3a4983a34..d0e5f5f3cf6 100644 --- a/submodules/Components/MultilineTextComponent/BUILD +++ b/submodules/Components/MultilineTextComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/MultilineTextWithEntitiesComponent/BUILD b/submodules/Components/MultilineTextWithEntitiesComponent/BUILD index f6050aa2dce..ab73891342b 100644 --- a/submodules/Components/MultilineTextWithEntitiesComponent/BUILD +++ b/submodules/Components/MultilineTextWithEntitiesComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/PagerComponent/BUILD b/submodules/Components/PagerComponent/BUILD index 182a32f152d..fda346eb3cf 100644 --- a/submodules/Components/PagerComponent/BUILD +++ b/submodules/Components/PagerComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/ProgressIndicatorComponent/BUILD b/submodules/Components/ProgressIndicatorComponent/BUILD index df967f4f4f3..b74d13d204b 100644 --- a/submodules/Components/ProgressIndicatorComponent/BUILD +++ b/submodules/Components/ProgressIndicatorComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/Components/ReactionButtonListComponent/BUILD b/submodules/Components/ReactionButtonListComponent/BUILD index f8167464d8e..4738980ea9b 100644 --- a/submodules/Components/ReactionButtonListComponent/BUILD +++ b/submodules/Components/ReactionButtonListComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/ReactionImageComponent/BUILD b/submodules/Components/ReactionImageComponent/BUILD index 80ef60793d8..b979888868c 100644 --- a/submodules/Components/ReactionImageComponent/BUILD +++ b/submodules/Components/ReactionImageComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/ReactionListContextMenuContent/BUILD b/submodules/Components/ReactionListContextMenuContent/BUILD index 08714ce9a7b..b691d0a3e46 100644 --- a/submodules/Components/ReactionListContextMenuContent/BUILD +++ b/submodules/Components/ReactionListContextMenuContent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/SheetComponent/BUILD b/submodules/Components/SheetComponent/BUILD index 5d521914299..2c04e6db614 100644 --- a/submodules/Components/SheetComponent/BUILD +++ b/submodules/Components/SheetComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift index 2626cccf9d1..b3bf62bc1e9 100644 --- a/submodules/Components/SheetComponent/Sources/SheetComponent.swift +++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift @@ -62,21 +62,27 @@ public final class SheetComponent: Component { public let content: AnyComponent public let backgroundColor: BackgroundColor public let followContentSizeChanges: Bool + public let clipsContent: Bool public let externalState: ExternalState? public let animateOut: ActionSlot> + public let onPan: () -> Void public init( content: AnyComponent, backgroundColor: BackgroundColor, followContentSizeChanges: Bool = false, + clipsContent: Bool = false, externalState: ExternalState? = nil, - animateOut: ActionSlot> + animateOut: ActionSlot>, + onPan: @escaping () -> Void = {} ) { self.content = content self.backgroundColor = backgroundColor self.followContentSizeChanges = followContentSizeChanges + self.clipsContent = clipsContent self.externalState = externalState self.animateOut = animateOut + self.onPan = onPan } public static func ==(lhs: SheetComponent, rhs: SheetComponent) -> Bool { @@ -125,6 +131,8 @@ public final class SheetComponent: Component { return false } + private var component: SheetComponent? + private let dimView: UIView private let scrollView: ScrollView private let backgroundView: UIView @@ -196,6 +204,10 @@ public final class SheetComponent: Component { self.dismiss?(true) } + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.component?.onPan() + } + private var scrollingOut = false public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { let contentOffset = (scrollView.contentOffset.y + scrollView.contentInset.top - scrollView.contentSize.height) * -1.0 @@ -299,6 +311,7 @@ public final class SheetComponent: Component { } } + self.component = component self.currentHasInputHeight = sheetEnvironment.hasInputHeight switch component.backgroundColor { @@ -349,6 +362,7 @@ public final class SheetComponent: Component { if contentView.superview == nil { self.scrollView.addSubview(contentView) } + contentView.clipsToBounds = component.clipsContent if sheetEnvironment.isCentered { let y: CGFloat = floorToScreenPixels((availableSize.height - contentSize.height) / 2.0) transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) diff --git a/submodules/Components/SolidRoundedButtonComponent/BUILD b/submodules/Components/SolidRoundedButtonComponent/BUILD index 5f8cb511690..86428c388e6 100644 --- a/submodules/Components/SolidRoundedButtonComponent/BUILD +++ b/submodules/Components/SolidRoundedButtonComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Components/UndoPanelComponent/BUILD b/submodules/Components/UndoPanelComponent/BUILD index c8f46ab4966..513a641c3d4 100644 --- a/submodules/Components/UndoPanelComponent/BUILD +++ b/submodules/Components/UndoPanelComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/Components/ViewControllerComponent/BUILD b/submodules/Components/ViewControllerComponent/BUILD index ca5e4d38f07..fe18efe781b 100644 --- a/submodules/Components/ViewControllerComponent/BUILD +++ b/submodules/Components/ViewControllerComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/ComposePollUI/BUILD b/submodules/ComposePollUI/BUILD index 209d04ead71..1049b90a02a 100644 --- a/submodules/ComposePollUI/BUILD +++ b/submodules/ComposePollUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/ComposePollUI/Sources/ComposePollScreen.swift b/submodules/ComposePollUI/Sources/ComposePollScreen.swift index 6bfda6e7141..f8366a68d09 100644 --- a/submodules/ComposePollUI/Sources/ComposePollScreen.swift +++ b/submodules/ComposePollUI/Sources/ComposePollScreen.swift @@ -657,6 +657,7 @@ final class ComposePollScreenComponent: Component { }, assumeIsEditing: self.inputMediaNodeTargetTag === self.pollTextFieldTag, characterLimit: component.initialData.maxPollTextLength, + emptyLineHandling: .allowed, returnKeyAction: { [weak self] in guard let self else { return @@ -751,6 +752,7 @@ final class ComposePollScreenComponent: Component { }, assumeIsEditing: self.inputMediaNodeTargetTag === pollOption.textFieldTag, characterLimit: component.initialData.maxPollOptionLength, + emptyLineHandling: .notAllowed, returnKeyAction: { [weak self] in guard let self else { return @@ -1132,6 +1134,7 @@ final class ComposePollScreenComponent: Component { }, assumeIsEditing: self.inputMediaNodeTargetTag === self.quizAnswerTextInputTag, characterLimit: component.initialData.maxPollTextLength, + emptyLineHandling: .allowed, returnKeyAction: { [weak self] in guard let self else { return @@ -1564,7 +1567,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont public static func initialData(context: AccountContext) -> InitialData { return InitialData( - maxPollTextLength: Int(context.userLimits.maxCaptionLength), + maxPollTextLength: Int(255), maxPollOptionLength: 100 ) } diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index 5fa3f3b45a6..ff3f8cc6349 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -147,6 +147,7 @@ private func processPollText(_ text: String) -> String { } private final class CreatePollControllerArguments { + let context: AccountContext let updatePollText: (String) -> Void let updateOptionText: (Int, String, Bool) -> Void let moveToNextOption: (Int) -> Void @@ -163,7 +164,8 @@ private final class CreatePollControllerArguments { let solutionTextFocused: (Bool) -> Void let questionTextFocused: (Bool) -> Void - init(updatePollText: @escaping (String) -> Void, updateOptionText: @escaping (Int, String, Bool) -> Void, moveToNextOption: @escaping (Int) -> Void, moveToPreviousOption: @escaping (Int) -> Void, removeOption: @escaping (Int, Bool) -> Void, optionFocused: @escaping (Int, Bool) -> Void, setItemIdWithRevealedOptions: @escaping (Int?, Int?) -> Void, toggleOptionSelected: @escaping (Int) -> Void, updateAnonymous: @escaping (Bool) -> Void, updateMultipleChoice: @escaping (Bool) -> Void, displayMultipleChoiceDisabled: @escaping () -> Void, updateQuiz: @escaping (Bool) -> Void, updateSolutionText: @escaping (NSAttributedString) -> Void, solutionTextFocused: @escaping (Bool) -> Void, questionTextFocused: @escaping (Bool) -> Void) { + init(context: AccountContext, updatePollText: @escaping (String) -> Void, updateOptionText: @escaping (Int, String, Bool) -> Void, moveToNextOption: @escaping (Int) -> Void, moveToPreviousOption: @escaping (Int) -> Void, removeOption: @escaping (Int, Bool) -> Void, optionFocused: @escaping (Int, Bool) -> Void, setItemIdWithRevealedOptions: @escaping (Int?, Int?) -> Void, toggleOptionSelected: @escaping (Int) -> Void, updateAnonymous: @escaping (Bool) -> Void, updateMultipleChoice: @escaping (Bool) -> Void, displayMultipleChoiceDisabled: @escaping () -> Void, updateQuiz: @escaping (Bool) -> Void, updateSolutionText: @escaping (NSAttributedString) -> Void, solutionTextFocused: @escaping (Bool) -> Void, questionTextFocused: @escaping (Bool) -> Void) { + self.context = context self.updatePollText = updatePollText self.updateOptionText = updateOptionText self.moveToNextOption = moveToNextOption @@ -397,7 +399,7 @@ private enum CreatePollEntry: ItemListNodeEntry { case let .quizSolutionHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .quizSolutionText(placeholder, text): - return CreatePollTextInputItem(presentationData: presentationData, text: text.value, placeholder: placeholder, maxLength: CreatePollTextInputItemTextLimit(value: 200, display: true), sectionId: self.section, style: .blocks, textUpdated: { text in + return CreatePollTextInputItem(context: arguments.context, presentationData: presentationData, text: text.value, placeholder: placeholder, maxLength: CreatePollTextInputItemTextLimit(value: 200, display: true), sectionId: self.section, style: .blocks, textUpdated: { text in arguments.updateSolutionText(text) }, updatedFocus: { value in arguments.solutionTextFocused(value) @@ -538,6 +540,17 @@ private final class CreatePollContext: AttachmentMediaPickerContext { return .single(nil) } + var captionIsAboveMedia: Signal { + return .single(false) + } + + var hasCaption: Bool { + return false + } + + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { + } + public var loadingProgress: Signal { return .single(nil) } @@ -549,10 +562,10 @@ private final class CreatePollContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { } - func schedule() { + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { } func mainButtonAction() { @@ -653,7 +666,7 @@ private func legacyCreatePollController(context: AccountContext, updatedPresenta let updateAddressNameDisposable = MetaDisposable() actionsDisposable.add(updateAddressNameDisposable) - let arguments = CreatePollControllerArguments(updatePollText: { value in + let arguments = CreatePollControllerArguments(context: context, updatePollText: { value in updateState { state in var state = state state.focusOptionId = nil diff --git a/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift b/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift index 37106a74a6c..eef54017836 100644 --- a/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift +++ b/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift @@ -8,6 +8,7 @@ import ItemListUI import TextFormat import ObjCRuntimeUtils import TextInputMenu +import AccountContext public enum CreatePollTextInputItemTextLimitMode { case characters @@ -37,6 +38,7 @@ public struct ItemListMultilineInputInlineAction { } public class CreatePollTextInputItem: ListViewItem, ItemListItem { + let context: AccountContext let presentationData: ItemListPresentationData let text: NSAttributedString let placeholder: String @@ -55,7 +57,8 @@ public class CreatePollTextInputItem: ListViewItem, ItemListItem { let inlineAction: ItemListMultilineInputInlineAction? public let tag: ItemListItemTag? - public init(presentationData: ItemListPresentationData, text: NSAttributedString, placeholder: String, maxLength: CreatePollTextInputItemTextLimit?, sectionId: ItemListSectionId, style: ItemListStyle, capitalization: Bool = true, autocorrection: Bool = true, returnKeyType: UIReturnKeyType = .default, minimalHeight: CGFloat? = nil, textUpdated: @escaping (NSAttributedString) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> Void)? = nil, updatedFocus: ((Bool) -> Void)? = nil, tag: ItemListItemTag? = nil, action: (() -> Void)? = nil, inlineAction: ItemListMultilineInputInlineAction? = nil) { + public init(context: AccountContext, presentationData: ItemListPresentationData, text: NSAttributedString, placeholder: String, maxLength: CreatePollTextInputItemTextLimit?, sectionId: ItemListSectionId, style: ItemListStyle, capitalization: Bool = true, autocorrection: Bool = true, returnKeyType: UIReturnKeyType = .default, minimalHeight: CGFloat? = nil, textUpdated: @escaping (NSAttributedString) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> Void)? = nil, updatedFocus: ((Bool) -> Void)? = nil, tag: ItemListItemTag? = nil, action: (() -> Void)? = nil, inlineAction: ItemListMultilineInputInlineAction? = nil) { + self.context = context self.presentationData = presentationData self.text = text self.placeholder = placeholder @@ -228,7 +231,7 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe rightInset += inlineAction.icon.size.width + 8.0 } - let itemText = textAttributedStringForStateText(item.text, fontSize: 17.0, textColor: item.presentationData.theme.chat.inputPanel.primaryTextColor, accentTextColor: item.presentationData.theme.chat.inputPanel.panelControlAccentColor, writingDirection: nil, spoilersRevealed: false, availableEmojis: Set(), emojiViewProvider: nil) + let itemText = textAttributedStringForStateText(context: item.context, stateText: item.text, fontSize: 17.0, textColor: item.presentationData.theme.chat.inputPanel.primaryTextColor, accentTextColor: item.presentationData.theme.chat.inputPanel.panelControlAccentColor, writingDirection: nil, spoilersRevealed: false, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil) let measureText = NSMutableAttributedString(attributedString: itemText) let measureRawString = measureText.string if measureRawString.hasSuffix("\n") || measureRawString.isEmpty { @@ -294,11 +297,11 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe if let currentText = strongSelf.textNode.attributedText { if currentText.string != attributedText.string || updatedTheme != nil { strongSelf.textNode.attributedText = attributedText - refreshGenericTextInputAttributes(strongSelf.textNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil) + refreshGenericTextInputAttributes(context: item.context, textView: strongSelf.textNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil) } } else { strongSelf.textNode.attributedText = attributedText - refreshGenericTextInputAttributes(strongSelf.textNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil) + refreshGenericTextInputAttributes(context: item.context, textView: strongSelf.textNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil) } if strongSelf.backgroundNode.supernode == nil { @@ -557,14 +560,14 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe @objc func formatAttributesQuote(_ sender: Any) { self.inputMenu.back() if let item = self.item { - chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote)) + chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false)) } } @objc func formatAttributesCodeBlock(_ sender: Any) { self.inputMenu.back() if let item = self.item { - chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: nil))) + chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: nil), isCollapsed: false)) } } @@ -591,7 +594,7 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { if let item = self.item { if let _ = self.textNode.attributedText { - refreshGenericTextInputAttributes(editableTextNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil) + refreshGenericTextInputAttributes(context: item.context, textView: editableTextNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil) let updatedText = stateAttributedStringForText(self.textNode.attributedText!) item.textUpdated(updatedText) } else { @@ -621,7 +624,7 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe } refreshChatTextInputTypingAttributes(editableTextNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0) - refreshGenericTextInputAttributes(editableTextNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil) + refreshGenericTextInputAttributes(context: item.context, textView: editableTextNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil) } } @@ -672,7 +675,7 @@ private func chatTextInputAddFormattingAttribute(item: CreatePollTextInputItem, textNode.selectedRange = nsRange refreshChatTextInputTypingAttributes(textNode.textView, theme: theme, baseFontSize: 17.0) - refreshGenericTextInputAttributes(textNode.textView, theme: theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil) + refreshGenericTextInputAttributes(context: item.context, textView: textNode.textView, theme: theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil) let updatedText = stateAttributedStringForText(textNode.attributedText!) item.textUpdated(updatedText) diff --git a/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift b/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift index c209cc6de06..42998dbb3a1 100644 --- a/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift +++ b/submodules/ComposePollUI/Sources/ListComposePollOptionComponent.swift @@ -75,6 +75,7 @@ public final class ListComposePollOptionComponent: Component { public let resetText: ResetText? public let assumeIsEditing: Bool public let characterLimit: Int? + public let emptyLineHandling: TextFieldComponent.EmptyLineHandling public let returnKeyAction: (() -> Void)? public let backspaceKeyAction: (() -> Void)? public let selection: Selection? @@ -90,6 +91,7 @@ public final class ListComposePollOptionComponent: Component { resetText: ResetText? = nil, assumeIsEditing: Bool = false, characterLimit: Int, + emptyLineHandling: TextFieldComponent.EmptyLineHandling, returnKeyAction: (() -> Void)?, backspaceKeyAction: (() -> Void)?, selection: Selection?, @@ -104,6 +106,7 @@ public final class ListComposePollOptionComponent: Component { self.resetText = resetText self.assumeIsEditing = assumeIsEditing self.characterLimit = characterLimit + self.emptyLineHandling = emptyLineHandling self.returnKeyAction = returnKeyAction self.backspaceKeyAction = backspaceKeyAction self.selection = selection @@ -134,6 +137,9 @@ public final class ListComposePollOptionComponent: Component { if lhs.characterLimit != rhs.characterLimit { return false } + if lhs.emptyLineHandling != rhs.emptyLineHandling { + return false + } if lhs.selection != rhs.selection { return false } @@ -317,6 +323,7 @@ public final class ListComposePollOptionComponent: Component { externalState: component.externalState ?? TextFieldComponent.ExternalState(), fontSize: 17.0, textColor: component.theme.list.itemPrimaryTextColor, + accentColor: component.theme.list.itemPrimaryTextColor, insets: UIEdgeInsets(top: verticalInset, left: 8.0, bottom: verticalInset, right: 8.0), hideKeyboard: component.inputMode == .emoji, customInputView: nil, @@ -325,7 +332,7 @@ public final class ListComposePollOptionComponent: Component { }, isOneLineWhenUnfocused: false, characterLimit: component.characterLimit, - emptyLineHandling: .notAllowed, + emptyLineHandling: component.emptyLineHandling, formatMenuAvailability: .none, returnKeyType: .next, lockedFormatAction: { diff --git a/submodules/ConfettiEffect/BUILD b/submodules/ConfettiEffect/BUILD index 9acf41b4d6f..5d1cc9f7dc5 100644 --- a/submodules/ConfettiEffect/BUILD +++ b/submodules/ConfettiEffect/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/ConfettiEffect/Sources/ConfettiView.swift b/submodules/ConfettiEffect/Sources/ConfettiView.swift index fb600452bca..961b1d833f4 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/ContactListUI/BUILD b/submodules/ContactListUI/BUILD index ef6c5d49a2a..137dace9e04 100644 --- a/submodules/ContactListUI/BUILD +++ b/submodules/ContactListUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ContactListUI/Sources/ContactContextMenus.swift b/submodules/ContactListUI/Sources/ContactContextMenus.swift index 8e8815dc86f..2a04b9af4cb 100644 --- a/submodules/ContactListUI/Sources/ContactContextMenus.swift +++ b/submodules/ContactListUI/Sources/ContactContextMenus.swift @@ -34,7 +34,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con items.append(.action(ContextMenuActionItem(text: strings.StoryFeed_ContextOpenProfile, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { let _ = (context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) ) diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 593a022af4e..1204cd5b396 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -23,6 +23,7 @@ import StoryContainerScreen import ChatListHeaderComponent import TelegramIntents import UndoUI +import ShareController private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController @@ -309,7 +310,7 @@ public class ContactsController: ViewController { guard let strongSelf = self, let value = value else { return } - (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .vcard(nil, id, value), completed: nil, cancelled: nil), completion: { [weak self] in + (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, id, value), completed: nil, cancelled: nil), completion: { [weak self] in if let strongSelf = self { strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) } @@ -741,7 +742,7 @@ public class ContactsController: ViewController { case .allowed: let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in guard let strongSelf = self else { return } @@ -755,7 +756,7 @@ public class ContactsController: ViewController { } } else { if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) + navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) } } }), completed: nil, cancelled: nil)) @@ -778,7 +779,7 @@ public class ContactsController: ViewController { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Contacts_AddContact, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let strongSelf = self else { return } @@ -789,7 +790,7 @@ public class ContactsController: ViewController { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Contacts_AddPeopleNearby, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Contact List/Context Menu/PeopleNearby"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let strongSelf = self else { return } diff --git a/submodules/ContactsPeerItem/BUILD b/submodules/ContactsPeerItem/BUILD index 921376ec475..97db8afb2fe 100644 --- a/submodules/ContactsPeerItem/BUILD +++ b/submodules/ContactsPeerItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index 61ed16b0af9..4fb300ce218 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -67,6 +67,7 @@ public struct ContactsPeerItemEditing: Equatable { public enum ContactsPeerItemPeerMode: Equatable { case generalSearch(isSavedMessages: Bool) case peer + case memberList } public enum ContactsPeerItemBadgeType { @@ -174,7 +175,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { let options: [ItemListPeerItemRevealOption] let additionalActions: [ContactsPeerItemAction] let actionIcon: ContactsPeerItemActionIcon - let action: (ContactsPeerItemPeer) -> Void + let action: ((ContactsPeerItemPeer) -> Void)? let disabledAction: ((ContactsPeerItemPeer) -> Void)? let setPeerIdWithRevealedOptions: ((EnginePeer.Id?, EnginePeer.Id?) -> Void)? let deletePeer: ((EnginePeer.Id) -> Void)? @@ -214,7 +215,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { actionIcon: ContactsPeerItemActionIcon = .none, index: SortIndex?, header: ListViewItemHeader?, - action: @escaping (ContactsPeerItemPeer) -> Void, + action: ((ContactsPeerItemPeer) -> Void)?, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((EnginePeer.Id?, EnginePeer.Id?) -> Void)? = nil, deletePeer: ((EnginePeer.Id) -> Void)? = nil, @@ -250,7 +251,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { self.deletePeer = deletePeer self.header = header self.itemHighlighting = itemHighlighting - self.selectable = enabled || disabledAction != nil + self.selectable = (enabled && action != nil) || disabledAction != nil self.contextAction = contextAction self.arrowAction = arrowAction self.animationCache = animationCache @@ -349,7 +350,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { public func selected(listView: ListView) { if self.enabled { - self.action(self.peer) + self.action?(self.peer) } else { listView.clearHighlightAnimated(true) self.disabledAction?(self.peer) @@ -748,7 +749,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { var verifiedIcon: EmojiStatusComponent.Content? switch item.peer { case let .peer(peer, _): - if let peer = peer, peer.id != item.context.account.peerId { + if let peer = peer, (peer.id != item.context.account.peerId || item.peerMode == .memberList) { if peer.isScam { credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased()) } else if peer.isFake { @@ -1409,7 +1410,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { actionButton.action?(item.peer, sourceNode, gesture) } let moreButtonSize = moreButtonNode.measure(CGSize(width: 100.0, height: nodeLayout.contentSize.height)) - moreButtonNode.frame = CGRect(origin: CGPoint(x: revealOffset + params.width - params.rightInset - 18.0 - moreButtonSize.width, y:floor((nodeLayout.contentSize.height - moreButtonSize.height) / 2.0)), size: moreButtonSize) + moreButtonNode.frame = CGRect(origin: CGPoint(x: revealOffset + params.width - params.rightInset - 21.0 - moreButtonSize.width, y:floor((nodeLayout.contentSize.height - moreButtonSize.height) / 2.0)), size: moreButtonSize) } else if let actionButtons = actionButtons { if strongSelf.actionButtonNodes == nil { var actionButtonNodes: [HighlightableButtonNode] = [] diff --git a/submodules/ContextUI/BUILD b/submodules/ContextUI/BUILD index ad064177f8f..661391694ce 100644 --- a/submodules/ContextUI/BUILD +++ b/submodules/ContextUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ContextUI/Sources/ContextActionNode.swift b/submodules/ContextUI/Sources/ContextActionNode.swift index 4aa8dd42fe9..e0ab2041901 100644 --- a/submodules/ContextUI/Sources/ContextActionNode.swift +++ b/submodules/ContextUI/Sources/ContextActionNode.swift @@ -121,6 +121,19 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol { statusNode.attributedText = NSAttributedString(string: value, font: subtitleFont, textColor: presentationData.theme.contextMenu.secondaryColor) statusNode.maximumNumberOfLines = 1 self.statusNode = statusNode + case let .secondLineWithAttributedValue(value): + self.textNode.maximumNumberOfLines = 1 + let statusNode = ImmediateTextNode() + statusNode.isAccessibilityElement = false + statusNode.isUserInteractionEnabled = false + statusNode.displaysAsynchronously = false + + let mutableString = value.mutableCopy() as! NSMutableAttributedString + mutableString.addAttribute(.foregroundColor, value: presentationData.theme.contextMenu.secondaryColor, range: NSRange(location: 0, length: mutableString.length)) + mutableString.addAttribute(.font, value: subtitleFont, range: NSRange(location: 0, length: mutableString.length)) + statusNode.attributedText = mutableString + statusNode.maximumNumberOfLines = 1 + self.statusNode = statusNode case .multiline: self.textNode.maximumNumberOfLines = 0 self.statusNode = nil @@ -350,10 +363,15 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol { self.textNode.attributedText = NSAttributedString(string: self.action.text, font: titleFont, textColor: textColor) + let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0) switch self.action.textLayout { case let .secondLineWithValue(value): - let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0) self.statusNode?.attributedText = NSAttributedString(string: value, font: subtitleFont, textColor: presentationData.theme.contextMenu.secondaryColor) + case let .secondLineWithAttributedValue(value): + let mutableString = value.mutableCopy() as! NSMutableAttributedString + mutableString.addAttribute(.foregroundColor, value: presentationData.theme.contextMenu.secondaryColor, range: NSRange(location: 0, length: mutableString.length)) + mutableString.addAttribute(.font, value: subtitleFont, range: NSRange(location: 0, length: mutableString.length)) + self.statusNode?.attributedText = mutableString default: break } diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index d58400ca178..acf2dcbb5f4 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -36,6 +36,7 @@ public enum ContextMenuActionItemTextLayout { case singleLine case twoLinesMax case secondLineWithValue(String) + case secondLineWithAttributedValue(NSAttributedString) case multiline } @@ -101,11 +102,11 @@ public struct ContextMenuActionBadge: Equatable { public final class ContextMenuActionItem { public final class Action { - public let controller: ContextControllerProtocol + public let controller: ContextControllerProtocol? public let dismissWithResult: (ContextMenuActionResult) -> Void public let updateAction: (AnyHashable, ContextMenuActionItem) -> Void - init(controller: ContextControllerProtocol, dismissWithResult: @escaping (ContextMenuActionResult) -> Void, updateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void) { + init(controller: ContextControllerProtocol?, dismissWithResult: @escaping (ContextMenuActionResult) -> Void, updateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void) { self.controller = controller self.dismissWithResult = dismissWithResult self.updateAction = updateAction @@ -155,7 +156,7 @@ public final class ContextMenuActionItem { iconAnimation: IconAnimation? = nil, textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil }, textLinkAction: @escaping () -> Void = {}, - action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)? + action: ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)? ) { self.init( id: id, @@ -2150,6 +2151,7 @@ public protocol ContextExtractedContentSource: AnyObject { var initialAppearanceOffset: CGPoint { get } var centerVertically: Bool { get } var keepInPlace: Bool { get } + var adjustContentHorizontally: Bool { get } var adjustContentForSideInset: Bool { get } var ignoreContentTouches: Bool { get } var blurBackground: Bool { get } @@ -2170,6 +2172,10 @@ public extension ContextExtractedContentSource { return false } + var adjustContentHorizontally: Bool { + return false + } + var adjustContentForSideInset: Bool { return false } diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index a4cd43a079b..6666b5da871 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -175,12 +175,8 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking } @objc private func pressed() { - guard let controller = self.getController() else { - return - } - self.item.action?(ContextMenuActionItem.Action( - controller: controller, + controller: self.getController(), dismissWithResult: { [weak self] result in guard let strongSelf = self else { return @@ -232,21 +228,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking let iconSpacing: CGFloat = 8.0 self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor - - var subtitle: String? - switch self.item.textLayout { - case .singleLine: - self.titleLabelNode.maximumNumberOfLines = 1 - case .twoLinesMax: - self.titleLabelNode.maximumNumberOfLines = 2 - case let .secondLineWithValue(subtitleValue): - self.titleLabelNode.maximumNumberOfLines = 1 - subtitle = subtitleValue - case .multiline: - self.titleLabelNode.maximumNumberOfLines = 0 - self.titleLabelNode.lineSpacing = 0.1 - } - + var forcedHeight: CGFloat? var titleVerticalOffset: CGFloat? let titleFont: UIFont @@ -269,6 +251,30 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) let subtitleColor = presentationData.theme.contextMenu.secondaryColor + var subtitle: NSAttributedString? + switch self.item.textLayout { + case .singleLine: + self.titleLabelNode.maximumNumberOfLines = 1 + case .twoLinesMax: + self.titleLabelNode.maximumNumberOfLines = 2 + case let .secondLineWithValue(subtitleValue): + self.titleLabelNode.maximumNumberOfLines = 1 + subtitle = NSAttributedString( + string: subtitleValue, + font: subtitleFont, + textColor: subtitleColor + ) + case let .secondLineWithAttributedValue(subtitleValue): + self.titleLabelNode.maximumNumberOfLines = 1 + let mutableString = subtitleValue.mutableCopy() as! NSMutableAttributedString + mutableString.addAttribute(.foregroundColor, value: subtitleColor, range: NSRange(location: 0, length: mutableString.length)) + mutableString.addAttribute(.font, value: subtitleFont, range: NSRange(location: 0, length: mutableString.length)) + subtitle = mutableString + case .multiline: + self.titleLabelNode.maximumNumberOfLines = 0 + self.titleLabelNode.lineSpacing = 0.1 + } + let titleColor: UIColor switch self.item.textColor { case .primary: @@ -312,13 +318,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking self.titleLabelNode.isUserInteractionEnabled = self.titleLabelNode.tapAttributeAction != nil && self.item.action == nil - self.subtitleNode.attributedText = subtitle.flatMap { subtitle in - return NSAttributedString( - string: subtitle, - font: subtitleFont, - textColor: subtitleColor - ) - } + self.subtitleNode.attributedText = subtitle var iconSize: CGSize? if let iconSource = self.item.iconSource { @@ -668,7 +668,7 @@ private final class ContextControllerActionsListCustomItemNode: ASDisplayNode, C } } -final class ContextControllerActionsListStackItem: ContextControllerActionsStackItem { +public final class ContextControllerActionsListStackItem: ContextControllerActionsStackItem { final class Node: ASDisplayNode, ContextControllerActionsStackItemNode { private final class Item { let node: ContextControllerActionsListItemNode @@ -932,14 +932,14 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack } } - let id: AnyHashable? - let items: [ContextMenuItem] - let reactionItems: ContextControllerReactionItems? - let tip: ContextController.Tip? - let tipSignal: Signal? - let dismissed: (() -> Void)? + public let id: AnyHashable? + public let items: [ContextMenuItem] + public let reactionItems: ContextControllerReactionItems? + public let tip: ContextController.Tip? + public let tipSignal: Signal? + public let dismissed: (() -> Void)? - init( + public init( id: AnyHashable?, items: [ContextMenuItem], reactionItems: ContextControllerReactionItems?, @@ -955,7 +955,7 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack self.dismissed = dismissed } - func node( + public func node( getController: @escaping () -> ContextControllerProtocol?, requestDismiss: @escaping (ContextMenuActionResult) -> Void, requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void, @@ -1093,8 +1093,8 @@ func makeContextControllerActionsStackItem(items: ContextController.Items) -> [C } } -final class ContextControllerActionsStackNode: ASDisplayNode { - enum Presentation { +public final class ContextControllerActionsStackNode: ASDisplayNode { + public enum Presentation { case modal case inline case additional @@ -1371,19 +1371,19 @@ final class ContextControllerActionsStackNode: ASDisplayNode { private var selectionPanGesture: UIPanGestureRecognizer? - var topReactionItems: ContextControllerReactionItems? { + public var topReactionItems: ContextControllerReactionItems? { return self.itemContainers.last?.reactionItems } - var topPositionLock: CGFloat? { + public var topPositionLock: CGFloat? { return self.itemContainers.last?.positionLock } - var storedScrollingState: CGFloat? { + public var storedScrollingState: CGFloat? { return self.itemContainers.last?.storedScrollingState } - init( + public init( getController: @escaping () -> ContextControllerProtocol?, requestDismiss: @escaping (ContextMenuActionResult) -> Void, requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void @@ -1433,7 +1433,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { } } - func replace(item: ContextControllerActionsStackItem, animated: Bool?) { + public func replace(item: ContextControllerActionsStackItem, animated: Bool?) { if let item = item as? ContextControllerActionsListStackItem, let topContainer = self.itemContainers.first, let topItem = topContainer.item as? ContextControllerActionsListStackItem, let topId = topItem.id, let id = item.id, topId == id, item.items.count == topItem.items.count { if let topNode = topContainer.node as? ContextControllerActionsListStackItem.Node { var matches = true @@ -1490,7 +1490,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { self.push(item: item, currentScrollingState: nil, positionLock: nil, animated: resolvedAnimated) } - func push(item: ContextControllerActionsStackItem, currentScrollingState: CGFloat?, positionLock: CGFloat?, animated: Bool) { + public func push(item: ContextControllerActionsStackItem, currentScrollingState: CGFloat?, positionLock: CGFloat?, animated: Bool) { if let itemContainer = self.itemContainers.last { itemContainer.storedScrollingState = currentScrollingState } @@ -1524,11 +1524,11 @@ final class ContextControllerActionsStackNode: ASDisplayNode { self.requestUpdate(transition) } - func clearStoredScrollingState() { + public func clearStoredScrollingState() { self.itemContainers.last?.storedScrollingState = nil } - func pop() { + public func pop() { if self.itemContainers.count == 1 { //dismiss } else { @@ -1545,7 +1545,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { self.requestUpdate(transition) } - func update( + public func update( presentationData: PresentationData, constrainedSize: CGSize, presentation: Presentation, @@ -1769,37 +1769,37 @@ final class ContextControllerActionsStackNode: ASDisplayNode { return CGSize(width: topItemWidth, height: topItemSize.height) } - func highlightGestureMoved(location: CGPoint) { + public func highlightGestureMoved(location: CGPoint) { if let topItemContainer = self.itemContainers.last { topItemContainer.highlightGestureMoved(location: self.view.convert(location, to: topItemContainer.view)) } } - func highlightGestureFinished(performAction: Bool) { + public func highlightGestureFinished(performAction: Bool) { if let topItemContainer = self.itemContainers.last { topItemContainer.highlightGestureFinished(performAction: performAction) } } - func decreaseHighlightedIndex() { + public func decreaseHighlightedIndex() { if let topItemContainer = self.itemContainers.last { topItemContainer.decreaseHighlightedIndex() } } - func increaseHighlightedIndex() { + public func increaseHighlightedIndex() { if let topItemContainer = self.itemContainers.last { topItemContainer.increaseHighlightedIndex() } } - func updatePanSelection(isEnabled: Bool) { + public func updatePanSelection(isEnabled: Bool) { if let selectionPanGesture = self.selectionPanGesture { selectionPanGesture.isEnabled = isEnabled } } - func animateIn() { + public func animateIn() { for itemContainer in self.itemContainers { if let tipNode = itemContainer.tipNode { tipNode.animateIn() diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index bd65f9abdf5..0faa57a5958 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -451,7 +451,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo reactionContextNode.updateIsIntersectingContent(isIntersectingContent: isIntersectingContent, transition: .animated(duration: 0.25, curve: .easeInOut)) if !reactionContextNode.isExpanded && reactionContextNode.canBeExpanded { - if topOverscroll > 30.0 && self.scroller.isDragging { + if topOverscroll > 30.0 && self.scroller.isTracking { self.scroller.panGestureRecognizer.state = .cancelled reactionContextNode.expand() } else { @@ -1064,6 +1064,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let contentNode = itemContentNode { var contentFrame = CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: contentNode.containingItem.view.bounds.size) if case let .extracted(extracted) = self.source { + if extracted.adjustContentHorizontally { + contentFrame.origin.x = combinedActionsFrame.minX + } if extracted.centerVertically { if combinedActionsFrame.height.isZero { contentFrame.origin.y = floorToScreenPixels((layout.size.height - contentFrame.height) / 2.0) @@ -1172,7 +1175,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX let contentWidth = contentNode.containingItem.view.bounds.size.width let contentHeight = contentNode.containingItem.view.bounds.size.height - if case let .extracted(extracted) = self.source, extracted.centerVertically { + if case let .extracted(extracted) = self.source, extracted.adjustContentHorizontally { + let fixedContentX = self.actionsContainerNode.frame.minX + animationInContentXDistance = fixedContentX - contentX + } else if case let .extracted(extracted) = self.source, extracted.centerVertically { if actionsSize.height.isZero { var initialContentRect = contentRect initialContentRect.origin.y += extracted.initialAppearanceOffset.y @@ -1441,7 +1447,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo var animationInContentXDistance: CGFloat = 0.0 let contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX let contentWidth = contentNode.containingItem.view.bounds.size.width - if case let .extracted(extracted) = self.source, extracted.centerVertically { + if case let .extracted(extracted) = self.source, extracted.adjustContentHorizontally { + let fixedContentX = self.actionsContainerNode.frame.minX + animationInContentXDistance = contentX - fixedContentX + } else if case let .extracted(extracted) = self.source, extracted.centerVertically { if actionsSize.height.isZero { // let fixedContentY = floorToScreenPixels((layout.size.height - contentHeight) / 2.0) animationInContentYDistance = 0.0 //contentY - fixedContentY diff --git a/submodules/CounterControllerTitleView/BUILD b/submodules/CounterControllerTitleView/BUILD index f63f0315bbd..54fe28f76e8 100644 --- a/submodules/CounterControllerTitleView/BUILD +++ b/submodules/CounterControllerTitleView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/CountrySelectionUI/BUILD b/submodules/CountrySelectionUI/BUILD index 1104b6f78c5..e3ad7ddf187 100644 --- a/submodules/CountrySelectionUI/BUILD +++ b/submodules/CountrySelectionUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/DatePickerNode/BUILD b/submodules/DatePickerNode/BUILD index 02d1f00f53f..49b58c2aa6e 100644 --- a/submodules/DatePickerNode/BUILD +++ b/submodules/DatePickerNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/DateSelectionUI/BUILD b/submodules/DateSelectionUI/BUILD index be8c15b5082..02161cd7780 100644 --- a/submodules/DateSelectionUI/BUILD +++ b/submodules/DateSelectionUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/DebugSettingsUI/BUILD b/submodules/DebugSettingsUI/BUILD index 38736604aeb..86b9cb4fdd8 100644 --- a/submodules/DebugSettingsUI/BUILD +++ b/submodules/DebugSettingsUI/BUILD @@ -9,7 +9,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index b36d56889e1..199cc78cc32 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -306,7 +306,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { } else { UIPasteboard.general.setData(data, forPasteboardType: dataType) } - context.sharedContext.openResolvedUrl(.importStickers, context: context, urlContext: .generic, navigationController: arguments.getNavigationController(), forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { c, a in arguments.presentController(c, a as? ViewControllerPresentationArguments) }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) + context.sharedContext.openResolvedUrl(.importStickers, context: context, urlContext: .generic, navigationController: arguments.getNavigationController(), forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { c, a in arguments.presentController(c, a as? ViewControllerPresentationArguments) }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) } }) case .sendLogs: diff --git a/submodules/DeleteChatPeerActionSheetItem/BUILD b/submodules/DeleteChatPeerActionSheetItem/BUILD index 40f03aff214..96a19db8bc1 100644 --- a/submodules/DeleteChatPeerActionSheetItem/BUILD +++ b/submodules/DeleteChatPeerActionSheetItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/DeviceAccess/BUILD b/submodules/DeviceAccess/BUILD index 83e31b8d67b..45e8da1bc32 100644 --- a/submodules/DeviceAccess/BUILD +++ b/submodules/DeviceAccess/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/DeviceLocationManager/BUILD b/submodules/DeviceLocationManager/BUILD index 8635180aed0..8504bda9254 100644 --- a/submodules/DeviceLocationManager/BUILD +++ b/submodules/DeviceLocationManager/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/DirectMediaImageCache/BUILD b/submodules/DirectMediaImageCache/BUILD index fea404afaa6..6ae97d1ec5b 100644 --- a/submodules/DirectMediaImageCache/BUILD +++ b/submodules/DirectMediaImageCache/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/DirectionalPanGesture/BUILD b/submodules/DirectionalPanGesture/BUILD index e0b8dd6c1f2..ac6f12e3cda 100644 --- a/submodules/DirectionalPanGesture/BUILD +++ b/submodules/DirectionalPanGesture/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/Display/BUILD b/submodules/Display/BUILD index 8e56c1d1ec1..79d4cea7e18 100644 --- a/submodules/Display/BUILD +++ b/submodules/Display/BUILD @@ -7,7 +7,7 @@ swift_library( "Source/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ObjCRuntimeUtils:ObjCRuntimeUtils", diff --git a/submodules/Display/Source/AlertControllerNode.swift b/submodules/Display/Source/AlertControllerNode.swift index 71ea50c34b1..8f4970eaef1 100644 --- a/submodules/Display/Source/AlertControllerNode.swift +++ b/submodules/Display/Source/AlertControllerNode.swift @@ -13,7 +13,8 @@ final class AlertControllerNode: ASDisplayNode { private let rightDimView: UIView private let containerNode: ASDisplayNode - private let effectNode: ASDisplayNode +// private let effectNode: ASDisplayNode + private let effectView: UIVisualEffectView private let backgroundNode: ASDisplayNode private let contentNode: AlertContentNode private let allowInputInset: Bool @@ -51,9 +52,11 @@ final class AlertControllerNode: ASDisplayNode { self.backgroundNode = ASDisplayNode() self.backgroundNode.backgroundColor = theme.backgroundColor - self.effectNode = ASDisplayNode(viewBlock: { - return UIVisualEffectView(effect: UIBlurEffect(style: theme.backgroundType == .light ? .light : .dark)) - }) +// self.effectNode = ASDisplayNode(viewBlock: { +// return UIVisualEffectView(effect: UIBlurEffect(style: theme.backgroundType == .light ? .light : .dark)) +// }) + + self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: theme.backgroundType == .light ? .light : .dark)) self.contentNode = contentNode @@ -66,7 +69,8 @@ final class AlertControllerNode: ASDisplayNode { self.dimContainerView.addSubview(self.leftDimView) self.dimContainerView.addSubview(self.rightDimView) - self.containerNode.addSubnode(self.effectNode) + self.containerNode.view.addSubview(self.effectView) +// self.containerNode.addSubnode(self.effectNode) self.containerNode.addSubnode(self.backgroundNode) self.containerNode.addSubnode(self.contentNode) self.addSubnode(self.containerNode) @@ -100,9 +104,7 @@ final class AlertControllerNode: ASDisplayNode { } func updateTheme(_ theme: AlertControllerTheme) { - if let effectView = self.effectNode.view as? UIVisualEffectView { - effectView.effect = UIBlurEffect(style: theme.backgroundType == .light ? .light : .dark) - } + self.effectView.effect = UIBlurEffect(style: theme.backgroundType == .light ? .light : .dark) self.backgroundNode.backgroundColor = theme.backgroundColor self.contentNode.updateTheme(theme) } @@ -186,7 +188,10 @@ final class AlertControllerNode: ASDisplayNode { transition.updateFrame(view: self.rightDimView, frame: CGRect(origin: CGPoint(x: containerFrame.maxX, y: containerFrame.minY), size: CGSize(width: layout.size.width - containerFrame.maxX + outerEdge, height: containerFrame.height))) transition.updateFrame(node: self.containerNode, frame: containerFrame) - transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: containerFrame.size)) + transition.animateView { + self.effectView.frame = CGRect(origin: CGPoint(), size: containerFrame.size) + } +// transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: containerFrame.size)) transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: containerFrame.size)) transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: containerFrame.size)) } diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 86fd7ffcc1a..c74db47d293 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -1672,6 +1672,27 @@ public extension ContainedViewLayoutTransition { ) } } + + func animateContents(layer: CALayer, from fromContents: Any) { + guard case let .animated(duration, curve) = self else { + return + } + guard let contents = layer.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID else { + return + } + guard CFGetTypeID(fromContents as CFTypeRef) == CGImage.typeID else { + return + } + + let contentsImage = contents as! CGImage + let fromContentsImage = fromContents as! CGImage + + if contentsImage === fromContentsImage { + return + } + + layer.animate(from: fromContentsImage, to: contentsImage, keyPath: "contents", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false) + } } public struct CombinedTransition { @@ -1822,6 +1843,8 @@ public protocol ControlledTransitionAnimator: AnyObject { func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)?) func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)?) func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)?) + func updateTransform(layer: CALayer, transform: CATransform3D, completion: ((Bool) -> Void)?) + func updateBackgroundColor(layer: CALayer, color: UIColor, completion: ((Bool) -> Void)?) } protocol AnyValueProviding { @@ -1967,6 +1990,83 @@ extension CGRect: AnyValueProviding { } } +extension CATransform3D: Equatable { + public static func ==(lhs: CATransform3D, rhs: CATransform3D) -> Bool { + return CATransform3DEqualToTransform(lhs, rhs) + } +} + +extension CATransform3D: AnyValueProviding { + func interpolate(with other: CATransform3D, fraction: CGFloat) -> CATransform3D { + return CATransform3D( + m11: self.m11.interpolate(with: other.m11, fraction: fraction), + m12: self.m12.interpolate(with: other.m12, fraction: fraction), + m13: self.m13.interpolate(with: other.m13, fraction: fraction), + m14: self.m14.interpolate(with: other.m14, fraction: fraction), + m21: self.m21.interpolate(with: other.m21, fraction: fraction), + m22: self.m22.interpolate(with: other.m22, fraction: fraction), + m23: self.m23.interpolate(with: other.m23, fraction: fraction), + m24: self.m24.interpolate(with: other.m24, fraction: fraction), + m31: self.m31.interpolate(with: other.m31, fraction: fraction), + m32: self.m32.interpolate(with: other.m32, fraction: fraction), + m33: self.m33.interpolate(with: other.m33, fraction: fraction), + m34: self.m34.interpolate(with: other.m34, fraction: fraction), + m41: self.m41.interpolate(with: other.m41, fraction: fraction), + m42: self.m42.interpolate(with: other.m42, fraction: fraction), + m43: self.m43.interpolate(with: other.m43, fraction: fraction), + m44: self.m44.interpolate(with: other.m44, fraction: fraction) + ) + } + + var anyValue: ControlledTransitionProperty.AnyValue { + return ControlledTransitionProperty.AnyValue( + value: self, + nsValue: NSValue(caTransform3D: self), + stringValue: { "\(self)" }, + isEqual: { other in + if let otherValue = other.value as? CATransform3D { + return self == otherValue + } else { + return false + } + }, + interpolate: { other, fraction in + guard let otherValue = other.value as? CATransform3D else { + preconditionFailure() + } + return self.interpolate(with: otherValue, fraction: fraction).anyValue + } + ) + } +} + +extension CGColor: AnyValueProviding { + func interpolate(with other: CGColor, fraction: CGFloat) -> CGColor { + return UIColor(cgColor: self).mixedWith(UIColor(cgColor: other), alpha: fraction).cgColor + } + + var anyValue: ControlledTransitionProperty.AnyValue { + return ControlledTransitionProperty.AnyValue( + value: self, + nsValue: self, + stringValue: { "\(self)" }, + isEqual: { other in + if CFGetTypeID(other.value as CFTypeRef) == CGColor.typeID { + return self == (other.value as! CGColor) + } else { + return false + } + }, + interpolate: { other, fraction in + guard CFGetTypeID(other.value as CFTypeRef) == CGColor.typeID else { + preconditionFailure() + } + return self.interpolate(with: other.value as! CGColor, fraction: fraction).anyValue + } + ) + } +} + final class ControlledTransitionProperty { final class AnyValue: Equatable, CustomStringConvertible { let value: Any @@ -2232,6 +2332,76 @@ public final class ControlledTransition { self.updateBounds(layer: layer, bounds: CGRect(origin: CGPoint(), size: frame.size), completion: nil) } + public func updateTransform(layer: CALayer, transform: CATransform3D, completion: ((Bool) -> Void)?) { + if layer.transform == transform { + return + } + let fromValue: CATransform3D + if let animationKeys = layer.animationKeys(), animationKeys.contains(where: { key in + guard let animation = layer.animation(forKey: key) as? CAPropertyAnimation else { + return false + } + if animation.keyPath == "transform" { + return true + } else { + return false + } + }) { + fromValue = layer.presentation()?.transform ?? layer.transform + } else { + fromValue = layer.transform + } + layer.transform = transform + self.add(animation: ControlledTransitionProperty( + layer: layer, + path: "transform", + fromValue: fromValue, + toValue: transform, + completion: completion + )) + } + + public func updateBackgroundColor(layer: CALayer, color: UIColor, completion: ((Bool) -> Void)?) { + if let currentColor = layer.backgroundColor, currentColor == color.cgColor { + if let completion = completion { + completion(true) + } + return + } + + let fromValue: CGColor? + if let animationKeys = layer.animationKeys(), animationKeys.contains(where: { key in + guard let animation = layer.animation(forKey: key) as? CAPropertyAnimation else { + return false + } + if animation.keyPath == "backgroundColor" { + return true + } else { + return false + } + }) { + fromValue = layer.presentation()?.backgroundColor ?? layer.backgroundColor + } else { + fromValue = layer.backgroundColor + } + + var mappedFromValue: UIColor + if let fromValue { + mappedFromValue = UIColor(cgColor: fromValue) + } else { + mappedFromValue = .clear + } + + layer.backgroundColor = color.cgColor + self.add(animation: ControlledTransitionProperty( + layer: layer, + path: "backgroundColor", + fromValue: mappedFromValue.cgColor, + toValue: color.cgColor, + completion: completion + )) + } + public func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)?) { if layer.cornerRadius == cornerRadius { return @@ -2305,6 +2475,14 @@ public final class ControlledTransition { self.transition.updatePosition(layer: layer, position: position, completion: completion) } + public func updateTransform(layer: CALayer, transform: CATransform3D, completion: ((Bool) -> Void)?) { + self.transition.updateTransform(layer: layer, transform: CATransform3DGetAffineTransform(transform), completion: completion) + } + + public func updateBackgroundColor(layer: CALayer, color: UIColor, completion: ((Bool) -> Void)?) { + self.transition.updateBackgroundColor(layer: layer, color: color, completion: completion) + } + public func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, completion: ((Bool) -> Void)?) { self.transition.animatePosition(layer: layer, from: fromValue, to: toValue, completion: completion) } diff --git a/submodules/Display/Source/GenerateImage.swift b/submodules/Display/Source/GenerateImage.swift index 7049b066ad4..18bafbed6d7 100644 --- a/submodules/Display/Source/GenerateImage.swift +++ b/submodules/Display/Source/GenerateImage.swift @@ -336,7 +336,7 @@ public func generateTintedImage(image: UIImage?, color: UIColor, backgroundColor return tintedImage } -public func generateGradientTintedImage(image: UIImage?, colors: [UIColor]) -> UIImage? { +public func generateGradientTintedImage(image: UIImage?, colors: [UIColor], direction: GradientImageDirection = .vertical) -> UIImage? { guard let image = image else { return nil } @@ -363,7 +363,24 @@ public func generateGradientTintedImage(image: UIImage?, colors: [UIColor]) -> U let colorSpace = DeviceGraphicsContextSettings.shared.colorSpace let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: imageRect.height), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions()) + let start: CGPoint + let end: CGPoint + switch direction { + case .horizontal: + start = .zero + end = CGPoint(x: imageRect.width, y: 0.0) + case .vertical: + start = CGPoint(x: 0.0, y: imageRect.height) + end = .zero + 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()) } else if !colors.isEmpty { context.setFillColor(colors[0].cgColor) context.fill(imageRect) @@ -382,6 +399,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? { @@ -432,6 +450,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/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index a50a4cbde32..365152e8259 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -574,7 +574,7 @@ open class NavigationController: UINavigationController, ContainableController, modalStyleOverlayTransitionFactor = max(modalStyleOverlayTransitionFactor, overlayContainer.controller.modalStyleOverlayTransitionFactor) - if overlayContainer.isReady { + if overlayContainer.isReady && !overlayContainer.isRemoved { let wasNotAdded = overlayContainer.supernode == nil if overlayWantsToBeBelowKeyboard { @@ -1529,26 +1529,26 @@ open class NavigationController: UINavigationController, ContainableController, guard let strongSelf = self else { return } - if inGlobal { - for i in 0 ..< strongSelf.globalOverlayContainers.count { - let overlayContainer = strongSelf.globalOverlayContainers[i] - if overlayContainer.controller === controller { - overlayContainer.removeFromSupernode() - strongSelf.globalOverlayContainers.remove(at: i) - strongSelf.internalGlobalOverlayControllersUpdated() - break - } + + for i in 0 ..< strongSelf.globalOverlayContainers.count { + let overlayContainer = strongSelf.globalOverlayContainers[i] + if overlayContainer.controller === controller { + overlayContainer.isRemoved = true + overlayContainer.removeFromSupernode() + strongSelf.globalOverlayContainers.remove(at: i) + strongSelf.internalGlobalOverlayControllersUpdated() + break } - } else { - for i in 0 ..< strongSelf.overlayContainers.count { - let overlayContainer = strongSelf.overlayContainers[i] - if overlayContainer.controller === controller { - overlayContainer.removeFromSupernode() - strongSelf.overlayContainers.remove(at: i) - strongSelf._overlayControllersPromise.set(strongSelf.overlayContainers.map({ $0.controller })) - strongSelf.internalOverlayControllersUpdated() - break - } + } + for i in 0 ..< strongSelf.overlayContainers.count { + let overlayContainer = strongSelf.overlayContainers[i] + if overlayContainer.controller === controller { + overlayContainer.isRemoved = true + overlayContainer.removeFromSupernode() + strongSelf.overlayContainers.remove(at: i) + strongSelf._overlayControllersPromise.set(strongSelf.overlayContainers.map({ $0.controller })) + strongSelf.internalOverlayControllersUpdated() + break } } diff --git a/submodules/Display/Source/Navigation/NavigationOverlayContainer.swift b/submodules/Display/Source/Navigation/NavigationOverlayContainer.swift index a8c358a5f96..1ac9906257a 100644 --- a/submodules/Display/Source/Navigation/NavigationOverlayContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationOverlayContainer.swift @@ -8,6 +8,7 @@ final class NavigationOverlayContainer: ASDisplayNode { let blocksInteractionUntilReady: Bool private(set) var isReady: Bool = false + var isRemoved: Bool = false var isReadyUpdated: (() -> Void)? private var isReadyDisposable: Disposable? diff --git a/submodules/Display/Source/Nodes/ASImageNode.swift b/submodules/Display/Source/Nodes/ASImageNode.swift index 2ebf0e99870..183f0632e7c 100644 --- a/submodules/Display/Source/Nodes/ASImageNode.swift +++ b/submodules/Display/Source/Nodes/ASImageNode.swift @@ -8,7 +8,7 @@ open class ASImageNode: ASDisplayNode { if self.isNodeLoaded { if let image = self.image { let capInsets = image.capInsets - if capInsets.left.isZero && capInsets.top.isZero { + if capInsets.left.isZero && capInsets.top.isZero && capInsets.right.isZero && capInsets.bottom.isZero { self.contentsScale = image.scale self.contents = image.cgImage } else { diff --git a/submodules/Display/Source/Nodes/ButtonNode.swift b/submodules/Display/Source/Nodes/ButtonNode.swift index e3045884de3..18f3cb76ed4 100644 --- a/submodules/Display/Source/Nodes/ButtonNode.swift +++ b/submodules/Display/Source/Nodes/ButtonNode.swift @@ -401,31 +401,31 @@ open class ASButtonNode: ASControlNode { if self.laysOutHorizontally { switch self.contentHorizontalAlignment { case .left: - titleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floor((contentRect.height - self.calculatedTitleSize.height) / 2.0)) - highlightedTitleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floor((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0)) - disabledTitleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floor((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0)) - imageOrigin = CGPoint(x: titleOrigin.x + self.calculatedTitleSize.width + self.contentSpacing, y: contentRect.minY + floor((contentRect.height - imageSize.height) / 2.0)) + titleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floorToScreenPixels((contentRect.height - self.calculatedTitleSize.height) / 2.0)) + highlightedTitleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floorToScreenPixels((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0)) + disabledTitleOrigin = CGPoint(x: contentRect.minX, y: contentRect.minY + floorToScreenPixels((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0)) + imageOrigin = CGPoint(x: titleOrigin.x + self.calculatedTitleSize.width + self.contentSpacing, y: contentRect.minY + floorToScreenPixels((contentRect.height - imageSize.height) / 2.0)) case .right: - titleOrigin = CGPoint(x: contentRect.maxX - self.calculatedTitleSize.width, y: contentRect.minY + floor((contentRect.height - self.calculatedTitleSize.height) / 2.0)) - highlightedTitleOrigin = CGPoint(x: contentRect.maxX - self.calculatedHighlightedTitleSize.width, y: contentRect.minY + floor((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0)) - disabledTitleOrigin = CGPoint(x: contentRect.maxX - self.calculatedDisabledTitleSize.width, y: contentRect.minY + floor((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0)) - imageOrigin = CGPoint(x: titleOrigin.x - self.contentSpacing - imageSize.width, y: contentRect.minY + floor((contentRect.height - imageSize.height) / 2.0)) + titleOrigin = CGPoint(x: contentRect.maxX - self.calculatedTitleSize.width, y: contentRect.minY + floorToScreenPixels((contentRect.height - self.calculatedTitleSize.height) / 2.0)) + highlightedTitleOrigin = CGPoint(x: contentRect.maxX - self.calculatedHighlightedTitleSize.width, y: contentRect.minY + floorToScreenPixels((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0)) + disabledTitleOrigin = CGPoint(x: contentRect.maxX - self.calculatedDisabledTitleSize.width, y: contentRect.minY + floorToScreenPixels((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0)) + imageOrigin = CGPoint(x: titleOrigin.x - self.contentSpacing - imageSize.width, y: contentRect.minY + floorToScreenPixels((contentRect.height - imageSize.height) / 2.0)) default: - titleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedTitleSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - self.calculatedTitleSize.height) / 2.0)) - highlightedTitleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedHighlightedTitleSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0)) - disabledTitleOrigin = CGPoint(x: floor((contentRect.width - self.calculatedDisabledTitleSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0)) - imageOrigin = CGPoint(x: floor((contentRect.width - imageSize.width) / 2.0), y: contentRect.minY + floor((contentRect.height - imageSize.height) / 2.0)) + titleOrigin = CGPoint(x: contentRect.minX + floorToScreenPixels((contentRect.width - self.calculatedTitleSize.width) / 2.0), y: contentRect.minY + floorToScreenPixels((contentRect.height - self.calculatedTitleSize.height) / 2.0)) + highlightedTitleOrigin = CGPoint(x: contentRect.minX + floorToScreenPixels((contentRect.width - self.calculatedHighlightedTitleSize.width) / 2.0), y: contentRect.minY + floorToScreenPixels((contentRect.height - self.calculatedHighlightedTitleSize.height) / 2.0)) + disabledTitleOrigin = CGPoint(x: floorToScreenPixels((contentRect.width - self.calculatedDisabledTitleSize.width) / 2.0), y: contentRect.minY + floorToScreenPixels((contentRect.height - self.calculatedDisabledTitleSize.height) / 2.0)) + imageOrigin = CGPoint(x: floorToScreenPixels((contentRect.width - imageSize.width) / 2.0), y: contentRect.minY + floorToScreenPixels((contentRect.height - imageSize.height) / 2.0)) } } else { var contentHeight: CGFloat = self.calculatedTitleSize.height if !imageSize.height.isZero { contentHeight += self.contentSpacing + imageSize.height } - let contentY = contentRect.minY + floor((contentRect.height - contentHeight) / 2.0) - titleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedTitleSize.height) - highlightedTitleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedHighlightedTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedHighlightedTitleSize.height) - disabledTitleOrigin = CGPoint(x: contentRect.minX + floor((contentRect.width - self.calculatedDisabledTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedDisabledTitleSize.height) - imageOrigin = CGPoint(x: floor((contentRect.width - imageSize.width) / 2.0), y: contentY) + let contentY = contentRect.minY + floorToScreenPixels((contentRect.height - contentHeight) / 2.0) + titleOrigin = CGPoint(x: contentRect.minX + floorToScreenPixels((contentRect.width - self.calculatedTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedTitleSize.height) + highlightedTitleOrigin = CGPoint(x: contentRect.minX + floorToScreenPixels((contentRect.width - self.calculatedHighlightedTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedHighlightedTitleSize.height) + disabledTitleOrigin = CGPoint(x: contentRect.minX + floorToScreenPixels((contentRect.width - self.calculatedDisabledTitleSize.width) / 2.0), y: contentY + contentHeight - self.calculatedDisabledTitleSize.height) + imageOrigin = CGPoint(x: floorToScreenPixels((contentRect.width - imageSize.width) / 2.0), y: contentY) } self.titleNode.frame = CGRect(origin: titleOrigin, size: self.calculatedTitleSize) diff --git a/submodules/Display/Source/PortalView.swift b/submodules/Display/Source/PortalView.swift index 7762252bce8..c26d2632fe5 100644 --- a/submodules/Display/Source/PortalView.swift +++ b/submodules/Display/Source/PortalView.swift @@ -22,4 +22,10 @@ public class PortalView { portalSuperlayer.insertSublayer(self.view.layer, at: UInt32(index)) } } + + public func reloadPortal() { + if let sourceView = self.sourceView as? PortalSourceView { + self.reloadPortal(sourceView: sourceView) + } + } } diff --git a/submodules/Display/Source/TextAlertController.swift b/submodules/Display/Source/TextAlertController.swift index 54437bb0f6d..252d5efc546 100644 --- a/submodules/Display/Source/TextAlertController.swift +++ b/submodules/Display/Source/TextAlertController.swift @@ -9,6 +9,7 @@ public enum TextAlertActionType { case genericAction case defaultAction case destructiveAction + case defaultDestructiveAction } public struct TextAlertAction { @@ -25,7 +26,11 @@ public struct TextAlertAction { public final class TextAlertContentActionNode: HighlightableButtonNode { private var theme: AlertControllerTheme - let action: TextAlertAction + public var action: TextAlertAction { + didSet { + self.updateTitle() + } + } private let backgroundNode: ASDisplayNode @@ -110,11 +115,11 @@ public final class TextAlertContentActionNode: HighlightableButtonNode { switch self.action.type { case .defaultAction, .genericAction: color = self.actionEnabled ? self.theme.accentColor : self.theme.disabledColor - case .destructiveAction: + case .destructiveAction, .defaultDestructiveAction: color = self.actionEnabled ? self.theme.destructiveColor : self.theme.disabledColor } switch self.action.type { - case .defaultAction: + case .defaultAction, .defaultDestructiveAction: font = Font.semibold(theme.baseFontSize) case .destructiveAction, .genericAction: break diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index b469147b603..ce884134209 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -83,14 +83,16 @@ public final class TextNodeBlockQuoteData: NSObject { public let secondaryColor: UIColor? public let tertiaryColor: UIColor? public let backgroundColor: UIColor + public let isCollapsible: Bool - public init(kind: Kind, title: NSAttributedString?, color: UIColor, secondaryColor: UIColor?, tertiaryColor: UIColor?, backgroundColor: UIColor) { + public init(kind: Kind, title: NSAttributedString?, color: UIColor, secondaryColor: UIColor?, tertiaryColor: UIColor?, backgroundColor: UIColor, isCollapsible: Bool) { self.kind = kind self.title = title self.color = color self.secondaryColor = secondaryColor self.tertiaryColor = tertiaryColor self.backgroundColor = backgroundColor + self.isCollapsible = isCollapsible super.init() } @@ -1167,7 +1169,13 @@ private func addAttachment(attachment: UIImage, line: TextNodeLine, ascent: CGFl line.attachments.append(TextNodeAttachment(range: NSMakeRange(startIndex, endIndex - startIndex), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent), attachment: attachment)) } -open class TextNode: ASDisplayNode { +public protocol TextNodeProtocol: ASDisplayNode { + var currentText: NSAttributedString? { get } + func textRangeRects(in range: NSRange) -> (rects: [CGRect], start: TextRangeRectEdge, end: TextRangeRectEdge)? + func attributesAtPoint(_ point: CGPoint, orNearest: Bool) -> (Int, [NSAttributedString.Key: Any])? +} + +open class TextNode: ASDisplayNode, TextNodeProtocol { public struct RenderContentTypes: OptionSet { public var rawValue: Int @@ -1196,6 +1204,14 @@ open class TextNode: ASDisplayNode { public internal(set) var cachedLayout: TextNodeLayout? public var renderContentTypes: RenderContentTypes = .all + public var currentText: NSAttributedString? { + return self.cachedLayout?.attributedString + } + + public func textRangeRects(in range: NSRange) -> (rects: [CGRect], start: TextRangeRectEdge, end: TextRangeRectEdge)? { + return self.cachedLayout?.rangeRects(in: range) + } + override public init() { super.init() diff --git a/submodules/Display/Source/TooltipController.swift b/submodules/Display/Source/TooltipController.swift index ca34cecec9c..5ddaa0ac018 100644 --- a/submodules/Display/Source/TooltipController.swift +++ b/submodules/Display/Source/TooltipController.swift @@ -94,6 +94,11 @@ public final class TooltipControllerPresentationArguments { } open class TooltipController: ViewController, StandalonePresentableController { + public enum Alignment { + case center + case natural + } + private var controllerNode: TooltipControllerNode { return self.displayNode as! TooltipControllerNode } @@ -101,6 +106,7 @@ open class TooltipController: ViewController, StandalonePresentableController { public private(set) var content: TooltipControllerContent private let baseFontSize: CGFloat private let balancedTextLayout: Bool + private let alignment: Alignment private let isBlurred: Bool open func updateContent(_ content: TooltipControllerContent, animated: Bool, extendTimer: Bool, arrowOnBottom: Bool = true) { @@ -132,10 +138,11 @@ open class TooltipController: ViewController, StandalonePresentableController { public var dismissed: ((Bool) -> Void)? - public init(content: TooltipControllerContent, baseFontSize: CGFloat, balancedTextLayout: Bool = false, isBlurred: Bool = false, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true, padding: CGFloat = 8.0, innerPadding: UIEdgeInsets = UIEdgeInsets()) { + public init(content: TooltipControllerContent, baseFontSize: CGFloat, balancedTextLayout: Bool = false, alignment: Alignment = .center, isBlurred: Bool = false, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true, padding: CGFloat = 8.0, innerPadding: UIEdgeInsets = UIEdgeInsets()) { self.content = content self.baseFontSize = baseFontSize self.balancedTextLayout = balancedTextLayout + self.alignment = alignment self.isBlurred = isBlurred self.timeout = timeout self.dismissByTapOutside = dismissByTapOutside @@ -159,7 +166,7 @@ open class TooltipController: ViewController, StandalonePresentableController { } override open func loadDisplayNode() { - self.displayNode = TooltipControllerNode(content: self.content, baseFontSize: self.baseFontSize, balancedTextLayout: self.balancedTextLayout, isBlurred: self.isBlurred, dismiss: { [weak self] tappedInside in + self.displayNode = TooltipControllerNode(content: self.content, baseFontSize: self.baseFontSize, balancedTextLayout: self.balancedTextLayout, alignment: self.alignment, isBlurred: self.isBlurred, dismiss: { [weak self] tappedInside in self?.dismiss(tappedInside: tappedInside) }, dismissByTapOutside: self.dismissByTapOutside, dismissByTapOutsideSource: self.dismissByTapOutsideSource) self.controllerNode.padding = self.padding diff --git a/submodules/Display/Source/TooltipControllerNode.swift b/submodules/Display/Source/TooltipControllerNode.swift index b190f3a0fe0..79e37647d03 100644 --- a/submodules/Display/Source/TooltipControllerNode.swift +++ b/submodules/Display/Source/TooltipControllerNode.swift @@ -5,6 +5,7 @@ import AsyncDisplayKit final class TooltipControllerNode: ASDisplayNode { private let baseFontSize: CGFloat private let balancedTextLayout: Bool + private let alignment: TooltipController.Alignment private let dismiss: (Bool) -> Void @@ -26,9 +27,10 @@ final class TooltipControllerNode: ASDisplayNode { private var dismissedByTouchOutside = false private var dismissByTapOutsideSource = false - init(content: TooltipControllerContent, baseFontSize: CGFloat, balancedTextLayout: Bool, isBlurred: Bool, dismiss: @escaping (Bool) -> Void, dismissByTapOutside: Bool, dismissByTapOutsideSource: Bool) { + init(content: TooltipControllerContent, baseFontSize: CGFloat, balancedTextLayout: Bool, alignment: TooltipController.Alignment, isBlurred: Bool, dismiss: @escaping (Bool) -> Void, dismissByTapOutside: Bool, dismissByTapOutsideSource: Bool) { self.baseFontSize = baseFontSize self.balancedTextLayout = balancedTextLayout + self.alignment = alignment self.dismissByTapOutside = dismissByTapOutside self.dismissByTapOutsideSource = dismissByTapOutsideSource @@ -45,7 +47,7 @@ final class TooltipControllerNode: ASDisplayNode { if case let .attributedText(text) = content { self.textNode.attributedText = text } else { - self.textNode.attributedText = NSAttributedString(string: content.text, font: Font.regular(floor(baseFontSize * 14.0 / 17.0)), textColor: .white, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: content.text, font: Font.regular(floor(baseFontSize * 14.0 / 17.0)), textColor: .white, paragraphAlignment: alignment == .center ? .center : .natural) } self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false @@ -75,7 +77,7 @@ final class TooltipControllerNode: ASDisplayNode { }) self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) } - self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(floor(self.baseFontSize * 14.0 / 17.0)), textColor: .white, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(floor(self.baseFontSize * 14.0 / 17.0)), textColor: .white, paragraphAlignment: self.alignment == .center ? .center : .natural) if let layout = self.validLayout { self.containerLayoutUpdated(layout, transition: transition) } @@ -84,7 +86,7 @@ final class TooltipControllerNode: ASDisplayNode { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.validLayout = layout - let maxWidth = layout.size.width - 20.0 + let maxWidth = layout.size.width - 20.0 - self.padding * 2.0 let contentSize: CGSize diff --git a/submodules/DrawingUI/BUILD b/submodules/DrawingUI/BUILD index 950c0313310..4a527421b97 100644 --- a/submodules/DrawingUI/BUILD +++ b/submodules/DrawingUI/BUILD @@ -55,7 +55,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":DrawingUIBundle", @@ -108,6 +108,7 @@ swift_library( "//submodules/TelegramUI/Components/DustEffect", "//submodules/TelegramUI/Components/DynamicCornerRadiusView", "//submodules/TelegramUI/Components/StickerPickerScreen", + "//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation", ], visibility = [ "//visibility:public", diff --git a/submodules/DrawingUI/Sources/ColorPickerScreen.swift b/submodules/DrawingUI/Sources/ColorPickerScreen.swift index 5745ca248b1..6b79a8f5360 100644 --- a/submodules/DrawingUI/Sources/ColorPickerScreen.swift +++ b/submodules/DrawingUI/Sources/ColorPickerScreen.swift @@ -591,11 +591,15 @@ final class ColorGridComponent: Component { else { return nil } - let row = Int(point.y / size.height * 10.0) - let col = Int(point.x / size.width * 12.0) + let row = max(0, min(10, Int(point.y / size.height * 10.0))) + let col = max(0, min(12, Int(point.x / size.width * 12.0))) let index = row * 12 + col - return DrawingColor(rgb: palleteColors[index]) + if index < palleteColors.count { + return DrawingColor(rgb: palleteColors[index]) + } else { + return DrawingColor(rgb: 0x000000) + } } @objc func handlePress(_ gestureRecognizer: UILongPressGestureRecognizer) { diff --git a/submodules/DrawingUI/Sources/DrawingReactionView.swift b/submodules/DrawingUI/Sources/DrawingReactionView.swift index 0396953a96c..959c3525c74 100644 --- a/submodules/DrawingUI/Sources/DrawingReactionView.swift +++ b/submodules/DrawingUI/Sources/DrawingReactionView.swift @@ -133,7 +133,7 @@ public class DrawingReactionEntityView: DrawingStickerEntityView { context: self.context, animationCache: self.context.animationCache, presentationData: self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme), - items: reactionItems.map(ReactionContextItem.reaction), + items: reactionItems.map { ReactionContextItem.reaction(item: $0, icon: .none) }, selectedItems: Set(), title: nil, reactionsLocked: false, diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index e5d661d92ec..982235d5996 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -25,6 +25,7 @@ import TelegramUIPreferences import FastBlur import MediaEditor import StickerPickerScreen +import ImageObjectSeparation public struct DrawingResultData { public let data: Data? diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift index e2c9d4e880c..0299a883cf6 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift @@ -1327,8 +1327,6 @@ private extension UIBezierPath { } } - - extension UIImageView { func setDrawingAnimatedImage(data: Data) { DispatchQueue.global().async { @@ -1354,48 +1352,3 @@ extension UIImageView { self.animationRepeatCount = 0 } } - -//private func prerenderEntityTransformations(entity: DrawingEntity, image: UIImage, colorSpace: CGColorSpace) -> UIImage { -// let imageSize = image.size -// -// let angle: CGFloat -// var scale: CGFloat -// let position: CGPoint -// -// if let entity = entity as? DrawingStickerEntity { -// angle = -entity.rotation -// scale = entity.scale -// position = entity.position -// } else { -// fatalError() -// } -// -// let rotatedSize = CGSize( -// width: abs(imageSize.width * cos(angle)) + abs(imageSize.height * sin(angle)), -// height: abs(imageSize.width * sin(angle)) + abs(imageSize.height * cos(angle)) -// ) -// let newSize = CGSize(width: rotatedSize.width * scale, height: rotatedSize.height * scale) -// -// let newImage = generateImage(newSize, contextGenerator: { size, context in -// context.setAllowsAntialiasing(true) -// context.setShouldAntialias(true) -// context.interpolationQuality = .high -// context.clear(CGRect(origin: .zero, size: size)) -// context.translateBy(x: newSize.width * 0.5, y: newSize.height * 0.5) -// context.rotate(by: angle) -// context.scaleBy(x: scale, y: scale) -// let drawRect = CGRect( -// x: -imageSize.width * 0.5, -// y: -imageSize.height * 0.5, -// width: imageSize.width, -// height: imageSize.height -// ) -// if let cgImage = image.cgImage { -// context.draw(cgImage, in: drawRect) -// } -// }, scale: 1.0)! -// -// let _ = position -// -// return newImage -//} diff --git a/submodules/DrawingUI/Sources/DrawingUtils.swift b/submodules/DrawingUI/Sources/DrawingUtils.swift index 1050beaafbe..43f60470689 100644 --- a/submodules/DrawingUI/Sources/DrawingUtils.swift +++ b/submodules/DrawingUI/Sources/DrawingUtils.swift @@ -544,59 +544,3 @@ extension CATransform3D { return (t, r, s) } } - -public extension UIImage { - class func animatedImageFromData(data: Data) -> DrawingAnimatedImage? { - guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { - return nil - } - - let count = CGImageSourceGetCount(source) - var images = [UIImage]() - var duration = 0.0 - - for i in 0.. Double { - var delay = 0.0 - - let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil) - let gifPropertiesPointer = UnsafeMutablePointer.allocate(capacity: 0) - if CFDictionaryGetValueIfPresent(cfProperties, Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque(), gifPropertiesPointer) == false { - return delay - } - - let gifProperties:CFDictionary = unsafeBitCast(gifPropertiesPointer.pointee, to: CFDictionary.self) - - var delayObject: AnyObject = unsafeBitCast(CFDictionaryGetValue(gifProperties, Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()), to: AnyObject.self) - if delayObject.doubleValue == 0 { - delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties, Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self) - } - - delay = delayObject as? Double ?? 0 - - return delay - } -} - -public final class DrawingAnimatedImage { - public let images: [UIImage] - public let duration: Double - - init(images: [UIImage], duration: Double) { - self.images = images - self.duration = duration - } -} diff --git a/submodules/Emoji/BUILD b/submodules/Emoji/BUILD index cba5496deaf..33d9816ec14 100644 --- a/submodules/Emoji/BUILD +++ b/submodules/Emoji/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/EncryptionKeyVisualization/BUILD b/submodules/EncryptionKeyVisualization/BUILD index 63804e3386b..d24b2cb24a0 100644 --- a/submodules/EncryptionKeyVisualization/BUILD +++ b/submodules/EncryptionKeyVisualization/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/FeaturedStickersScreen/BUILD b/submodules/FeaturedStickersScreen/BUILD index 450df08ea2f..3bc650f6bd0 100644 --- a/submodules/FeaturedStickersScreen/BUILD +++ b/submodules/FeaturedStickersScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/FetchManagerImpl/BUILD b/submodules/FetchManagerImpl/BUILD index c2621c54ef0..140c2e596d5 100644 --- a/submodules/FetchManagerImpl/BUILD +++ b/submodules/FetchManagerImpl/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/FileMediaResourceStatus/BUILD b/submodules/FileMediaResourceStatus/BUILD index 44d70774c68..44e0d855d3c 100644 --- a/submodules/FileMediaResourceStatus/BUILD +++ b/submodules/FileMediaResourceStatus/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/GalleryData/BUILD b/submodules/GalleryData/BUILD index ec0ae7d4d84..92d189c1564 100644 --- a/submodules/GalleryData/BUILD +++ b/submodules/GalleryData/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/GalleryUI/BUILD b/submodules/GalleryUI/BUILD index 5ecce435139..57a094c90ee 100644 --- a/submodules/GalleryUI/BUILD +++ b/submodules/GalleryUI/BUILD @@ -12,7 +12,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 03b47bfce27..204ef30309c 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -2503,11 +2503,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor) }, action: { c, _ in guard let strongSelf = self else { - c.dismiss(completion: nil) + c?.dismiss(completion: nil) return } - c.setItems(strongSelf.contextMenuSpeedItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + c?.setItems(strongSelf.contextMenuSpeedItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) items.append(.separator) @@ -2633,10 +2633,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) }, iconPosition: .left, action: { c, _ in guard let strongSelf = self else { - c.dismiss(completion: nil) + c?.dismiss(completion: nil) return } - c.setItems(strongSelf.contextMenuMainItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + c?.setItems(strongSelf.contextMenuMainItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) let sliderValuePromise = ValuePromise(nil) diff --git a/submodules/GameUI/BUILD b/submodules/GameUI/BUILD index dce9403ef67..4b4e2768298 100644 --- a/submodules/GameUI/BUILD +++ b/submodules/GameUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/Geocoding/BUILD b/submodules/Geocoding/BUILD index 35ce472ddf8..a39838e879d 100644 --- a/submodules/Geocoding/BUILD +++ b/submodules/Geocoding/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/GlassButtonNode/BUILD b/submodules/GlassButtonNode/BUILD index bab4d6718eb..2094c5b6c95 100644 --- a/submodules/GlassButtonNode/BUILD +++ b/submodules/GlassButtonNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/GradientBackground/BUILD b/submodules/GradientBackground/BUILD index a84790b04bd..cc12e8dedbf 100644 --- a/submodules/GradientBackground/BUILD +++ b/submodules/GradientBackground/BUILD @@ -8,7 +8,7 @@ swift_library( ]), copts = [ "-O", - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/GraphCore/BUILD b/submodules/GraphCore/BUILD index 058b5faf808..5907d86a01f 100644 --- a/submodules/GraphCore/BUILD +++ b/submodules/GraphCore/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/GraphUI/BUILD b/submodules/GraphUI/BUILD index 48670e81ce9..98b6efd4d00 100644 --- a/submodules/GraphUI/BUILD +++ b/submodules/GraphUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/GridMessageSelectionNode/BUILD b/submodules/GridMessageSelectionNode/BUILD index 0f49aaa07e7..858f9a7e41c 100644 --- a/submodules/GridMessageSelectionNode/BUILD +++ b/submodules/GridMessageSelectionNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/HashtagSearchUI/BUILD b/submodules/HashtagSearchUI/BUILD index 91c74fef791..39640b2499a 100644 --- a/submodules/HashtagSearchUI/BUILD +++ b/submodules/HashtagSearchUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index 5340d936df6..9e2496cca91 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -18,6 +18,8 @@ public final class HashtagSearchController: TelegramBaseController { private let peer: EnginePeer? private let query: String let all: Bool + let publicPosts: Bool + private var transitionDisposable: Disposable? private let openMessageFromSearchDisposable = MetaDisposable() @@ -31,11 +33,12 @@ public final class HashtagSearchController: TelegramBaseController { return self.displayNode as! HashtagSearchControllerNode } - public init(context: AccountContext, peer: EnginePeer?, query: String, all: Bool = false) { + public init(context: AccountContext, peer: EnginePeer?, query: String, all: Bool = false, publicPosts: Bool = false) { self.context = context self.peer = peer self.query = query self.all = all + self.publicPosts = publicPosts self.animationCache = context.animationCache self.animationRenderer = context.animationRenderer @@ -48,92 +51,7 @@ public final class HashtagSearchController: TelegramBaseController { self.title = query self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - - let location: SearchMessagesLocation = .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil) - let search = context.engine.messages.searchMessages(location: location, query: query, state: nil) - let foundMessages: Signal<[ChatListSearchEntry], NoError> = combineLatest(search, self.context.sharedContext.presentationData) - |> map { result, presentationData in - let result = result.0 - let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true) - return result.messages.map({ .message(EngineMessage($0), EngineRenderedPeer(message: EngineMessage($0)), result.readStates[$0.id.peerId].flatMap { EnginePeerReadCounters(state: $0, isMuted: false) }, nil, chatListPresentationData, result.totalCount, nil, false, .index($0.index), nil, .generic, false, nil, false) }) - } - let interaction = ChatListNodeInteraction(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: { - }, peerSelected: { _, _, _, _ in - }, disabledPeerSelected: { _, _, _ in - }, togglePeerSelected: { _, _ in - }, togglePeersSelection: { _, _ in - }, additionalCategorySelected: { _ in - }, messageSelected: { [weak self] peer, _, message, _ in - if let strongSelf = self { - strongSelf.openMessageFromSearchDisposable.set((strongSelf.context.engine.peers.ensurePeerIsLocallyAvailable(peer: peer) |> deliverOnMainQueue).start(next: { actualPeer in - if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeer), subject: message.id.peerId == actualPeer.id ? .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil) : nil, keepStack: .always)) - } - })) - strongSelf.controllerNode.listNode.clearHighlightAnimated(true) - } - }, groupSelected: { _ in - }, addContact: {_ in - }, setPeerIdWithRevealedOptions: { _, _ in - }, setItemPinned: { _, _ in - }, setPeerMuted: { _, _ in - }, setPeerThreadMuted: { _, _, _ in - }, deletePeer: { _, _ in - }, deletePeerThread: { _, _ in - }, setPeerThreadStopped: { _, _, _ in - }, setPeerThreadPinned: { _, _, _ in - }, setPeerThreadHidden: { _, _, _ in - }, updatePeerGrouping: { _, _ in - }, togglePeerMarkedUnread: { _, _ in - }, toggleArchivedFolderHiddenByDefault: { - }, toggleThreadsSelection: { _, _ in - }, hidePsa: { _ in - }, activateChatPreview: { _, _, _, gesture, _ in - gesture?.cancel() - }, present: { _ in - }, openForumThread: { _, _ in - }, openStorageManagement: { - }, openPasswordSetup: { - }, openPremiumIntro: { - }, openPremiumGift: { _ in - }, openPremiumManagement: { - }, openActiveSessions: { - }, openBirthdaySetup: { - }, performActiveSessionAction: { _, _ in - }, openChatFolderUpdates: { - }, hideChatFolderUpdates: { - }, openStories: { _, _ in - }, dismissNotice: { _ in - }, editPeer: { _ in - }) - - let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil) - self.transitionDisposable = (foundMessages - |> deliverOnMainQueue).start(next: { [weak self] entries in - if let strongSelf = self { - let previousEntries = previousSearchItems.swap(entries) - - let listInteraction = ListMessageItemInteraction(openMessage: { message, mode -> Bool in - return true - }, openMessageContextMenu: { message, bool, node, rect, gesture in - }, toggleMessagesSelection: { messageId, selected in - }, openUrl: { url, _, _, message in - }, openInstantPage: { message, data in - }, longTap: { action, message in - }, getHiddenMedia: { - return [:] - }) - - let firstTime = previousEntries == nil - let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], requestPeerType: nil, location: .chatList(groupId: .root), key: .chats, tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: { - }, toggleExpandGlobalResults: { - }, searchPeer: { _ in - }, searchQuery: "", searchOptions: nil, messageContextAction: nil, openClearRecentlyDownloaded: {}, toggleAllPaused: {}, openStories: { _, _ in - }) - strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime) - } - }) - + self.presentationDataDisposable = (self.context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { @@ -158,23 +76,18 @@ public final class HashtagSearchController: TelegramBaseController { } deinit { - self.transitionDisposable?.dispose() self.presentationDataDisposable?.dispose() self.openMessageFromSearchDisposable.dispose() } override public func loadDisplayNode() { self.displayNode = HashtagSearchControllerNode(context: self.context, controller: self, peer: self.peer, query: self.query, navigationBar: self.navigationBar, navigationController: self.navigationController as? NavigationController) - if let chatController = self.controllerNode.chatController { + if let chatController = self.controllerNode.currentController { chatController.parentController = self } self.displayNodeDidLoad() } - - private var suspendNavigationBarLayout: Bool = false - private var suspendedNavigationBarLayout: ContainerViewLayout? - private var additionalNavigationBarBackgroundHeight: CGFloat = 0.0 private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -183,26 +96,10 @@ public final class HashtagSearchController: TelegramBaseController { self.controllerNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) } - - override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - if self.suspendNavigationBarLayout { - self.suspendedNavigationBarLayout = layout - return - } - self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition) - } - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.suspendNavigationBarLayout = true - + public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) - self.additionalNavigationBarBackgroundHeight = self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) - - self.suspendNavigationBarLayout = false - if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout { - self.suspendedNavigationBarLayout = suspendedNavigationBarLayout - self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition) - } + let _ = self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, transition: transition) } } diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift index 7bf1f75596f..6e7883caa79 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift @@ -1,6 +1,7 @@ import Display import UIKit import AsyncDisplayKit +import SwiftSignalKit import TelegramCore import TelegramPresentationData import AccountContext @@ -8,21 +9,35 @@ import ChatListUI import SegmentedControlNode import ChatListSearchItemHeader -final class HashtagSearchControllerNode: ASDisplayNode { +final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { private let context: AccountContext private weak var controller: HashtagSearchController? - private let query: String + private var query: String + + private let searchQueryPromise = ValuePromise() + private var searchQueryDisposable: Disposable? private let navigationBar: NavigationBar? - private let segmentedControlNode: SegmentedControlNode - let listNode: ListView - let shimmerNode: ChatListSearchShimmerNode + private let searchContentNode: HashtagSearchNavigationContentNode + private let shimmerNode: ChatListSearchShimmerNode + private let recentListNode: HashtagSearchRecentListNode + + private let isSearching = Promise() + private var isSearchingDisposable: Disposable? + + private let clippingNode: ASDisplayNode + private let containerNode: ASDisplayNode + let currentController: ChatController? + let myController: ChatController? + let myChatContents: HashtagSearchGlobalChatContents? + + let globalController: ChatController? + let globalChatContents: HashtagSearchGlobalChatContents? - let chatController: ChatController? + private var panRecognizer: InteractiveTransitionGestureRecognizer? private var containerLayout: (ContainerViewLayout, CGFloat)? - private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = [] private var hasValidLayout = false init(context: AccountContext, controller: HashtagSearchController, peer: EnginePeer?, query: String, navigationBar: NavigationBar?, navigationController: NavigationController?) { @@ -33,32 +48,55 @@ final class HashtagSearchControllerNode: ASDisplayNode { let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.clippingNode = ASDisplayNode() + self.clippingNode.clipsToBounds = true + + self.containerNode = ASDisplayNode() + + let cleanHashtag = cleanHashtag(query) + self.searchContentNode = HashtagSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, initialQuery: cleanHashtag, hasCurrentChat: peer != nil, cancel: { [weak controller] in + controller?.dismiss() + }) + self.shimmerNode = ChatListSearchShimmerNode(key: .chats) self.shimmerNode.isUserInteractionEnabled = false self.shimmerNode.allowsGroupOpacity = true - self.listNode = ListView() - self.listNode.accessibilityPageScrolledString = { row, count in - return presentationData.strings.VoiceOver_ScrollStatus(row, count).string - } - - var items: [String] = [] - if peer?.id == context.account.peerId { - items.append(presentationData.strings.Conversation_SavedMessages) - } else if let id = peer?.id, id.isReplies { - items.append(presentationData.strings.DialogList_Replies) + self.recentListNode = HashtagSearchRecentListNode(context: context) + self.recentListNode.alpha = 0.0 + + let navigationController = controller.navigationController as? NavigationController + if let peer, !controller.all { + self.currentController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .inline(navigationController)) + self.currentController?.alwaysShowSearchResultsAsList = true + self.currentController?.showListEmptyResults = true + self.currentController?.customNavigationController = navigationController } else { - items.append(peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) ?? "") + self.currentController = nil } - items.append(presentationData.strings.HashtagSearch_AllChats) - self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: presentationData.theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: controller.all ? 1 : 0) + + let myChatContents = HashtagSearchGlobalChatContents(context: context, query: cleanHashtag, publicPosts: false) + self.myChatContents = myChatContents + self.myController = context.sharedContext.makeChatController(context: context, chatLocation: .customChatContents, subject: .customChatContents(contents: myChatContents), botStart: nil, mode: .standard(.default)) + self.myController?.alwaysShowSearchResultsAsList = true + self.myController?.showListEmptyResults = true + self.myController?.customNavigationController = navigationController + + let globalChatContents = HashtagSearchGlobalChatContents(context: context, query: cleanHashtag, publicPosts: true) + self.globalChatContents = globalChatContents + self.globalController = context.sharedContext.makeChatController(context: context, chatLocation: .customChatContents, subject: .customChatContents(contents: globalChatContents), botStart: nil, mode: .standard(.default)) + self.globalController?.alwaysShowSearchResultsAsList = true + self.globalController?.showListEmptyResults = true + self.globalController?.customNavigationController = navigationController - if let peer = peer { - self.chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .inline(navigationController)) + if controller.publicPosts { + self.searchContentNode.selectedIndex = 2 + } else if peer == nil { + self.searchContentNode.selectedIndex = 1 } else { - self.chatController = nil + self.searchContentNode.selectedIndex = 0 } - + super.init() self.setViewBlock({ @@ -67,137 +105,367 @@ final class HashtagSearchControllerNode: ASDisplayNode { self.backgroundColor = presentationData.theme.chatList.backgroundColor - self.addSubnode(self.listNode) -// self.addSubnode(self.shimmerNode) - + self.addSubnode(self.clippingNode) + self.clippingNode.addSubnode(self.containerNode) + if controller.all { - self.chatController?.displayNode.isHidden = true - self.listNode.isHidden = false + self.isSearching.set(self.myChatContents?.searching ?? .single(false)) } else { - self.chatController?.displayNode.isHidden = false - self.listNode.isHidden = true - } - - self.segmentedControlNode.selectedIndexChanged = { [weak self] index in - if let strongSelf = self { - if index == 0 { - strongSelf.chatController?.displayNode.isHidden = false - strongSelf.listNode.isHidden = true + if let _ = peer { + let isSearching: Signal + if let currentController = self.currentController { + isSearching = .single(true) + |> then( + currentController.searching.get() + |> delay(0.5, queue: Queue.mainQueue()) + ) } else { - strongSelf.chatController?.displayNode.isHidden = true - strongSelf.listNode.isHidden = false + isSearching = .single(false) } + self.isSearching.set(isSearching) + } else { + self.isSearching.set(self.myChatContents?.searching ?? .single(false)) + } + } + + self.searchContentNode.indexUpdated = { [weak self] index in + guard let self else { + return + } + self.searchContentNode.selectedIndex = index + if index == 0 { + self.isSearching.set(self.currentController?.searching.get() ?? .single(false)) + } else if index == 1 { + self.isSearching.set(self.myChatContents?.searching ?? .single(false)) + } else if index == 2 { + self.isSearching.set(self.globalChatContents?.searching ?? .single(false)) + } + if let (layout, navigationHeight) = self.containerLayout { + let _ = self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + } + } + + self.recentListNode.setSearchQuery = { [weak self] query in + guard let self else { + return + } + self.searchContentNode.query = query + self.updateSearchQuery(query) + + Queue.mainQueue().after(0.4) { + let _ = addRecentHashtagSearchQuery(engine: context.engine, string: query).startStandalone() } } - self.chatController?.isSelectingMessagesUpdated = { [weak self] isSelecting in + self.currentController?.isSelectingMessagesUpdated = { [weak self] isSelecting in if let strongSelf = self { let button: UIBarButtonItem? = isSelecting ? UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .done, target: self, action: #selector(strongSelf.cancelPressed)) : nil strongSelf.controller?.navigationItem.setRightBarButton(button, animated: true) } } + + navigationBar?.setContentNode(self.searchContentNode, animated: false) + + self.addSubnode(self.shimmerNode) + + self.searchContentNode.setQueryUpdated { [weak self] query in + self?.searchQueryPromise.set(query) + } + + let _ = addRecentHashtagSearchQuery(engine: context.engine, string: cleanHashtag).startStandalone() + self.searchContentNode.onReturn = { query in + let _ = addRecentHashtagSearchQuery(engine: context.engine, string: query).startStandalone() + } + + let throttledSearchQuery = self.searchQueryPromise.get() + |> mapToSignal { query -> Signal in + if !query.isEmpty { + return (.complete() |> delay(1.0, queue: Queue.mainQueue())) + |> then(.single(query)) + } else { + return .single(query) + } + } + + self.searchQueryDisposable = (throttledSearchQuery + |> deliverOnMainQueue).start(next: { [weak self] query in + if let self { + self.updateSearchQuery(query) + } + }) + + self.isSearchingDisposable = (self.isSearching.get() + |> deliverOnMainQueue).start(next: { [weak self] isSearching in + if let self { + self.searchContentNode.isSearching = isSearching + let transition: ContainedViewLayoutTransition = isSearching ? .immediate : .animated(duration: 0.2, curve: .easeInOut) + transition.updateAlpha(node: self.shimmerNode, alpha: isSearching ? 1.0 : 0.0) + } + }) } - @objc private func cancelPressed() { - self.chatController?.cancelSelectingMessages() + deinit { + self.searchQueryDisposable?.dispose() + self.isSearchingDisposable?.dispose() } - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - self.backgroundColor = theme.chatList.backgroundColor + private var panAllowedDirections: InteractiveTransitionGestureRecognizerDirections { + let currentIndex = self.searchContentNode.selectedIndex + let minIndex: Int + if let _ = self.currentController { + minIndex = 0 + } else { + minIndex = 1 + } + let maxIndex = 2 - self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: theme)) + var directions: InteractiveTransitionGestureRecognizerDirections = [] + if currentIndex > minIndex { + directions.insert(.rightCenter) + } + if currentIndex < maxIndex { + directions.insert(.leftCenter) + } + return directions + } + + override func didLoad() { + super.didLoad() - self.listNode.forEachItemHeaderNode({ itemHeaderNode in - if let itemHeaderNode = itemHeaderNode as? ChatListSearchItemHeaderNode { - itemHeaderNode.updateTheme(theme: theme) + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in + guard let self else { + return [] } - }) + return self.panAllowedDirections + }, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0)) + panRecognizer.delegate = self.wrappedGestureRecognizerDelegate + panRecognizer.delaysTouchesBegan = false + panRecognizer.cancelsTouchesInView = true + self.panRecognizer = panRecognizer + self.view.addGestureRecognizer(panRecognizer) + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer { + return false + } + if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { + return true + } + return false } - func enqueueTransition(_ transition: ChatListSearchContainerTransition, firstTime: Bool) { - self.enqueuedTransitions.append((transition, firstTime)) + private var panTransitionFraction: CGFloat = 0.0 + private var panCurrentAllowedDirections: InteractiveTransitionGestureRecognizerDirections = [.leftCenter, .rightCenter] + + @objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) { + let translation = gestureRecognizer.translation(in: self.view).x + let velocity = gestureRecognizer.velocity(in: self.view).x - if self.hasValidLayout { - while !self.enqueuedTransitions.isEmpty { - self.dequeueTransition() + switch gestureRecognizer.state { + case .began, .changed: + if case .began = gestureRecognizer.state { + self.panCurrentAllowedDirections = self.panAllowedDirections + } + + self.panTransitionFraction = -translation / self.view.bounds.width + if !self.panCurrentAllowedDirections.contains(.leftCenter) { + self.panTransitionFraction = min(0.0, self.panTransitionFraction) + } + if !self.panCurrentAllowedDirections.contains(.rightCenter) { + self.panTransitionFraction = max(0.0, self.panTransitionFraction) + } + + self.searchContentNode.transitionFraction = self.panTransitionFraction + + if let (layout, navigationHeight) = self.containerLayout { + let _ = self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate) + } + case .ended, .cancelled: + var directionIsToRight: Bool? + if abs(velocity) > 10.0 { + if translation > 0.0 { + if velocity <= 0.0 { + directionIsToRight = nil + } else { + directionIsToRight = true + } + } else { + if velocity >= 0.0 { + directionIsToRight = nil + } else { + directionIsToRight = false + } + } + } else { + if abs(translation) > self.view.bounds.width / 2.0 { + directionIsToRight = translation > self.view.bounds.width / 2.0 + } + } + if !self.panCurrentAllowedDirections.contains(.rightCenter) && directionIsToRight == true { + directionIsToRight = nil + } + if !self.panCurrentAllowedDirections.contains(.leftCenter) && directionIsToRight == false { + directionIsToRight = nil + } + + if let directionIsToRight { + if directionIsToRight { + self.searchContentNode.selectedIndex -= 1 + } else { + self.searchContentNode.selectedIndex += 1 + } } + + self.panTransitionFraction = 0.0 + self.searchContentNode.transitionFraction = nil + + if let (layout, navigationHeight) = self.containerLayout { + let _ = self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + } + default: + break } } - private func dequeueTransition() { - if let (transition, _) = self.enqueuedTransitions.first { - self.enqueuedTransitions.remove(at: 0) + func updateSearchQuery(_ query: String) { + self.query = query + + let cleanQuery = cleanHashtag(query) + if !cleanQuery.isEmpty { + self.currentController?.beginMessageSearch("#" + cleanQuery) + + self.myChatContents?.hashtagSearchUpdate(query: cleanQuery) + self.myController?.beginMessageSearch("#" + cleanQuery) - let options = ListViewDeleteAndInsertOptions() - self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in }) + self.globalChatContents?.hashtagSearchUpdate(query: cleanQuery) + self.globalController?.beginMessageSearch("#" + cleanQuery) + } + + if let (layout, navigationHeight) = self.containerLayout { + let _ = self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate) } } + @objc private func cancelPressed() { + self.currentController?.cancelSelectingMessages() + } + + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + self.backgroundColor = theme.chatList.backgroundColor + self.searchContentNode.updateTheme(theme) + } + func scrollToTop() { - if self.segmentedControlNode.selectedIndex == 0 { - self.chatController?.scrollToTop?() + if self.searchContentNode.selectedIndex == 0 { + self.currentController?.scrollToTop?() + } else if self.searchContentNode.selectedIndex == 2 { + self.globalController?.scrollToTop?() } else { - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.myController?.scrollToTop?() } } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + let isFirstTime = self.containerLayout == nil self.containerLayout = (layout, navigationBarHeight) - - if self.chatController != nil && self.segmentedControlNode.supernode == nil { - self.navigationBar?.additionalContentNode.addSubnode(self.segmentedControlNode) - } - + var insets = layout.insets(options: [.input]) insets.top += navigationBarHeight let toolbarHeight: CGFloat = 40.0 - let panelY: CGFloat = insets.top - UIScreenPixel - 4.0 + insets.top += toolbarHeight - 4.0 + + if isFirstTime { + self.insertSubnode(self.clippingNode, at: 0) + } + + if let controller = self.currentController { + transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)) + controller.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top - 79.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) + + if controller.displayNode.supernode == nil { + controller.viewWillAppear(false) + self.containerNode.addSubnode(controller.displayNode) + controller.viewDidAppear(false) + + controller.beginMessageSearch(self.query) + } + } - let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: layout.size.width - 14.0 * 2.0), transition: transition) - transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: panelY + 2.0 + floor((toolbarHeight - controlSize.height) / 2.0)), size: controlSize)) + if let controller = self.myController { + transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(x: layout.size.width, y: 0.0), size: layout.size)) + controller.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top - 89.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) + + if controller.displayNode.supernode == nil { + controller.viewWillAppear(false) + self.containerNode.addSubnode(controller.displayNode) + controller.viewDidAppear(false) + + controller.beginMessageSearch(self.query) + } + } - if let chatController = self.chatController { - insets.top += toolbarHeight - 4.0 - let chatSize = CGSize(width: layout.size.width, height: layout.size.height) - transition.updateFrame(node: chatController.displayNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: chatSize)) - chatController.containerLayoutUpdated(ContainerViewLayout(size: chatSize, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) + if let controller = self.globalController { + transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(x: layout.size.width * 2.0, y: 0.0), size: layout.size)) + controller.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top - 89.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) - if chatController.displayNode.supernode == nil { - chatController.viewWillAppear(false) - self.insertSubnode(chatController.displayNode, at: 0) - chatController.viewDidAppear(false) + if controller.displayNode.supernode == nil { + controller.viewWillAppear(false) + self.containerNode.addSubnode(controller.displayNode) + controller.viewDidAppear(false) - chatController.beginMessageSearch(self.query) + controller.beginMessageSearch(self.query) } } - self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) - self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) + transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: .zero, size: layout.size)) + + let containerPosition: CGFloat = -layout.size.width * CGFloat(self.searchContentNode.selectedIndex) - self.panTransitionFraction * layout.size.width + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: containerPosition, y: 0.0), size: CGSize(width: layout.size.width * 3.0, height: layout.size.height))) let overflowInset: CGFloat = 0.0 let topInset = navigationBarHeight self.shimmerNode.frame = CGRect(origin: CGPoint(x: overflowInset, y: topInset), size: CGSize(width: layout.size.width - overflowInset * 2.0, height: layout.size.height)) self.shimmerNode.update(context: self.context, size: CGSize(width: layout.size.width - overflowInset * 2.0, height: layout.size.height), presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, animationCache: self.context.animationCache, animationRenderer: self.context.animationRenderer, key: .chats, hasSelection: false, transition: transition) - insets.top += 4.0 + if isFirstTime { + self.insertSubnode(self.recentListNode, aboveSubnode: self.shimmerNode) + } - let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve) + transition.updateFrame(node: self.recentListNode, frame: CGRect(origin: .zero, size: layout.size)) + self.recentListNode.updateLayout(layout: ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top - 35.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + let recentTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + if self.query.isEmpty { + recentTransition.updateAlpha(node: self.recentListNode, alpha: 1.0) + } else if self.recentListNode.alpha > 0.0 { + Queue.mainQueue().after(0.1, { + if !self.query.isEmpty { + recentTransition.updateAlpha(node: self.recentListNode, alpha: 0.0) + } + }) + } if !self.hasValidLayout { self.hasValidLayout = true - while !self.enqueuedTransitions.isEmpty { - self.dequeueTransition() - } } - if self.chatController != nil { + if self.currentController != nil { return toolbarHeight } else { return 0.0 } } } + +private func cleanHashtag(_ string: String) -> String { + var string = string + if string.hasPrefix("#") { + string.removeFirst() + } + if string.hasPrefix("$") { + string.removeFirst() + } + return string +} diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift new file mode 100644 index 00000000000..4db226fcd84 --- /dev/null +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift @@ -0,0 +1,217 @@ +import Foundation +import UIKit +import SwiftSignalKit +import Postbox +import TelegramCore +import AccountContext + +final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { + private final class Impl { + let queue: Queue + let context: AccountContext + + fileprivate var query: String { + didSet { + if self.query != oldValue { + self.updateHistoryViewRequest(reload: true) + } + } + } + private let publicPosts: Bool + private var currentSearchState: SearchMessagesState? + + private(set) var mergedHistoryView: MessageHistoryView? + private var sourceHistoryView: MessageHistoryView? + + private var historyViewDisposable: Disposable? + let historyViewStream = ValuePipe<(MessageHistoryView, ViewUpdateType)>() + private var nextUpdateIsHoleFill: Bool = false + + var hashtagSearchResultsUpdate: ((SearchMessagesResult, SearchMessagesState)) -> Void = { _ in } + + let isSearchingPromise = ValuePromise(true) + + init(queue: Queue, context: AccountContext, query: String, publicPosts: Bool) { + self.queue = queue + self.context = context + self.query = query + self.publicPosts = publicPosts + + self.updateHistoryViewRequest(reload: false) + } + + deinit { + self.historyViewDisposable?.dispose() + } + + private func updateHistoryViewRequest(reload: Bool) { + guard self.historyViewDisposable == nil || reload else { + return + } + self.historyViewDisposable?.dispose() + + let search: Signal<(SearchMessagesResult, SearchMessagesState), NoError> + if self.publicPosts { + search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: nil) + } else { + search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil), query: "#\(self.query)", state: nil) + } + + self.isSearchingPromise.set(true) + self.historyViewDisposable = (search + |> deliverOn(self.queue)).start(next: { [weak self] result in + guard let self else { + return + } + + let updateType: ViewUpdateType = .Initial + + let historyView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: !result.0.completed, holeLater: false, isLoading: false) + self.sourceHistoryView = historyView + self.updateHistoryView(updateType: updateType) + + Queue.mainQueue().async { + self.currentSearchState = result.1 + + self.hashtagSearchResultsUpdate(result) + } + + self.historyViewDisposable?.dispose() + self.historyViewDisposable = nil + + self.isSearchingPromise.set(false) + }) + } + + private func updateHistoryView(updateType: ViewUpdateType) { + var entries = self.sourceHistoryView?.entries ?? [] + entries.sort(by: { $0.message.index < $1.message.index }) + + let mergedHistoryView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: entries, holeEarlier: self.sourceHistoryView?.holeEarlier ?? false, holeLater: false, isLoading: false) + self.mergedHistoryView = mergedHistoryView + + self.historyViewStream.putNext((mergedHistoryView, updateType)) + } + + func loadMore() { + guard self.historyViewDisposable == nil, let currentSearchState = self.currentSearchState, let sourceHistoryView = self.sourceHistoryView, sourceHistoryView.holeEarlier else { + return + } + + let search: Signal<(SearchMessagesResult, SearchMessagesState), NoError> + if self.publicPosts { + search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: self.currentSearchState) + } else { + search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil), query: "#\(self.query)", state: currentSearchState) + } + + self.historyViewDisposable?.dispose() + self.historyViewDisposable = (search + |> deliverOn(self.queue)).startStrict(next: { [weak self] result in + guard let self else { + return + } + + let updateType: ViewUpdateType = .FillHole + + let historyView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: !result.0.completed, holeLater: false, isLoading: false) + self.sourceHistoryView = historyView + + self.updateHistoryView(updateType: updateType) + + Queue.mainQueue().async { + self.currentSearchState = result.1 + + self.hashtagSearchResultsUpdate(result) + } + + self.historyViewDisposable?.dispose() + self.historyViewDisposable = nil + }) + } + + func enqueueMessages(messages: [EnqueueMessage]) { + } + + func deleteMessages(ids: [EngineMessage.Id]) { + + } + + func editMessage(id: EngineMessage.Id, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool) { + } + } + + var kind: ChatCustomContentsKind + + var historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError> { + return self.impl.signalWith({ impl, subscriber in + if let mergedHistoryView = impl.mergedHistoryView { + subscriber.putNext((mergedHistoryView, .Initial)) + } + return impl.historyViewStream.signal().start(next: subscriber.putNext) + }) + } + + var searching: Signal { + return self.impl.signalWith({ impl, subscriber in + return impl.isSearchingPromise.get().start(next: subscriber.putNext) + }) + } + + var messageLimit: Int? { + return nil + } + + private let queue: Queue + private let impl: QueueLocalObject + + init(context: AccountContext, query: String, publicPosts: Bool) { + self.kind = .hashTagSearch(publicPosts: publicPosts) + + let queue = Queue() + self.queue = queue + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue, context: context, query: query, publicPosts: publicPosts) + }) + } + + func enqueueMessages(messages: [EnqueueMessage]) { + + } + + func deleteMessages(ids: [EngineMessage.Id]) { + + } + + func editMessage(id: EngineMessage.Id, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool) { + + } + + func quickReplyUpdateShortcut(value: String) { + + } + + func businessLinkUpdate(message: String, entities: [TelegramCore.MessageTextEntity], title: String?) { + + } + + func loadMore() { + self.impl.with { impl in + impl.loadMore() + } + } + + var hashtagSearchResultsUpdate: ((SearchMessagesResult, SearchMessagesState)) -> Void = { _ in } { + didSet { + self.impl.with { impl in + impl.hashtagSearchResultsUpdate = self.hashtagSearchResultsUpdate + } + } + } + + func hashtagSearchUpdate(query: String) { + self.impl.with { impl in + impl.query = query + } + } +} diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift new file mode 100644 index 00000000000..ebd74ef10c9 --- /dev/null +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift @@ -0,0 +1,182 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import SearchBarNode +import ComponentFlow +import ComponentDisplayAdapters +import TabSelectorComponent + +private let searchBarFont = Font.regular(17.0) + +final class HashtagSearchNavigationContentNode: NavigationBarContentNode { + private var theme: PresentationTheme + private let strings: PresentationStrings + private let hasCurrentChat: Bool + + private let cancel: () -> Void + + var onReturn: (String) -> Void = { _ in } + + private let searchBar: SearchBarNode + private let tabSelector = ComponentView() + + private var queryUpdated: ((String) -> Void)? + var indexUpdated: ((Int) -> Void)? + + var selectedIndex: Int = 0 { + didSet { + if let (size, leftInset, rightInset) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .animated(duration: 0.35, curve: .spring)) + } + } + } + + var transitionFraction: CGFloat? { + didSet { + if self.transitionFraction != oldValue { + if let (size, leftInset, rightInset) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: self.transitionFraction == nil ? .animated(duration: 0.35, curve: .spring) : .immediate) + } + } + } + } + + var isSearching: Bool = false { + didSet { + self.searchBar.activity = self.isSearching + } + } + + var query: String { + get { + return self.searchBar.text + } + set { + self.searchBar.text = newValue + } + } + + init(theme: PresentationTheme, strings: PresentationStrings, initialQuery: String, hasCurrentChat: Bool, cancel: @escaping () -> Void) { + self.theme = theme + self.strings = strings + self.hasCurrentChat = hasCurrentChat + + self.cancel = cancel + + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, icon: .hashtag, displayBackground: false) + self.searchBar.text = initialQuery + self.searchBar.placeholderString = NSAttributedString(string: strings.HashtagSearch_SearchPlaceholder, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) + + super.init() + + self.searchBar.autocapitalization = .none + + if hasCurrentChat { + self.addSubnode(self.searchBar) + } + + self.searchBar.cancel = { [weak self] in + self?.searchBar.deactivate(clear: false) + self?.cancel() + } + + self.searchBar.textUpdated = { [weak self] query, _ in + self?.queryUpdated?(query) + } + + self.searchBar.textReturned = { [weak self] query in + self?.onReturn(query) + } + } + + override var mode: NavigationBarContentMode { + return self.hasCurrentChat ? .replacement : .expansion + } + + func updateTheme(_ theme: PresentationTheme) { + self.theme = theme + self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: self.strings) + } + + func setQueryUpdated(_ f: @escaping (String) -> Void) { + self.queryUpdated = f + } + + override var nominalHeight: CGFloat { + if self.hasCurrentChat { + return 54.0 + 44.0 + } else { + return 45.0 + } + } + + private var validLayout: (CGSize, CGFloat, CGFloat)? + + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, leftInset, rightInset) + + let sideInset: CGFloat = 6.0 + + let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight + 5.0), size: CGSize(width: size.width, height: 54.0)) + self.searchBar.frame = searchBarFrame + self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset + sideInset, rightInset: rightInset + sideInset, transition: transition) + + var items: [TabSelectorComponent.Item] = [] + if self.hasCurrentChat { + items.append(TabSelectorComponent.Item(id: AnyHashable(0), title: self.strings.HashtagSearch_ThisChat)) + } + items.append(TabSelectorComponent.Item(id: AnyHashable(1), title: self.strings.HashtagSearch_MyMessages)) + items.append(TabSelectorComponent.Item(id: AnyHashable(2), title: self.strings.HashtagSearch_PublicPosts)) + + let tabSelectorSize = self.tabSelector.update( + transition: Transition(transition), + component: AnyComponent(TabSelectorComponent( + colors: TabSelectorComponent.Colors( + foreground: self.theme.list.itemSecondaryTextColor, + selection: self.theme.list.itemAccentColor + ), + customLayout: TabSelectorComponent.CustomLayout( + font: Font.medium(14.0), + spacing: self.hasCurrentChat ? 24.0 : 8.0, + lineSelection: true + ), + items: items, + selectedId: AnyHashable(self.selectedIndex), + setSelectedId: { [weak self] id in + guard let self, let index = id.base as? Int else { + return + } + self.indexUpdated?(index) + }, + transitionFraction: self.transitionFraction + )), + environment: {}, + containerSize: CGSize(width: size.width, height: 44.0) + ) + let tabSelectorFrameOriginX: CGFloat + if self.hasCurrentChat || "".isEmpty { + tabSelectorFrameOriginX = floorToScreenPixels((size.width - tabSelectorSize.width) / 2.0) + } else { + tabSelectorFrameOriginX = 4.0 + } + let tabSelectorFrame = CGRect(origin: CGPoint(x: tabSelectorFrameOriginX, y: size.height - tabSelectorSize.height - 9.0), size: tabSelectorSize) + if let tabSelectorView = self.tabSelector.view { + if tabSelectorView.superview == nil { + self.view.addSubview(tabSelectorView) + } + transition.updateFrame(view: tabSelectorView, frame: tabSelectorFrame) + } + } + + func activate() { + self.searchBar.activate() + } + + func deactivate() { + self.searchBar.deactivate(clear: false) + } +} diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift new file mode 100644 index 00000000000..b6238082571 --- /dev/null +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift @@ -0,0 +1,518 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit +import Display +import TelegramCore +import TelegramPresentationData +import ItemListUI +import MergeLists +import AccountContext + +final class HashtagSearchInteraction { + let setSearchQuery: (String) -> Void + let deleteRecentQuery: (String) -> Void + let clearRecentQueries: () -> Void + + init(setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, clearRecentQueries: @escaping () -> Void) { + self.setSearchQuery = setSearchQuery + self.deleteRecentQuery = deleteRecentQuery + self.clearRecentQueries = clearRecentQueries + } +} + +private enum HashtagSearchRecentQueryStableId: Hashable { + case query(String) + case clear +} + +private enum HashtagSearchRecentQueryEntry: Comparable, Identifiable { + case query(index: Int, text: String) + case clear + + var stableId: HashtagSearchRecentQueryStableId { + switch self { + case let .query(_, text): + return .query(text) + case .clear: + return .clear + } + } + + static func ==(lhs: HashtagSearchRecentQueryEntry, rhs: HashtagSearchRecentQueryEntry) -> Bool { + switch lhs { + case let .query(lhsIndex, lhsText): + if case let .query(rhsIndex, rhsText) = rhs { + return lhsIndex == rhsIndex && lhsText == rhsText + } else { + return false + } + case .clear: + if case .clear = rhs { + return true + } else { + return false + } + } + } + + static func <(lhs: HashtagSearchRecentQueryEntry, rhs: HashtagSearchRecentQueryEntry) -> Bool { + switch lhs { + case let .query(lhsIndex, _): + switch rhs { + case let .query(rhsIndex, _): + return lhsIndex < rhsIndex + case .clear: + return true + } + case .clear: + switch rhs { + case .query: + return false + case .clear: + return true + } + } + } + + func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: HashtagSearchInteraction) -> ListViewItem { + var isClear = false + let text: String + switch self { + case let .query(_, value): + text = value + case .clear: + isClear = true + text = strings.HashtagSearch_ClearRecent + } + return HashtagSearchRecentQueryItem(account: account, theme: theme, strings: strings, query: text, clear: isClear, tapped: { query in + if isClear { + interaction.clearRecentQueries() + } else { + interaction.setSearchQuery(text) + } + }, deleted: { query in + interaction.deleteRecentQuery(query) + }) + } +} + +private struct HashtagSearchRecentTransition { + let deletions: [ListViewDeleteItem] + let insertions: [ListViewInsertItem] + let updates: [ListViewUpdateItem] + let isEmpty: Bool +} + +private func preparedHashtagSearchRecentTransition(from fromEntries: [HashtagSearchRecentQueryEntry], to toEntries: [HashtagSearchRecentQueryEntry], account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: HashtagSearchInteraction) -> HashtagSearchRecentTransition { + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) + + let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, interaction: interaction), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, interaction: interaction), directionHint: nil) } + + return HashtagSearchRecentTransition(deletions: deletions, insertions: insertions, updates: updates, isEmpty: toEntries.isEmpty) +} + +private enum RevealOptionKey: Int32 { + case delete +} + +public class HashtagSearchRecentQueryItem: ListViewItem { + let theme: PresentationTheme + let strings: PresentationStrings + let account: Account + let query: String + let clear: Bool + let tapped: (String) -> Void + let deleted: (String) -> Void + + let header: ListViewItemHeader? = nil + + public init(account: Account, theme: PresentationTheme, strings: PresentationStrings, query: String, clear: Bool, tapped: @escaping (String) -> Void, deleted: @escaping (String) -> Void) { + self.theme = theme + self.strings = strings + self.account = account + self.query = query + self.clear = clear + self.tapped = tapped + self.deleted = deleted + } + + public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = HashtagSearchRecentQueryItemNode() + let makeLayout = node.asyncLayout() + let (nodeLayout, nodeApply) = makeLayout(self, params, nextItem == nil, !(previousItem is HashtagSearchRecentQueryItem)) + node.contentSize = nodeLayout.contentSize + node.insets = nodeLayout.insets + + completion(node, nodeApply) + } + } + + public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? HashtagSearchRecentQueryItemNode { + let layout = nodeValue.asyncLayout() + async { + let (nodeLayout, apply) = layout(self, params, nextItem == nil, !(previousItem is HashtagSearchRecentQueryItem)) + Queue.mainQueue().async { + completion(nodeLayout, { info in + apply().1(info) + }) + } + } + } + } + } + + public var selectable: Bool { + return true + } + + public func selected(listView: ListView) { + listView.clearHighlightAnimated(true) + self.tapped(self.query) + } +} + +final class HashtagSearchRecentQueryItemNode: ItemListRevealOptionsItemNode { + private let backgroundNode: ASDisplayNode + private let separatorNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + private var textNode: TextNode? + private let iconNode: ASImageNode + + private var item: HashtagSearchRecentQueryItem? + private var layoutParams: ListViewItemLayoutParams? + + required init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + + self.separatorNode = ASDisplayNode() + self.separatorNode.isLayerBacked = true + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.separatorNode) + self.addSubnode(self.iconNode) + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + if let item = self.item { + let makeLayout = self.asyncLayout() + let (nodeLayout, nodeApply) = makeLayout(item, params, nextItem == nil, previousItem == nil) + self.contentSize = nodeLayout.contentSize + self.insets = nodeLayout.insets + let _ = nodeApply() + } + } + + override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + super.setHighlighted(highlighted, at: point, animated: animated) + + if highlighted { + self.highlightedBackgroundNode.alpha = 1.0 + if self.highlightedBackgroundNode.supernode == nil { + self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) + } + } else { + if self.highlightedBackgroundNode.supernode != nil { + if animated { + self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in + if let strongSelf = self { + if completed { + strongSelf.highlightedBackgroundNode.removeFromSupernode() + } + } + }) + self.highlightedBackgroundNode.alpha = 0.0 + } else { + self.highlightedBackgroundNode.removeFromSupernode() + } + } + } + } + + func asyncLayout() -> (_ item: HashtagSearchRecentQueryItem, _ params: ListViewItemLayoutParams, _ last: Bool, _ firstWithHeader: Bool) -> (ListViewItemNodeLayout, () -> (Signal?, (ListViewItemApply) -> Void)) { + let currentItem = self.item + + let textLayout = TextNode.asyncLayout(self.textNode) + + return { [weak self] item, params, last, firstWithHeader in + + let leftInset: CGFloat = 62.0 + params.leftInset + let rightInset: CGFloat = params.rightInset + + let attributedString = NSAttributedString(string: item.query, font: Font.regular(17.0), textColor: item.clear ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor) + let textApply = textLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 44.0), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)) + + return (nodeLayout, { [weak self] in + var updatedTheme: PresentationTheme? + if currentItem?.theme !== item.theme { + updatedTheme = item.theme + } + + return (nil, { _ in + if let strongSelf = self { + strongSelf.item = item + strongSelf.layoutParams = params + + if let _ = updatedTheme { + strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor + strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor + if item.clear { + strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Hashtag/ClearRecent"), color: item.theme.list.itemAccentColor) + } else { + strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Hashtag/RecentHashtag"), color: item.theme.list.itemSecondaryTextColor) + } + } + + let (textLayout, textApply) = textApply + let textNode = textApply() + if strongSelf.textNode == nil { + strongSelf.textNode = textNode + strongSelf.addSubnode(textNode) + } + + let textFrame = CGRect(origin: CGPoint(x: leftInset + strongSelf.revealOffset, y: floorToScreenPixels((nodeLayout.contentSize.height - textLayout.size.height) / 2.0)), size: textLayout.size) + textNode.frame = textFrame + + if let icon = strongSelf.iconNode.image { + strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: textFrame.minX - icon.size.width - 16.0 + strongSelf.revealOffset, y: floorToScreenPixels((nodeLayout.contentSize.height - icon.size.height) / 2.0)), size: icon.size) + } + + let separatorHeight = UIScreenPixel + let topHighlightInset: CGFloat = separatorHeight + + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset)) + strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) + strongSelf.separatorNode.isHidden = last + + strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) + + if item.clear { + strongSelf.setRevealOptions((left: [], right: [])) + } else { + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + } + } + }) + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false) + } + + override public func headers() -> [ListViewItemHeader]? { + if let item = self.item { + return item.header.flatMap { [$0] } + } else { + return nil + } + } + + override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { + super.updateRevealOffset(offset: offset, transition: transition) + + if let params = self.layoutParams, let textNode = self.textNode { + let leftInset: CGFloat = 62.0 + params.leftInset + + var textFrame = textNode.frame + textFrame.origin.x = leftInset + offset + transition.updateFrame(node: textNode, frame: textFrame) + + var iconFrame = self.iconNode.frame + iconFrame.origin.x = textFrame.minX - iconFrame.width - 16.0 + transition.updateFrame(node: self.iconNode, frame: iconFrame) + } + } + + override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { + if let item = self.item { + switch option.key { + case RevealOptionKey.delete.rawValue: + item.deleted(item.query) + default: + break + } + } + self.setRevealOptionsOpened(false, animated: true) + self.revealOptionsInteractivelyClosed() + } +} + +final class HashtagSearchRecentListNode: ASDisplayNode { + private let context: AccountContext + private var presentationData: PresentationData + + private let listNode: ListView + + private let emptyIconNode: ASImageNode + private let emptyTextNode: ImmediateTextNode + + private var enqueuedRecentTransitions: [(HashtagSearchRecentTransition, Bool)] = [] + private var recentDisposable: Disposable? + + private var validLayout: ContainerViewLayout? + + private var interaction: HashtagSearchInteraction? + + var setSearchQuery: (String) -> Void = { _ in } + + init(context: AccountContext) { + self.context = context + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.presentationData = presentationData + + self.listNode = ListView() + self.listNode.accessibilityPageScrolledString = { row, count in + return presentationData.strings.VoiceOver_ScrollStatus(row, count).string + } + + self.emptyIconNode = ASImageNode() + self.emptyIconNode.displaysAsynchronously = false + + self.emptyTextNode = ImmediateTextNode() + self.emptyTextNode.displaysAsynchronously = false + self.emptyTextNode.maximumNumberOfLines = 0 + self.emptyTextNode.textAlignment = .center + + super.init() + + self.addSubnode(self.listNode) + self.addSubnode(self.emptyIconNode) + self.addSubnode(self.emptyTextNode) + + self.interaction = HashtagSearchInteraction( + setSearchQuery: { [weak self] query in + self?.setSearchQuery(query) + }, + deleteRecentQuery: { query in + let _ = removeRecentHashtagSearchQuery(engine: context.engine, string: query).startStandalone() + }, + clearRecentQueries: { + let _ = clearRecentHashtagSearchQueries(engine: context.engine).startStandalone() + } + ) + + self.listNode.beganInteractiveDragging = { [weak self] _ in + self?.view.window?.endEditing(true) + } + + let previousRecentItems = Atomic<[HashtagSearchRecentQueryEntry]?>(value: nil) + self.recentDisposable = (hashtagSearchRecentQueries(engine: self.context.engine) + |> deliverOnMainQueue).start(next: { [weak self] queries in + guard let self else { + return + } + var entries: [HashtagSearchRecentQueryEntry] = [] + for i in 0 ..< queries.count { + entries.append(.query(index: i, text: queries[i])) + } + + if !entries.isEmpty { + entries.append(.clear) + } + + let previousEntries = previousRecentItems.swap(entries) + + let transition = preparedHashtagSearchRecentTransition(from: previousEntries ?? [], to: entries, account: context.account, theme: self.presentationData.theme, strings: self.presentationData.strings, interaction: self.interaction!) + self.enqueueRecentTransition(transition, firstTime: previousEntries == nil) + }) + + self.updatePresentationData(self.presentationData) + } + + deinit { + self.recentDisposable?.dispose() + } + + private func updatePresentationData(_ presentationData: PresentationData) { + self.presentationData = presentationData + + self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + + self.emptyIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Hashtag/EmptyHashtag"), color: self.presentationData.theme.list.freeMonoIconColor) + self.emptyTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.HashtagSearch_NoRecentQueries, font: Font.regular(15.0), textColor: self.presentationData.theme.list.freeTextColor) + } + + private func enqueueRecentTransition(_ transition: HashtagSearchRecentTransition, firstTime: Bool) { + enqueuedRecentTransitions.append((transition, firstTime)) + + if let _ = self.validLayout { + while !self.enqueuedRecentTransitions.isEmpty { + self.dequeueRecentTransition() + } + } + } + + private func dequeueRecentTransition() { + if let (transition, firstTime) = self.enqueuedRecentTransitions.first { + self.enqueuedRecentTransitions.remove(at: 0) + + var options = ListViewDeleteAndInsertOptions() + if firstTime { + options.insert(.PreferSynchronousDrawing) + } else { + options.insert(.AnimateInsertion) + } + + self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in + guard let self else { + return + } + + self.emptyIconNode.isHidden = !transition.isEmpty + self.emptyTextNode.isHidden = !transition.isEmpty + }) + } + } + + func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + + let insets: UIEdgeInsets = layout.insets(options: [.input]) + + let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) + self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + + if let emptyIconSize = self.emptyIconNode.image?.size { + let topInset: CGFloat = insets.top + let bottomInset: CGFloat = insets.bottom + + let sideInset: CGFloat = 0.0 + let padding: CGFloat = 16.0 + let emptyTextSize = self.emptyTextNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0 - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) + + let emptyTextSpacing: CGFloat = 6.0 + let emptyTotalHeight = emptyIconSize.height + emptyTextSpacing + emptyTextSize.height + let emptyOriginY = topInset + floorToScreenPixels((layout.size.height - topInset - bottomInset - emptyTotalHeight) / 2.0) + + transition.updateFrame(node: self.emptyIconNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (layout.size.width - sideInset * 2.0 - padding * 2.0 - emptyIconSize.width) / 2.0, y: emptyOriginY), size: emptyIconSize)) + transition.updateFrame(node: self.emptyTextNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (layout.size.width - sideInset * 2.0 - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyOriginY + emptyIconSize.height + emptyTextSpacing), size: emptyTextSize)) + } + } +} diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchRecentQueries.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentQueries.swift new file mode 100644 index 00000000000..bca1b834977 --- /dev/null +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentQueries.swift @@ -0,0 +1,68 @@ +import Foundation +import Postbox +import TelegramCore +import SwiftSignalKit +import TelegramUIPreferences + +private struct HashtagSearchRecentQueryItemId { + public let rawValue: MemoryBuffer + + var value: String { + return String(data: self.rawValue.makeData(), encoding: .utf8) ?? "" + } + + init(_ rawValue: MemoryBuffer) { + self.rawValue = rawValue + } + + init?(_ value: String) { + if let data = value.data(using: .utf8) { + self.rawValue = MemoryBuffer(data: data) + } else { + return nil + } + } +} + +public final class RecentHashtagSearchQueryItem: Codable { + public init() { + } + + public init(from decoder: Decoder) throws { + } + + public func encode(to encoder: Encoder) throws { + } +} + +func addRecentHashtagSearchQuery(engine: TelegramEngine, string: String) -> Signal { + if let itemId = HashtagSearchRecentQueryItemId(string) { + return engine.orderedLists.addOrMoveToFirstPosition(collectionId: ApplicationSpecificOrderedItemListCollectionId.hashtagSearchRecentQueries, id: itemId.rawValue, item: RecentHashtagSearchQueryItem(), removeTailIfCountExceeds: 100) + } else { + return .complete() + } +} + +func removeRecentHashtagSearchQuery(engine: TelegramEngine, string: String) -> Signal { + if let itemId = HashtagSearchRecentQueryItemId(string) { + return engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.hashtagSearchRecentQueries, id: itemId.rawValue) + } else { + return .complete() + } +} + +func clearRecentHashtagSearchQueries(engine: TelegramEngine) -> Signal { + return engine.orderedLists.clear(collectionId: ApplicationSpecificOrderedItemListCollectionId.hashtagSearchRecentQueries) +} + +func hashtagSearchRecentQueries(engine: TelegramEngine) -> Signal<[String], NoError> { + return engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: ApplicationSpecificOrderedItemListCollectionId.hashtagSearchRecentQueries)) + |> map { items -> [String] in + var result: [String] = [] + for item in items { + let value = HashtagSearchRecentQueryItemId(item.id).value + result.append(value) + } + return result + } +} diff --git a/submodules/HexColor/BUILD b/submodules/HexColor/BUILD index 4dfd5a4ec72..88c4adf491a 100644 --- a/submodules/HexColor/BUILD +++ b/submodules/HexColor/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TextFormat:TextFormat", diff --git a/submodules/HorizontalPeerItem/BUILD b/submodules/HorizontalPeerItem/BUILD index 36c2ddebc88..5653b5ae7f7 100644 --- a/submodules/HorizontalPeerItem/BUILD +++ b/submodules/HorizontalPeerItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ICloudResources/BUILD b/submodules/ICloudResources/BUILD index b282708cee7..e29786c3443 100644 --- a/submodules/ICloudResources/BUILD +++ b/submodules/ICloudResources/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore", diff --git a/submodules/ImageBlur/BUILD b/submodules/ImageBlur/BUILD index 08d7c3d40a8..829c155c02e 100644 --- a/submodules/ImageBlur/BUILD +++ b/submodules/ImageBlur/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/FastBlur:FastBlur", diff --git a/submodules/ImageCompression/BUILD b/submodules/ImageCompression/BUILD index fe40c802c39..a16b98e8f40 100644 --- a/submodules/ImageCompression/BUILD +++ b/submodules/ImageCompression/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/MozjpegBinding:MozjpegBinding", diff --git a/submodules/ImageContentAnalysis/BUILD b/submodules/ImageContentAnalysis/BUILD index cfcaa9efbbb..70301e06048 100644 --- a/submodules/ImageContentAnalysis/BUILD +++ b/submodules/ImageContentAnalysis/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - #"-warnings-as-errors", + ##"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/ImageTransparency/BUILD b/submodules/ImageTransparency/BUILD index 19c5699fcb2..df33c422ddd 100644 --- a/submodules/ImageTransparency/BUILD +++ b/submodules/ImageTransparency/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - #"-warnings-as-errors", + ##"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/ImageTransparency/Sources/ImageTransparency.swift b/submodules/ImageTransparency/Sources/ImageTransparency.swift index e7b2804c03b..f05e77c4159 100644 --- a/submodules/ImageTransparency/Sources/ImageTransparency.swift +++ b/submodules/ImageTransparency/Sources/ImageTransparency.swift @@ -56,6 +56,21 @@ private func generateHistogram(cgImage: CGImage) -> ([[vImagePixelCount]], Int)? return ([histogramBinZero, histogramBinOne, histogramBinTwo, histogramBinThree], alphaBinIndex) } +public func imageHasSubject(_ image: UIImage) -> Bool { + guard let cgImage = image.cgImage, cgImage.bitsPerComponent == 8, cgImage.bitsPerPixel == 32 else { + return false + } + if let (histogramBins, _) = generateHistogram(cgImage: cgImage) { + var totalCount: vImagePixelCount = 0 + for i in 0 ..< 255 { + totalCount += histogramBins[1][i] + } + let opaqueCount: vImagePixelCount = histogramBins[1][255] + return Double(opaqueCount) / Double(totalCount) > 0.05 + } + return false +} + public func imageHasTransparency(_ image: UIImage) -> Bool { guard let cgImage = image.cgImage, cgImage.bitsPerComponent == 8, cgImage.bitsPerPixel == 32 else { return false diff --git a/submodules/ImportStickerPackUI/BUILD b/submodules/ImportStickerPackUI/BUILD index 0c44d12e089..81410bfc7e4 100644 --- a/submodules/ImportStickerPackUI/BUILD +++ b/submodules/ImportStickerPackUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/InAppPurchaseManager/BUILD b/submodules/InAppPurchaseManager/BUILD index 8cbb34b326d..451dc521336 100644 --- a/submodules/InAppPurchaseManager/BUILD +++ b/submodules/InAppPurchaseManager/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index 2a78a2f056a..e2f0c79b932 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -26,7 +26,21 @@ private let productIdentifiers = [ "org.telegram.telegramPremium.threeMonths.code_x10", "org.telegram.telegramPremium.sixMonths.code_x10", - "org.telegram.telegramPremium.twelveMonths.code_x10" + "org.telegram.telegramPremium.twelveMonths.code_x10", + + "org.telegram.telegramStars.topup.x15", + "org.telegram.telegramStars.topup.x25", + "org.telegram.telegramStars.topup.x50", + "org.telegram.telegramStars.topup.x75", + "org.telegram.telegramStars.topup.x100", + "org.telegram.telegramStars.topup.x150", + "org.telegram.telegramStars.topup.x250", + "org.telegram.telegramStars.topup.x350", + "org.telegram.telegramStars.topup.x500", + "org.telegram.telegramStars.topup.x750", + "org.telegram.telegramStars.topup.x1000", + "org.telegram.telegramStars.topup.x1500", + "org.telegram.telegramStars.topup.x2500" ] private extension NSDecimalNumber { @@ -170,6 +184,7 @@ public final class InAppPurchaseManager: NSObject { case notAllowed case cantMakePayments case assignFailed + case tryLater } public enum RestoreState { @@ -205,6 +220,8 @@ public final class InAppPurchaseManager: NSObject { private let stateQueue = Queue() private var paymentContexts: [String: PaymentTransactionContext] = [:] + + private var finishedSuccessfulTransactions = Set() private var onRestoreCompletion: ((RestoreState) -> Void)? @@ -301,6 +318,12 @@ public final class InAppPurchaseManager: NSObject { mappedError = .network case .paymentNotAllowed, .clientInvalid: mappedError = .notAllowed + case .unknown: + if let _ = error.userInfo["tryLater"] { + mappedError = .tryLater + } else { + mappedError = .generic + } default: mappedError = .generic } @@ -386,9 +409,15 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { let transactionState: TransactionState? switch transaction.transactionState { case .purchased: - Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") purchased") - transactionState = .purchased(transactionId: transaction.transactionIdentifier) - transactionsToAssign.append(transaction) + if transaction.payment.productIdentifier.contains(".topup."), let transactionIdentifier = transaction.transactionIdentifier, self.finishedSuccessfulTransactions.contains(transactionIdentifier) { + Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") seems to be already reported, ask to try later") + transactionState = .failed(error: SKError(SKError.Code.unknown, userInfo: ["tryLater": true])) + queue.finishTransaction(transaction) + } else { + Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") purchased") + transactionState = .purchased(transactionId: transaction.transactionIdentifier) + transactionsToAssign.append(transaction) + } case .restored: Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "") restroring") let transactionIdentifier = transaction.transactionIdentifier @@ -476,6 +505,12 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { self.debugSaveReceipt(receiptData: receiptData) #endif + for transaction in transactionsToAssign { + if let transactionIdentifier = transaction.transactionIdentifier { + self.finishedSuccessfulTransactions.insert(transactionIdentifier) + } + } + self.disposableSet.set( (purpose |> castError(AssignAppStoreTransactionError.self) @@ -582,6 +617,7 @@ private final class PendingInAppPurchaseState: Codable { case prizeDescription case randomId case untilDate + case stars } enum PurposeType: Int32 { @@ -591,6 +627,7 @@ private final class PendingInAppPurchaseState: Codable { case gift case giftCode case giveaway + case stars } case subscription @@ -599,6 +636,7 @@ private final class PendingInAppPurchaseState: Codable { case gift(peerId: EnginePeer.Id) case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?) case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32) + case stars(count: Int64) public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -631,6 +669,8 @@ private final class PendingInAppPurchaseState: Codable { randomId: try container.decode(Int64.self, forKey: .randomId), untilDate: try container.decode(Int32.self, forKey: .untilDate) ) + case .stars: + self = .stars(count: try container.decode(Int64.self, forKey: .stars)) default: throw DecodingError.generic } @@ -663,6 +703,9 @@ private final class PendingInAppPurchaseState: Codable { try container.encodeIfPresent(prizeDescription, forKey: .prizeDescription) try container.encode(randomId, forKey: .randomId) try container.encode(untilDate, forKey: .untilDate) + case let .stars(count): + try container.encode(PurposeType.stars.rawValue, forKey: .type) + try container.encode(count, forKey: .stars) } } @@ -680,6 +723,8 @@ private final class PendingInAppPurchaseState: Codable { self = .giftCode(peerIds: peerIds, boostPeer: boostPeer) case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, _, _): self = .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate) + case let .stars(count, _, _): + self = .stars(count: count) } } @@ -698,6 +743,8 @@ private final class PendingInAppPurchaseState: Codable { return .giftCode(peerIds: peerIds, boostPeer: boostPeer, currency: currency, amount: amount) case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate): return .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount) + case let .stars(count): + return .stars(count: count, currency: currency, amount: amount) } } } diff --git a/submodules/InstantPageCache/BUILD b/submodules/InstantPageCache/BUILD index 0b2afced142..bf71b63402c 100644 --- a/submodules/InstantPageCache/BUILD +++ b/submodules/InstantPageCache/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/InstantPageUI/BUILD b/submodules/InstantPageUI/BUILD index 6b849ce87a5..5d75f41cbad 100644 --- a/submodules/InstantPageUI/BUILD +++ b/submodules/InstantPageUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index f27a0b7f070..c79d463bd5b 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -1375,8 +1375,10 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { default: break } - }, sendFile: nil, + }, + sendFile: nil, sendSticker: nil, + sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { c, a in diff --git a/submodules/InvisibleInkDustNode/BUILD b/submodules/InvisibleInkDustNode/BUILD index 596e6986132..93acf4bbb42 100644 --- a/submodules/InvisibleInkDustNode/BUILD +++ b/submodules/InvisibleInkDustNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift index 98a4155585d..e46a510fd97 100644 --- a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift +++ b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift @@ -52,7 +52,7 @@ public class InvisibleInkDustView: UIView { private var animColor: CGColor? private let enableAnimations: Bool - private weak var textNode: TextNode? + private weak var textNode: ASDisplayNode? private let textMaskNode: ASDisplayNode private let textSpotNode: ASImageNode @@ -69,7 +69,7 @@ public class InvisibleInkDustView: UIView { public var isRevealed = false private var isExploding = false - public init(textNode: TextNode?, enableAnimations: Bool) { + public init(textNode: ASDisplayNode?, enableAnimations: Bool) { self.textNode = textNode self.enableAnimations = enableAnimations @@ -368,7 +368,7 @@ public class InvisibleInkDustNode: ASDisplayNode { private var animColor: CGColor? private let enableAnimations: Bool - private weak var textNode: TextNode? + private weak var textNode: ASDisplayNode? private let textMaskNode: ASDisplayNode private let textSpotNode: ASImageNode @@ -385,7 +385,7 @@ public class InvisibleInkDustNode: ASDisplayNode { public var isRevealed = false private var isExploding = false - public init(textNode: TextNode?, enableAnimations: Bool) { + public init(textNode: ASDisplayNode?, enableAnimations: Bool) { self.textNode = textNode self.enableAnimations = enableAnimations diff --git a/submodules/InvisibleInkDustNode/Sources/MediaDustNode.swift b/submodules/InvisibleInkDustNode/Sources/MediaDustNode.swift index 31ad26614bd..d6af2f9e1a2 100644 --- a/submodules/InvisibleInkDustNode/Sources/MediaDustNode.swift +++ b/submodules/InvisibleInkDustNode/Sources/MediaDustNode.swift @@ -211,7 +211,7 @@ public class MediaDustNode: ASDisplayNode { self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap(_:)))) } - @objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) { + public func tap(at location: CGPoint) { guard !self.isRevealed else { return } @@ -223,13 +223,12 @@ public class MediaDustNode: ASDisplayNode { if self.enableAnimations { self.isExploding = true - let position = gestureRecognizer.location(in: self.view) self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled") - self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position") + self.emitterLayer?.setValue(location, forKeyPath: "emitterBehaviors.fingerAttractor.position") let maskSize = self.emitterNode.frame.size Queue.concurrentDefaultQueue().async { - let emitterMaskImage = generateMaskImage(size: maskSize, position: position, inverse: true) + let emitterMaskImage = generateMaskImage(size: maskSize, position: location, inverse: true) Queue.mainQueue().async { self.emitterSpotNode.image = emitterMaskImage @@ -237,8 +236,8 @@ public class MediaDustNode: ASDisplayNode { } Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) { - let xFactor = (position.x / self.emitterNode.frame.width - 0.5) * 2.0 - let yFactor = (position.y / self.emitterNode.frame.height - 0.5) * 2.0 + let xFactor = (location.x / self.emitterNode.frame.width - 0.5) * 2.0 + let yFactor = (location.y / self.emitterNode.frame.height - 0.5) * 2.0 let maxFactor = max(abs(xFactor), abs(yFactor)) let scaleAddition = maxFactor * 4.0 @@ -247,8 +246,8 @@ public class MediaDustNode: ASDisplayNode { self.supernode?.view.mask = self.emitterMaskNode.view self.emitterSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0) - self.emitterSpotNode.layer.anchorPoint = CGPoint(x: position.x / self.emitterMaskNode.frame.width, y: position.y / self.emitterMaskNode.frame.height) - self.emitterSpotNode.position = position + self.emitterSpotNode.layer.anchorPoint = CGPoint(x: location.x / self.emitterMaskNode.frame.width, y: location.y / self.emitterMaskNode.frame.height) + self.emitterSpotNode.position = location self.emitterSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.45 + durationAddition, removeOnCompletion: false, completion: { [weak self] _ in self?.revealed() self?.alpha = 0.0 @@ -272,6 +271,11 @@ public class MediaDustNode: ASDisplayNode { }) } } + + @objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) { + let location = gestureRecognizer.location(in: self.view) + self.tap(at: location) + } private var didSetupAnimations = false private func setupRandomAnimations() { diff --git a/submodules/InviteLinksUI/BUILD b/submodules/InviteLinksUI/BUILD index 1e3b637e4d7..2b8ae6f6a49 100644 --- a/submodules/InviteLinksUI/BUILD +++ b/submodules/InviteLinksUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ItemListAddressItem/BUILD b/submodules/ItemListAddressItem/BUILD index 590b8aaa229..74e35f45ab3 100644 --- a/submodules/ItemListAddressItem/BUILD +++ b/submodules/ItemListAddressItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ItemListAvatarAndNameInfoItem/BUILD b/submodules/ItemListAvatarAndNameInfoItem/BUILD index e24785c8552..54578730b2f 100644 --- a/submodules/ItemListAvatarAndNameInfoItem/BUILD +++ b/submodules/ItemListAvatarAndNameInfoItem/BUILD @@ -7,12 +7,13 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", + "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/PeerPresenceStatusManager:PeerPresenceStatusManager", diff --git a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift index efc5bc8c846..7c467645b29 100644 --- a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift +++ b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import AsyncDisplayKit +import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -60,7 +61,7 @@ public enum ItemListAvatarAndNameInfoItemName: Equatable { } } - public func composedDisplayTitle(context: AccountContext, strings: PresentationStrings) -> String { + public func composedDisplayTitle(context: AccountContext?, strings: PresentationStrings) -> String { switch self { case let .personName(firstName, lastName, phone): if !firstName.isEmpty { @@ -72,7 +73,11 @@ public enum ItemListAvatarAndNameInfoItemName: Equatable { } else if !lastName.isEmpty { return lastName } else if !phone.isEmpty { - return formatPhoneNumber(context: context, number: "+\(phone)") + if let context { + return formatPhoneNumber(context: context, number: "+\(phone)") + } else { + return "+\(phone)" + } } else { return strings.User_DeletedAccount } @@ -127,7 +132,18 @@ public enum ItemListAvatarAndNameInfoItemMode { } public class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem { - let accountContext: AccountContext + public enum ItemContext { + case accountContext(AccountContext) + case other(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network) + + var accountContext: AccountContext? { + if case let .accountContext(accountContext) = self { + return accountContext + } + return nil + } + } + let itemContext: ItemContext let presentationData: ItemListPresentationData let dateTimeFormat: PresentationDateTimeFormat let mode: ItemListAvatarAndNameInfoItemMode @@ -150,8 +166,8 @@ public class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem { public let selectable: Bool - public init(accountContext: AccountContext, presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, mode: ItemListAvatarAndNameInfoItemMode, peer: EnginePeer?, presence: EnginePeer.Presence?, label: String? = nil, memberCount: Int?, state: ItemListAvatarAndNameInfoItemState, sectionId: ItemListSectionId, style: ItemListAvatarAndNameInfoItemStyle, editingNameUpdated: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, editingNameCompleted: @escaping () -> Void = {}, avatarTapped: @escaping () -> Void, context: ItemListAvatarAndNameInfoItemContext? = nil, updatingImage: ItemListAvatarAndNameInfoItemUpdatingAvatar? = nil, call: (() -> Void)? = nil, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, tag: ItemListItemTag? = nil) { - self.accountContext = accountContext + public init(itemContext: ItemContext, presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, mode: ItemListAvatarAndNameInfoItemMode, peer: EnginePeer?, presence: EnginePeer.Presence?, label: String? = nil, memberCount: Int?, state: ItemListAvatarAndNameInfoItemState, sectionId: ItemListSectionId, style: ItemListAvatarAndNameInfoItemStyle, editingNameUpdated: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, editingNameCompleted: @escaping () -> Void = {}, avatarTapped: @escaping () -> Void, context: ItemListAvatarAndNameInfoItemContext? = nil, updatingImage: ItemListAvatarAndNameInfoItemUpdatingAvatar? = nil, call: (() -> Void)? = nil, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, tag: ItemListItemTag? = nil) { + self.itemContext = itemContext self.presentationData = presentationData self.dateTimeFormat = dateTimeFormat self.mode = mode @@ -358,7 +374,12 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo updatedTheme = item.presentationData.theme } - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.accountContext.currentAppConfiguration.with { $0 }) + let premiumConfiguration: PremiumConfiguration + if case let .accountContext(context) = item.itemContext { + premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + } else { + premiumConfiguration = .defaultValue + } var credibilityIconImage: UIImage? var credibilityIconOffset: CGFloat = 4.0 @@ -395,7 +416,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo nameMaximumNumberOfLines = 2 } - let (nameNodeLayout, nameNodeApply) = layoutNameNode(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayTitle.composedDisplayTitle(context: item.accountContext, strings: item.presentationData.strings), font: nameFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: nameMaximumNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: baseWidth - 20 - 94.0 - (item.call != nil ? 36.0 : 0.0) - additionalTitleInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (nameNodeLayout, nameNodeApply) = layoutNameNode(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayTitle.composedDisplayTitle(context: item.itemContext.accountContext, strings: item.presentationData.strings), font: nameFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: nameMaximumNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: baseWidth - 20 - 94.0 - (item.call != nil ? 36.0 : 0.0) - additionalTitleInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) var statusText: String = "" let statusColor: UIColor @@ -404,7 +425,11 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo switch item.mode { case .settings: if let phone = peer.phone, !phone.isEmpty { - statusText += formatPhoneNumber(context: item.accountContext, number: phone) + if let accountContext = item.itemContext.accountContext { + statusText += formatPhoneNumber(context: accountContext, number: phone) + } else { + statusText += formatPhoneNumber(phone) + } } if let username = peer.addressName, !username.isEmpty { if !statusText.isEmpty { @@ -669,7 +694,13 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo overrideImage = .deletedIcon } - strongSelf.avatarNode.setPeer(context: item.accountContext, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: ignoreEmpty ? nil : item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads) + switch item.itemContext { + case let .accountContext(context): + strongSelf.avatarNode.setPeer(context: context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: ignoreEmpty ? nil : item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads) + case let .other(accountPeerId, postbox, network): + strongSelf.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: postbox, network: network, contentSettings: .default, theme: item.presentationData.theme, peer: peer, authorOfMessage: nil, overrideImage: overrideImage, emptyColor: ignoreEmpty ? nil : item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads) + } + } let avatarFrame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floor((layout.contentSize.height - 66.0) / 2.0)), size: CGSize(width: 66.0, height: 66.0)) diff --git a/submodules/ItemListPeerActionItem/BUILD b/submodules/ItemListPeerActionItem/BUILD index 520700b146c..2e0e48e3353 100644 --- a/submodules/ItemListPeerActionItem/BUILD +++ b/submodules/ItemListPeerActionItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ItemListPeerItem/BUILD b/submodules/ItemListPeerItem/BUILD index b5fb4ad41cd..ec0ddc41bf8 100644 --- a/submodules/ItemListPeerItem/BUILD +++ b/submodules/ItemListPeerItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ItemListStickerPackItem/BUILD b/submodules/ItemListStickerPackItem/BUILD index 5752bd50bc7..13ba8a7fcdc 100644 --- a/submodules/ItemListStickerPackItem/BUILD +++ b/submodules/ItemListStickerPackItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ItemListUI/BUILD b/submodules/ItemListUI/BUILD index ab4bb1f6cf2..0711e33ed21 100644 --- a/submodules/ItemListUI/BUILD +++ b/submodules/ItemListUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 35785719f84..07c3cc8c8b1 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -905,11 +905,7 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { if let validLayout = self.validLayout { updatedNode.updateLayout(layout: validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate) } - if self.rightOverlayNode.supernode != nil { - self.insertSubnode(updatedNode, aboveSubnode: self.rightOverlayNode) - } else { - self.insertSubnode(updatedNode, aboveSubnode: self.listNode) - } + self.addSubnode(updatedNode) updatedNode.activate() } } else { diff --git a/submodules/ItemListVenueItem/BUILD b/submodules/ItemListVenueItem/BUILD index cb081af3c68..e3bc70e81c6 100644 --- a/submodules/ItemListVenueItem/BUILD +++ b/submodules/ItemListVenueItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/JoinLinkPreviewUI/BUILD b/submodules/JoinLinkPreviewUI/BUILD index 480a72f6fdc..1234038005c 100644 --- a/submodules/JoinLinkPreviewUI/BUILD +++ b/submodules/JoinLinkPreviewUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/LanguageLinkPreviewUI/BUILD b/submodules/LanguageLinkPreviewUI/BUILD index 6d40c1c2448..bc3bf1af87f 100644 --- a/submodules/LanguageLinkPreviewUI/BUILD +++ b/submodules/LanguageLinkPreviewUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/LanguageSuggestionUI/BUILD b/submodules/LanguageSuggestionUI/BUILD index b2e31d19ab8..5cf4e848ae8 100644 --- a/submodules/LanguageSuggestionUI/BUILD +++ b/submodules/LanguageSuggestionUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/LegacyMediaPickerUI/BUILD b/submodules/LegacyMediaPickerUI/BUILD index 63dd5dde657..abc4fbcdc47 100644 --- a/submodules/LegacyMediaPickerUI/BUILD +++ b/submodules/LegacyMediaPickerUI/BUILD @@ -11,7 +11,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index 6f668dfad33..52cbe8cf301 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -469,7 +469,7 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer?, threadTit present(legacyController, nil) - TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: NSAttributedString(), withItem: item, paint: false, adjustments: false, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: [], immediate: false, appeared: { + TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, withItem: item, paint: false, adjustments: false, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: [], immediate: false, appeared: { }, completion: { result, editingContext in let nativeGenerator = legacyAssetPickerItemGenerator() var selectableResult: TGMediaSelectableItem? diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index f82839b3a51..eabf62b742e 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -881,10 +881,20 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A fileAttributes.append(.Animated) } } + + if estimatedSize > 10 * 1024 * 1024 { + fileAttributes.append(.hintFileIsLarge) + } + } else { + if case let .asset(asset) = data, let phAsset = asset.backingAsset, let resource = PHAssetResource.assetResources(for: phAsset).last, let sizeValue = resource.value(forKey: "fileSize") as? CLong, sizeValue > 0 { + let size = Int64(bitPattern: UInt64(sizeValue)) + + if size > 10 * 1024 * 1024 { + fileAttributes.append(.hintFileIsLarge) + } + } } - if estimatedSize > 10 * 1024 * 1024 { - fileAttributes.append(.hintFileIsLarge) - } + var attributes: [MessageAttribute] = [] diff --git a/submodules/LegacyUI/BUILD b/submodules/LegacyUI/BUILD index d398f76aed3..d04cc410414 100644 --- a/submodules/LegacyUI/BUILD +++ b/submodules/LegacyUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ListMessageItem/BUILD b/submodules/ListMessageItem/BUILD index 8c143e954d8..46bfeba44ae 100644 --- a/submodules/ListMessageItem/BUILD +++ b/submodules/ListMessageItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ListSectionHeaderNode/BUILD b/submodules/ListSectionHeaderNode/BUILD index 38262bee999..cf781c889c2 100644 --- a/submodules/ListSectionHeaderNode/BUILD +++ b/submodules/ListSectionHeaderNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/LiveLocationManager/BUILD b/submodules/LiveLocationManager/BUILD index 47b328632a7..20cdde0fe70 100644 --- a/submodules/LiveLocationManager/BUILD +++ b/submodules/LiveLocationManager/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/LiveLocationPositionNode/BUILD b/submodules/LiveLocationPositionNode/BUILD index b57e84dcfb3..faccb6ccfd5 100644 --- a/submodules/LiveLocationPositionNode/BUILD +++ b/submodules/LiveLocationPositionNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/LiveLocationTimerNode/BUILD b/submodules/LiveLocationTimerNode/BUILD index 46781af862f..c3025f04076 100644 --- a/submodules/LiveLocationTimerNode/BUILD +++ b/submodules/LiveLocationTimerNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/LocalAuth/BUILD b/submodules/LocalAuth/BUILD index 73e432ce8ee..532e1e35a76 100644 --- a/submodules/LocalAuth/BUILD +++ b/submodules/LocalAuth/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/LocalMediaResources/BUILD b/submodules/LocalMediaResources/BUILD index b0f3f832fe5..ee25a8de77b 100644 --- a/submodules/LocalMediaResources/BUILD +++ b/submodules/LocalMediaResources/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/LocalizedPeerData/BUILD b/submodules/LocalizedPeerData/BUILD index 14f8343c3c1..f1530539782 100644 --- a/submodules/LocalizedPeerData/BUILD +++ b/submodules/LocalizedPeerData/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/LocationResources/BUILD b/submodules/LocationResources/BUILD index 38cdd05e3ad..6885f640909 100644 --- a/submodules/LocationResources/BUILD +++ b/submodules/LocationResources/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/LocationUI/BUILD b/submodules/LocationUI/BUILD index 4d8245d7ad6..1d59489dc9e 100644 --- a/submodules/LocationUI/BUILD +++ b/submodules/LocationUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index d6430907db3..61f0ad4f4d5 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -393,6 +393,17 @@ private final class LocationPickerContext: AttachmentMediaPickerContext { return .single(nil) } + var hasCaption: Bool { + return false + } + + var captionIsAboveMedia: Signal { + return .single(false) + } + + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { + } + public var loadingProgress: Signal { return .single(nil) } @@ -404,10 +415,10 @@ private final class LocationPickerContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { } - func schedule() { + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { } func mainButtonAction() { diff --git a/submodules/ManagedAnimationNode/BUILD b/submodules/ManagedAnimationNode/BUILD index 2cdef0d2246..0aef41bd20a 100644 --- a/submodules/ManagedAnimationNode/BUILD +++ b/submodules/ManagedAnimationNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/ManagedFile/BUILD b/submodules/ManagedFile/BUILD index ec4ed331cb8..64036928b8a 100644 --- a/submodules/ManagedFile/BUILD +++ b/submodules/ManagedFile/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/MapResourceToAvatarSizes/BUILD b/submodules/MapResourceToAvatarSizes/BUILD index 0db63358857..1015625be7d 100644 --- a/submodules/MapResourceToAvatarSizes/BUILD +++ b/submodules/MapResourceToAvatarSizes/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/Markdown/BUILD b/submodules/Markdown/BUILD index f206072b935..a6a1f663440 100644 --- a/submodules/Markdown/BUILD +++ b/submodules/Markdown/BUILD @@ -7,7 +7,7 @@ swift_library( "Source/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/Media/ConvertOpusToAAC/BUILD b/submodules/Media/ConvertOpusToAAC/BUILD index 67190237239..9bf73a39ae5 100644 --- a/submodules/Media/ConvertOpusToAAC/BUILD +++ b/submodules/Media/ConvertOpusToAAC/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/Media/LocalAudioTranscription/BUILD b/submodules/Media/LocalAudioTranscription/BUILD index ba35a1d26ee..af1707af55c 100644 --- a/submodules/Media/LocalAudioTranscription/BUILD +++ b/submodules/Media/LocalAudioTranscription/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/MediaPasteboardUI/BUILD b/submodules/MediaPasteboardUI/BUILD index df60617029c..85261b8c928 100644 --- a/submodules/MediaPasteboardUI/BUILD +++ b/submodules/MediaPasteboardUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/MediaPickerUI/BUILD b/submodules/MediaPickerUI/BUILD index 893eaf9a2e5..de41753b897 100644 --- a/submodules/MediaPickerUI/BUILD +++ b/submodules/MediaPickerUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", @@ -46,6 +46,10 @@ swift_library( "//submodules/TelegramUI/Components/MediaEditor", "//submodules/RadialStatusNode", "//submodules/Camera", + "//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation", + "//submodules/ChatSendMessageActionUI", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index fd6fc6fcccb..375f4033cbd 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -28,6 +28,8 @@ import MoreButtonNode import Camera import CameraScreen import MediaEditor +import ImageObjectSeparation +import ChatSendMessageActionUI final class MediaPickerInteraction { let downloadManager: AssetDownloadManager @@ -35,14 +37,23 @@ final class MediaPickerInteraction { let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void let openDraft: (MediaEditorDraft, UIImage?) -> Void let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Bool - let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void - let schedule: () -> Void + let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.SendParameters?, @escaping () -> Void) -> Void + let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void let dismissInput: () -> Void let selectionState: TGMediaSelectionContext? let editingState: TGMediaEditingContext var hiddenMediaId: String? - init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { + var captionIsAboveMedia: Bool = false { + didSet { + if self.captionIsAboveMedia != oldValue { + self.captionIsAboveMediaValue.set(self.captionIsAboveMedia) + } + } + } + let captionIsAboveMediaValue = ValuePromise(false) + + init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.SendParameters?, @escaping () -> Void) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { self.downloadManager = downloadManager self.openMedia = openMedia self.openSelectedMedia = openSelectedMedia @@ -202,7 +213,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var presentFilePicker: () -> Void = {} private var completed = false - public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _ in } + public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _, _ in } public var requestAttachmentMenuExpansion: () -> Void = { } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } @@ -215,6 +226,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } + public var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? = nil + private let selectedCollection = Promise(nil) var dismissAll: () -> Void = { } @@ -436,6 +449,53 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } }) } + + controller.getCurrentSendMessageContextMediaPreview = { [weak self] () -> ChatSendMessageContextScreenMediaPreview? in + guard let self else { + return nil + } + guard let controller = self.controller else { + return nil + } + guard let (layout, navigationHeight) = self.validLayout else { + return nil + } + + var persistentItems = false + if case .media = controller.subject { + persistentItems = true + } + var isObscuredExternalPreview = false + if case .selected = self.currentDisplayMode { + isObscuredExternalPreview = true + } + let previewNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: true, isObscuredExternalPreview: isObscuredExternalPreview) + let clippingRect = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - navigationHeight - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom - 1.0))) + previewNode.globalClippingRect = self.view.convert(clippingRect, to: nil) + previewNode.interaction = self.controller?.interaction + previewNode.getTransitionView = { [weak self] identifier in + guard let self else { + return nil + } + var node: MediaPickerGridItemNode? + self.gridNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? MediaPickerGridItemNode, itemNode.identifier == identifier { + node = itemNode + } + } + if let node = node { + return (node.view, node.spoilerNode?.dustNode, { [weak node] animateCheckNode in + node?.animateFadeIn(animateCheckNode: animateCheckNode, animateSpoilerNode: false) + }) + } else { + return nil + } + } + let selectedItems = controller.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? [] + previewNode.updateLayout(size: layout.size, insets: UIEdgeInsets(), items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: .immediate) + + return previewNode + } } deinit { @@ -948,7 +1008,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { persistentItems = true } - let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems) + let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: false, isObscuredExternalPreview: false) selectionNode.alpha = animated ? 0.0 : 1.0 selectionNode.layer.allowsGroupOpacity = true selectionNode.isUserInteractionEnabled = false @@ -995,11 +1055,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if animated { switch displayMode { case .selected: - self.selectionNode?.animateIn(initiated: { [weak self] in + self.selectionNode?.animateIn(transition: .animated(duration: 0.25, curve: .easeInOut), initiated: { [weak self] in self?.updateNavigation(transition: .immediate) }, completion: completion) case .all: - self.selectionNode?.animateOut(completion: completion) + self.selectionNode?.animateOut(transition: .animated(duration: 0.25, curve: .easeInOut), completion: completion) } } else { self.updateNavigation(transition: .immediate) @@ -1102,7 +1162,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { return self?.transitionView(for: identifier) }, completed: { [weak self] result, silently, scheduleTime, completion in if let strongSelf = self { - strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion) + strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, nil, completion) } }, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in self?.currentGalleryParentController = c @@ -1141,7 +1201,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { return self?.transitionView(for: identifier) }, completed: { [weak self] result, silently, scheduleTime, completion in if let strongSelf = self { - strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion) + strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, nil, completion) } }, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in self?.currentGalleryParentController = c @@ -1175,12 +1235,24 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } } - fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, completion: @escaping () -> Void) { + fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, parameters: ChatSendMessageActionSheetController.SendParameters?, completion: @escaping () -> Void) { guard let controller = self.controller, !controller.completed else { return } controller.dismissAllTooltips() + var parameters = parameters + if parameters == nil { + var textIsAboveMedia = false + if let interaction = controller.interaction { + textIsAboveMedia = interaction.captionIsAboveMedia + } + parameters = ChatSendMessageActionSheetController.SendParameters( + effect: nil, + textIsAboveMedia: textIsAboveMedia + ) + } + var hasHeic = false let allItems = controller.interaction?.selectionState?.selectedItems() ?? [] for item in allItems { @@ -1203,7 +1275,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { return } controller.completed = true - controller.legacyCompletion(signals, silently, scheduleTime, { [weak self] identifier in + controller.legacyCompletion(signals, silently, scheduleTime, parameters, { [weak self] identifier in return !asFile ? self?.getItemSnapshot(identifier) : nil }, { [weak self] in completion() @@ -1916,18 +1988,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } else { return false } - }, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, completion in + }, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, parameters, completion in if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState, !strongSelf.isDismissing { strongSelf.isDismissing = true if let currentItem = currentItem { selectionState.setItem(currentItem, selected: true) } - strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, completion: completion) + strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, parameters: parameters, completion: completion) } - }, schedule: { [weak self] in + }, schedule: { [weak self] parameters in if let strongSelf = self { strongSelf.presentSchedulePicker(false, { [weak self] time in - self?.interaction?.sendSelected(nil, false, time, true, {}) + self?.interaction?.sendSelected(nil, false, time, true, parameters, {}) }) } }, dismissInput: { [weak self] in @@ -1945,11 +2017,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.updateSelectionState(count: Int32(selectionContext.count())) -// self.longTapWithTabBar = { [weak self] in -// if let self, self.groupsController == nil { -// self.presentSearch(activateOnDisplay: false) -// } -// } + + if case let .assets(_, mode) = self.subject, case .createSticker = mode { + let _ = cutoutAvailability(context: context).startStandalone() + } } required init(coder aDecoder: NSCoder) { @@ -2432,10 +2503,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } } } + + var isCaptionAboveMediaAvailable: Signal = .single(false) + if let mediaPickerContext = self.mediaPickerContext { + isCaptionAboveMediaAvailable = .single(mediaPickerContext.hasCaption) + } - let items: Signal = self.groupedPromise.get() + let items: Signal = combineLatest( + self.groupedPromise.get(), + isCaptionAboveMediaAvailable + ) |> deliverOnMainQueue - |> map { [weak self] grouped -> ContextController.Items in + |> map { [weak self] grouped, isCaptionAboveMediaAvailable -> ContextController.Items in var items: [ContextMenuItem] = [] if !hasSpoilers { items.append(.action(ContextMenuActionItem(text: selectionCount > 1 ? strings.Attachment_SendAsFiles : strings.Attachment_SendAsFile, icon: { theme in @@ -2443,7 +2522,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { }, action: { [weak self] _, f in f(.default) - self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, completion: {}) + self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, parameters: nil, completion: {}) }))) } @@ -2460,7 +2539,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { NGRoundedVideos.sendAsRoundedVideo = true - self?.controllerNode.send(asFile: false, silently: false, scheduleTime: nil, animated: true, completion: {}) + self?.controllerNode.send(asFile: false, silently: false, scheduleTime: nil, animated: true, parameters: nil, completion: {}) }))) } // @@ -2490,25 +2569,47 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self?.groupedValue = false }))) } - if isSpoilerAvailable { + if isSpoilerAvailable || (selectionCount > 0 && isCaptionAboveMediaAvailable) { if !items.isEmpty { items.append(.separator) } - items.append(.action(ContextMenuActionItem(text: hasGeneric ? strings.Attachment_EnableSpoiler : strings.Attachment_DisableSpoiler, icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation( - name: "anim_spoiler", - loop: true - ), action: { [weak self] _, f in - f(.default) - guard let strongSelf = self else { - return + + if isCaptionAboveMediaAvailable { + var mediaCaptionIsAbove = false + if let interaction = self?.interaction { + mediaCaptionIsAbove = interaction.captionIsAboveMedia } - if let selectionContext = strongSelf.interaction?.selectionState, let editingContext = strongSelf.interaction?.editingState { - for case let item as TGMediaEditableItem in selectionContext.selectedItems() { - editingContext.setSpoiler(hasGeneric, for: item) + items.append(.action(ContextMenuActionItem(text: mediaCaptionIsAbove ? strings.Chat_SendMessageMenu_MoveCaptionDown : strings.Chat_SendMessageMenu_MoveCaptionUp, icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation( + name: !mediaCaptionIsAbove ? "message_preview_sort_above" : "message_preview_sort_below" + ), action: { [weak self] _, f in + f(.default) + guard let strongSelf = self else { + return } - } - }))) + + if let interaction = strongSelf.interaction { + interaction.captionIsAboveMedia = !interaction.captionIsAboveMedia + } + }))) + } + if isSpoilerAvailable { + items.append(.action(ContextMenuActionItem(text: hasGeneric ? strings.Attachment_EnableSpoiler : strings.Attachment_DisableSpoiler, icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation( + name: "anim_spoiler", + loop: true + ), action: { [weak self] _, f in + f(.default) + guard let strongSelf = self else { + return + } + + if let selectionContext = strongSelf.interaction?.selectionState, let editingContext = strongSelf.interaction?.editingState { + for case let item as TGMediaEditableItem in selectionContext.selectedItems() { + editingContext.setSpoiler(hasGeneric, for: item) + } + } + }))) + } } return ContextController.Items(content: .list(items)) } @@ -2566,7 +2667,18 @@ final class MediaPickerContext: AttachmentMediaPickerContext { var caption: Signal { return Signal { [weak self] subscriber in - let disposable = self?.controller?.interaction?.editingState.forcedCaption().start(next: { caption in + guard let self else { + subscriber.putNext(nil) + subscriber.putCompletion() + return EmptyDisposable + } + guard let caption = self.controller?.interaction?.editingState.forcedCaption() else { + subscriber.putNext(nil) + subscriber.putCompletion() + return EmptyDisposable + } + + let disposable = caption.start(next: { caption in if let caption = caption as? NSAttributedString { subscriber.putNext(caption) } else { @@ -2578,6 +2690,34 @@ final class MediaPickerContext: AttachmentMediaPickerContext { } } } + + var hasCaption: Bool { + guard let isForcedCaption = self.controller?.interaction?.editingState.isForcedCaption() else { + return false + } + return isForcedCaption + } + + var captionIsAboveMedia: Signal { + return Signal { [weak self] subscriber in + guard let interaction = self?.controller?.interaction else { + subscriber.putNext(false) + subscriber.putCompletion() + + return EmptyDisposable + } + let disposable = interaction.captionIsAboveMediaValue.get().start(next: { value in + subscriber.putNext(value) + }, error: { _ in }, completed: { }) + return ActionDisposable { + disposable.dispose() + } + } + } + + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { + self.controller?.interaction?.captionIsAboveMedia = captionIsAboveMedia + } public var loadingProgress: Signal { return .single(nil) @@ -2595,12 +2735,12 @@ final class MediaPickerContext: AttachmentMediaPickerContext { self.controller?.interaction?.editingState.setForcedCaption(caption, skipUpdate: true) } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { - self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, {}) + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { + self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, parameters, {}) } - func schedule() { - self.controller?.interaction?.schedule() + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { + self.controller?.interaction?.schedule(parameters) } func mainButtonAction() { diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 13536092431..ae408cd4ed0 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -13,11 +13,15 @@ import MosaicLayout import WallpaperBackgroundNode import AccountContext import ChatMessageBackground +import ChatSendMessageActionUI +import ComponentFlow +import ComponentDisplayAdapters private class MediaPickerSelectedItemNode: ASDisplayNode { let asset: TGMediaEditableItem private let interaction: MediaPickerInteraction? private let enableAnimations: Bool + private let isExternalPreview: Bool private let imageNode: ImageNode private var checkNode: InteractiveCheckNode? @@ -57,10 +61,11 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { private var videoDuration: Double? - init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool) { + init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool, isExternalPreview: Bool) { self.asset = asset self.interaction = interaction self.enableAnimations = enableAnimations + self.isExternalPreview = isExternalPreview self.imageNode = ImageNode() self.imageNode.contentMode = .scaleAspectFill @@ -279,7 +284,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { if let checkNode = self.checkNode { let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) - transition.updateAlpha(node: checkNode, alpha: selectionState.count() < 2 ? 0.0 : 1.0) + transition.updateAlpha(node: checkNode, alpha: (self.isExternalPreview || selectionState.count() < 2) ? 0.0 : 1.0) } } } @@ -382,7 +387,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { return view } - func animateFrom(_ view: UIView) { + func animateFrom(_ view: UIView, transition: ContainedViewLayoutTransition) { view.alpha = 0.0 let frame = view.convert(view.bounds, to: self.supernode?.view) @@ -392,13 +397,19 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { self.durationBackgroundNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.updateLayout(size: frame.size, transition: .immediate) - self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring)) - self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak view] _ in - view?.alpha = 1.0 + self.updateLayout(size: targetFrame.size, transition: transition) + transition.animateFrame(layer: self.layer, from: frame, to: targetFrame, completion: { [weak self, weak view] _ in + guard let self else { + view?.alpha = 1.0 + return + } + if !self.isExternalPreview { + view?.alpha = 1.0 + } }) } - func animateTo(_ view: UIView, dustNode: ASDisplayNode?, completion: @escaping (Bool) -> Void) { + func animateTo(_ view: UIView, dustNode: ASDisplayNode?, transition: ContainedViewLayoutTransition, completion: @escaping (Bool) -> Void) { view.alpha = 0.0 let frame = self.frame @@ -418,14 +429,13 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { self.addSubnode(dustNode) dustNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - dustNode.layer.animatePosition(from: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0), to: dustNode.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) - + transition.animatePositionAdditive(layer: dustNode.layer, offset: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0), to: dustNode.position) self.spoilerNode?.dustNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } self.corners = [] - self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring)) - self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak view, weak self] _ in + self.updateLayout(size: targetFrame.size, transition: transition) + transition.animateFrame(layer: self.layer, from: frame, to: targetFrame, removeOnCompletion: false, completion: { [weak view, weak self] _ in view?.alpha = 1.0 self?.durationTextNode?.layer.removeAllAnimations() @@ -471,7 +481,7 @@ private class MessageBackgroundNode: ASDisplayNode { private var absoluteRect: (CGRect, CGSize)? - func update(size: CGSize, theme: PresentationTheme, wallpaper: TelegramWallpaper, graphics: PrincipalThemeEssentialGraphics, wallpaperBackgroundNode: WallpaperBackgroundNode, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, theme: PresentationTheme, wallpaper: TelegramWallpaper, graphics: PrincipalThemeEssentialGraphics, wallpaperBackgroundNode: WallpaperBackgroundNode?, transition: ContainedViewLayoutTransition) { self.backgroundNode.setType(type: .outgoing(.Extracted), highlighted: false, graphics: graphics, maskMode: false, hasWallpaper: wallpaper.hasWallpaper, transition: transition, backgroundNode: wallpaperBackgroundNode) self.backgroundWallpaperNode.setType(type: .outgoing(.Extracted), theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), essentialGraphics: graphics, maskMode: true, backgroundNode: wallpaperBackgroundNode) self.shadowNode.setType(type: .outgoing(.Extracted), hasWallpaper: wallpaper.hasWallpaper, graphics: graphics) @@ -496,11 +506,17 @@ private class MessageBackgroundNode: ASDisplayNode { } } -final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate { +final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate, ChatSendMessageContextScreenMediaPreview { private let context: AccountContext private let persistentItems: Bool + private let isExternalPreview: Bool + private let isObscuredExternalPreview: Bool + var globalClippingRect: CGRect? + var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { + return .media + } - fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode + fileprivate var wallpaperBackgroundNode: WallpaperBackgroundNode? private let scrollNode: ASScrollNode private var backgroundNodes: [Int: MessageBackgroundNode] = [:] private var itemNodes: [String: MediaPickerSelectedItemNode] = [:] @@ -518,17 +534,29 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS private var didSetReady = false private var ready = Promise() - init(context: AccountContext, persistentItems: Bool) { + private var contentSize: CGSize = CGSize() + + var isReady: Signal { + return self.ready.get() + } + + init(context: AccountContext, persistentItems: Bool, isExternalPreview: Bool, isObscuredExternalPreview: Bool) { self.context = context self.persistentItems = persistentItems + self.isExternalPreview = isExternalPreview + self.isObscuredExternalPreview = isObscuredExternalPreview - self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false) - self.wallpaperBackgroundNode.backgroundColor = .black self.scrollNode = ASScrollNode() + self.scrollNode.clipsToBounds = false super.init() - self.addSubnode(self.wallpaperBackgroundNode) + if !self.isExternalPreview { + let wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false) + wallpaperBackgroundNode.backgroundColor = .black + self.wallpaperBackgroundNode = wallpaperBackgroundNode + self.addSubnode(wallpaperBackgroundNode) + } self.addSubnode(self.scrollNode) } @@ -578,7 +606,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS var getTransitionView: (String) -> (UIView, ASDisplayNode?, (Bool) -> Void)? = { _ in return nil } - func animateIn(initiated: @escaping () -> Void, completion: @escaping () -> Void = {}) { + func animateIn(transition: ContainedViewLayoutTransition, initiated: @escaping () -> Void, completion: @escaping () -> Void = {}) { let _ = (self.ready.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in @@ -589,82 +617,112 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS strongSelf.alpha = 1.0 initiated() - strongSelf.wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in + if let wallpaperBackgroundNode = strongSelf.wallpaperBackgroundNode { + wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in + completion() + }) + wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring) + } else { completion() - }) - strongSelf.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring) + } for (_, backgroundNode) in strongSelf.backgroundNodes { backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1) + if strongSelf.isExternalPreview { + Transition.immediate.setScale(layer: backgroundNode.layer, scale: 0.001) + transition.updateTransformScale(layer: backgroundNode.layer, scale: 1.0) + } } for (identifier, itemNode) in strongSelf.itemNodes { - if let (transitionView, _, _) = strongSelf.getTransitionView(identifier) { - itemNode.animateFrom(transitionView) + if !strongSelf.isObscuredExternalPreview, let (transitionView, _, _) = strongSelf.getTransitionView(identifier) { + itemNode.animateFrom(transitionView, transition: transition) } else { - itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + if strongSelf.isExternalPreview { + itemNode.alpha = 0.0 + transition.animateTransformScale(node: itemNode, from: 0.001) + transition.updateAlpha(node: itemNode, alpha: 1.0) + } else { + itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } } } if let topNode = strongSelf.messageNodes?.first, !topNode.alpha.isZero { topNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1) - topNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + transition.animatePositionAdditive(layer: topNode.layer, offset: CGPoint(x: 0.0, y: -30.0)) } if let bottomNode = strongSelf.messageNodes?.last, !bottomNode.alpha.isZero { bottomNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1) - bottomNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + transition.animatePositionAdditive(layer: bottomNode.layer, offset: CGPoint(x: 0.0, y: 30.0)) } }) } - func animateOut(completion: @escaping () -> Void = {}) { - self.wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in - completion() - - if let strongSelf = self { - Queue.mainQueue().after(0.01) { - for (_, backgroundNode) in strongSelf.backgroundNodes { - backgroundNode.layer.removeAllAnimations() - } - - for (_, itemNode) in strongSelf.itemNodes { - itemNode.layer.removeAllAnimations() + func animateOut(transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) { + if let wallpaperBackgroundNode = self.wallpaperBackgroundNode { + wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in + completion() + + if let strongSelf = self { + Queue.mainQueue().after(0.01) { + for (_, backgroundNode) in strongSelf.backgroundNodes { + backgroundNode.layer.removeAllAnimations() + } + + for (_, itemNode) in strongSelf.itemNodes { + itemNode.layer.removeAllAnimations() + } + + strongSelf.messageNodes?.first?.layer.removeAllAnimations() + strongSelf.messageNodes?.last?.layer.removeAllAnimations() + + strongSelf.wallpaperBackgroundNode?.layer.removeAllAnimations() } - - strongSelf.messageNodes?.first?.layer.removeAllAnimations() - strongSelf.messageNodes?.last?.layer.removeAllAnimations() - - strongSelf.wallpaperBackgroundNode.layer.removeAllAnimations() } - } - }) - - self.wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring) + }) + + wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring) + } else { + completion() + } for (_, backgroundNode) in self.backgroundNodes { backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false) + if self.isExternalPreview { + transition.updateTransformScale(layer: backgroundNode.layer, scale: 0.001) + } } for (identifier, itemNode) in self.itemNodes { - if let (transitionView, maybeDustNode, completion) = self.getTransitionView(identifier) { - itemNode.animateTo(transitionView, dustNode: maybeDustNode, completion: completion) + if !self.isObscuredExternalPreview, let (transitionView, maybeDustNode, completion) = self.getTransitionView(identifier) { + itemNode.animateTo(transitionView, dustNode: maybeDustNode, transition: transition, completion: completion) } else { - itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false) + if self.isExternalPreview { + transition.updateTransformScale(node: itemNode, scale: 0.001) + transition.updateAlpha(node: itemNode, alpha: 0.0) + } else { + itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false) + } } } if let topNode = self.messageNodes?.first { topNode.layer.animateAlpha(from: topNode.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false) - topNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) + transition.animatePositionAdditive(layer: topNode.layer, offset: CGPoint(), to: CGPoint(x: 0.0, y: -30.0), removeOnCompletion: false) } if let bottomNode = self.messageNodes?.last { bottomNode.layer.animateAlpha(from: bottomNode.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false) - bottomNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) + transition.animatePositionAdditive(layer: bottomNode.layer, offset: CGPoint(), to: CGPoint(x: 0.0, y: 30.0), removeOnCompletion: false) } } + func animateOutOnSend(transition: Transition) { + transition.setAlpha(view: self.view, alpha: 0.0) + } + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } @@ -782,7 +840,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS if let current = self.itemNodes[identifier] { itemNode = current } else { - itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency) + itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency, isExternalPreview: self.isExternalPreview) self.itemNodes[identifier] = itemNode self.scrollNode.addSubnode(itemNode) @@ -852,55 +910,61 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS } } - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) - var peers = SimpleDictionary() - peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil) - - let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview - - let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false) - - let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false) - - let headerItems: [ListViewItem] = [previewItem, dragItem] - - let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height) - if let messageNodes = self.messageNodes { - for i in 0 ..< headerItems.count { - let itemNode = messageNodes[i] - headerItems[i].updateNode(async: { $0() }, node: { - return itemNode - }, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in - let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: size.width, height: layout.size.height)) - - itemNode.contentSize = layout.contentSize - itemNode.insets = layout.insets - itemNode.frame = nodeFrame - itemNode.isUserInteractionEnabled = false - - apply(ListViewItemApply(isOnScreen: true)) - }) - } - } else { - var messageNodes: [ListViewItemNode] = [] - for i in 0 ..< headerItems.count { - var itemNode: ListViewItemNode? - headerItems[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in - itemNode = node - apply().1(ListViewItemApply(isOnScreen: true)) - }) - itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) - itemNode!.isUserInteractionEnabled = false - messageNodes.append(itemNode!) - self.scrollNode.addSubnode(itemNode!) + if !self.isExternalPreview { + let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) + var peers = SimpleDictionary() + peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil) + + let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview + + let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false) + + let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false) + + let headerItems: [ListViewItem] = [previewItem, dragItem] + + let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height) + if let messageNodes = self.messageNodes { + for i in 0 ..< headerItems.count { + let itemNode = messageNodes[i] + headerItems[i].updateNode(async: { $0() }, node: { + return itemNode + }, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in + let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: size.width, height: layout.size.height)) + + itemNode.contentSize = layout.contentSize + itemNode.insets = layout.insets + itemNode.frame = nodeFrame + itemNode.isUserInteractionEnabled = false + + apply(ListViewItemApply(isOnScreen: true)) + }) + } + } else { + var messageNodes: [ListViewItemNode] = [] + for i in 0 ..< headerItems.count { + var itemNode: ListViewItemNode? + headerItems[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in + itemNode = node + apply().1(ListViewItemApply(isOnScreen: true)) + }) + itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) + itemNode!.isUserInteractionEnabled = false + messageNodes.append(itemNode!) + self.scrollNode.addSubnode(itemNode!) + } + self.messageNodes = messageNodes } - self.messageNodes = messageNodes } let spacing: CGFloat = 8.0 - var contentHeight: CGFloat = 60.0 + var contentHeight: CGFloat = 0.0 + var contentWidth: CGFloat = 0.0 + if !self.isExternalPreview { + contentHeight += 60.0 + } if let previewNode = self.messageNodes?.first { transition.updateFrame(node: previewNode, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top + 28.0), size: previewNode.frame.size)) @@ -914,26 +978,41 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS let graphics = PresentationResourcesChat.principalGraphics(theme: theme, wallpaper: wallpaper, bubbleCorners: bubbleCorners) var groupIndex = 0 + var isFirstGroup = true for (items, groupSize) in groupLayouts { - let groupRect = CGRect(origin: CGPoint(x: insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0), y: insets.top + contentHeight), size: groupSize) - - let groupBackgroundNode: MessageBackgroundNode - if let current = self.backgroundNodes[groupIndex] { - groupBackgroundNode = current + if isFirstGroup { + isFirstGroup = false } else { - groupBackgroundNode = MessageBackgroundNode() - groupBackgroundNode.displaysAsynchronously = false - self.backgroundNodes[groupIndex] = groupBackgroundNode - self.scrollNode.insertSubnode(groupBackgroundNode, at: 0) + contentHeight += spacing + } + + var groupRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top + contentHeight), size: groupSize) + if !self.isExternalPreview { + groupRect.origin.x = insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0) } var itemTransition = transition - if groupBackgroundNode.frame.width.isZero { - itemTransition = .immediate + + if !self.isExternalPreview { + let groupBackgroundNode: MessageBackgroundNode + if let current = self.backgroundNodes[groupIndex] { + groupBackgroundNode = current + } else { + groupBackgroundNode = MessageBackgroundNode() + groupBackgroundNode.displaysAsynchronously = false + self.backgroundNodes[groupIndex] = groupBackgroundNode + self.scrollNode.insertSubnode(groupBackgroundNode, at: 0) + } + + if groupBackgroundNode.frame.width.isZero { + itemTransition = .immediate + } + + let groupBackgroundFrame = groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0) + itemTransition.updatePosition(node: groupBackgroundNode, position: groupBackgroundFrame.center) + itemTransition.updateBounds(node: groupBackgroundNode, bounds: CGRect(origin: CGPoint(), size: groupBackgroundFrame.size)) + groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition) } - - itemTransition.updateFrame(node: groupBackgroundNode, frame: groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0)) - groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition) for (item, itemRect, itemPosition) in items { if let identifier = item.uniqueIdentifier, let itemNode = self.itemNodes[identifier] { @@ -968,7 +1047,8 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS } } - contentHeight += groupSize.height + spacing + contentHeight += groupSize.height + contentWidth = max(contentWidth, groupSize.width) groupIndex += 1 } @@ -1032,7 +1112,13 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS self.updateAbsoluteRects() - self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight) + if self.isExternalPreview { + self.scrollNode.view.contentSize = CGSize(width: contentWidth, height: contentHeight) + } else { + self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight) + } + + self.contentSize = CGSize(width: contentWidth, height: contentHeight) } func updateSelectionState() { @@ -1047,6 +1133,25 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS } } + func animateIn(transition: Transition) { + self.animateIn(transition: transition.containedViewLayoutTransition, initiated: {}, completion: {}) + } + + func animateOut(transition: Transition) { + self.animateOut(transition: transition.containedViewLayoutTransition, completion: {}) + } + + func update(containerSize: CGSize, transition: Transition) -> CGSize { + if var validLayout = self.validLayout { + validLayout.size = containerSize + self.validLayout = validLayout + } + + self.updateItems(transition: transition.containedViewLayoutTransition) + + return self.contentSize + } + func updateLayout(size: CGSize, insets: UIEdgeInsets, items: [TGMediaSelectableItem], grouped: Bool, theme: PresentationTheme, wallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, transition: ContainedViewLayoutTransition) { let previous = self.validLayout self.validLayout = (size, insets, items, grouped, theme, wallpaper, bubbleCorners) @@ -1068,10 +1173,12 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS } let inset: CGFloat = insets.left == 70 ? insets.left : 0.0 - self.wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false) - self.wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners) - transition.updateFrame(node: self.wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(x: inset, y: 0.0), size: CGSize(width: size.width - inset * 2.0, height: size.height))) - self.wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), displayMode: .aspectFill, transition: transition) + if let wallpaperBackgroundNode = self.wallpaperBackgroundNode { + wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false) + wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners) + transition.updateFrame(node: wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(x: inset, y: 0.0), size: CGSize(width: size.width - inset * 2.0, height: size.height))) + wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), displayMode: .aspectFill, transition: transition) + } self.updateItems(transition: itemsTransition) diff --git a/submodules/MediaPlayer/BUILD b/submodules/MediaPlayer/BUILD index 318c28c7c54..39cd7896caa 100644 --- a/submodules/MediaPlayer/BUILD +++ b/submodules/MediaPlayer/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/MediaResources/BUILD b/submodules/MediaResources/BUILD index 16e9f6ac37e..b0677105301 100644 --- a/submodules/MediaResources/BUILD +++ b/submodules/MediaResources/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/MergeLists/BUILD b/submodules/MergeLists/BUILD index 642b19176dd..314a7f8f94c 100644 --- a/submodules/MergeLists/BUILD +++ b/submodules/MergeLists/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/MetalEngine/BUILD b/submodules/MetalEngine/BUILD index b721af49c26..e5acd67d395 100644 --- a/submodules/MetalEngine/BUILD +++ b/submodules/MetalEngine/BUILD @@ -48,7 +48,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":MetalEngineMetalSourcesBundle", diff --git a/submodules/MetalEngine/Sources/MetalEngine.swift b/submodules/MetalEngine/Sources/MetalEngine.swift index b34ca53a790..7fc70642459 100644 --- a/submodules/MetalEngine/Sources/MetalEngine.swift +++ b/submodules/MetalEngine/Sources/MetalEngine.swift @@ -347,6 +347,7 @@ public final class MetalEngineSubjectContext { fileprivate var computeOperations: [ComputeOperation] = [] fileprivate var renderToLayerOperationsGroupedByState: [ObjectIdentifier: [RenderToLayerOperation]] = [:] fileprivate var freeResourcesOnCompletion: [MetalEngineResource] = [] + fileprivate var customCompletions: [() -> Void] = [] fileprivate init(device: MTLDevice, impl: MetalEngine.Impl) { self.device = device @@ -446,6 +447,10 @@ public final class MetalEngineSubjectContext { return commands(commandBuffer, state) }) } + + public func addCustomCompletion(_ customCompletion: @escaping () -> Void) { + self.customCompletions.append(customCompletion) + } } public final class MetalEngineSubjectInternalData { @@ -1039,13 +1044,17 @@ public final class MetalEngine { } } - if !subjectContext.freeResourcesOnCompletion.isEmpty { + if !subjectContext.freeResourcesOnCompletion.isEmpty || !subjectContext.customCompletions.isEmpty { let freeResourcesOnCompletion = subjectContext.freeResourcesOnCompletion + let customCompletions = subjectContext.customCompletions commandBuffer.addCompletedHandler { _ in DispatchQueue.main.async { for resource in freeResourcesOnCompletion { resource.free() } + for customCompletion in customCompletions { + customCompletion() + } } } } diff --git a/submodules/MoreButtonNode/BUILD b/submodules/MoreButtonNode/BUILD index ca770c8c98f..147227f0223 100644 --- a/submodules/MoreButtonNode/BUILD +++ b/submodules/MoreButtonNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/MosaicLayout/BUILD b/submodules/MosaicLayout/BUILD index 91330d04401..96982c1c4a3 100644 --- a/submodules/MosaicLayout/BUILD +++ b/submodules/MosaicLayout/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h index fa26508ecba..c9b041c4724 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h @@ -99,6 +99,8 @@ - (void)removeChangeListener:(id _Nonnull)changeListener; - (void)setDiscoverBackupAddressListSignal:(MTSignal * _Nonnull)signal; +- (void)setExternalRequestVerification:(MTSignal * _Nonnull (^ _Nonnull)(NSString * _Nonnull))externalRequestVerification; +- (MTSignal * _Nullable)performExternalRequestVerificationWithNonce:(NSString * _Nonnull)nonce; - (NSTimeInterval)globalTime; - (NSTimeInterval)globalTimeDifference; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h index 9c631638116..0ad171fe57e 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h @@ -1,5 +1,3 @@ - - #import @interface MTRequestContext : NSObject diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h index c7105cb69cc..5f2db89e02b 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h @@ -1,6 +1,17 @@ +#import +@protocol MTDisposable; -#import +@interface MTRequestPendingVerificationData : NSObject + +@property (nonatomic, strong, readonly) NSString *nonce; +@property (nonatomic, strong) NSString *secret; +@property (nonatomic) bool isResolved; +@property (nonatomic, strong) id disposable; + +- (instancetype)initWithNonce:(NSString *)nonce; + +@end @interface MTRequestErrorContext : NSObject @@ -13,4 +24,6 @@ @property (nonatomic) bool waitingForTokenExport; @property (nonatomic, strong) id waitingForRequestToComplete; +@property (nonatomic, strong) MTRequestPendingVerificationData *pendingVerificationData; + @end diff --git a/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m b/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m index 5861cd5a1de..68e2f921ed8 100644 --- a/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m +++ b/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m @@ -1,6 +1,7 @@ #import #import +#import #import #import #import @@ -276,6 +277,15 @@ + (MTSignal *)fetchBackupIpsResolveCloudflare:(bool)isTesting phoneNumber:(NSStr return [[MTSignal mergeSignals:signals] take:1]; } +MTAtomic *sharedFetchConfigKeychains() { + static MTAtomic *value = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + value = [[MTAtomic alloc] initWithValue:[[NSMutableDictionary alloc] init]]; + }); + return value; +} + + (MTSignal *)fetchConfigFromAddress:(MTBackupDatacenterAddress *)address currentContext:(MTContext *)currentContext mainDatacenterId:(NSInteger)mainDatacenterId { MTApiEnvironment *apiEnvironment = [currentContext.apiEnvironment copy]; @@ -299,17 +309,17 @@ + (MTSignal *)fetchConfigFromAddress:(MTBackupDatacenterAddress *)address curren NSInteger authTokenMasterDatacenterId = 0; NSNumber *requiredAuthToken = nil; bool allowUnboundEphemeralKeys = true; - if (address.datacenterId != 0) { - authTokenMasterDatacenterId = mainDatacenterId; - requiredAuthToken = @(address.datacenterId); - MTTemporaryKeychain *tempKeychain = [[MTTemporaryKeychain alloc] init]; - [MTContext copyAuthInfoFrom:currentContext.keychain toTempKeychain:tempKeychain]; - context.keychain = tempKeychain; - allowUnboundEphemeralKeys = false; - } else { - MTTemporaryKeychain *tempKeychain = [[MTTemporaryKeychain alloc] init]; - context.keychain = tempKeychain; - } + NSString *keychainKey = [NSString stringWithFormat:@"%d:%@:%d", (int)address.datacenterId, address.ip, (int)address.port]; + MTTemporaryKeychain *tempKeychain = [sharedFetchConfigKeychains() with:^(NSMutableDictionary *dict) { + if (dict[keychainKey] != nil) { + return (MTTemporaryKeychain *)dict[keychainKey]; + } else { + MTTemporaryKeychain *keychain = [[MTTemporaryKeychain alloc] init]; + dict[keychainKey] = keychain; + return keychain; + } + }]; + context.keychain = tempKeychain; MTProto *mtProto = [[MTProto alloc] initWithContext:context datacenterId:address.datacenterId usageCalculationInfo:nil requiredAuthToken:requiredAuthToken authTokenMasterDatacenterId:authTokenMasterDatacenterId]; mtProto.useTempAuthKeys = true; diff --git a/submodules/MtProtoKit/Sources/MTContext.m b/submodules/MtProtoKit/Sources/MTContext.m index ef6d24c6cd4..7e506f5edcd 100644 --- a/submodules/MtProtoKit/Sources/MTContext.m +++ b/submodules/MtProtoKit/Sources/MTContext.m @@ -181,6 +181,7 @@ @interface MTContext () *_changeListeners; MTSignal *_discoverBackupAddressListSignal; + MTSignal * _Nonnull (^ _Nullable _externalRequestVerification)(NSString * _Nonnull); NSMutableDictionary *_discoverDatacenterAddressActions; NSMutableDictionary *_datacenterAuthActions; @@ -526,6 +527,25 @@ - (void)setDiscoverBackupAddressListSignal:(MTSignal *)signal { } synchronous:true]; } +- (void)setExternalRequestVerification:(MTSignal * _Nonnull (^ _Nonnull)(NSString * _Nonnull))externalRequestVerification { + [[MTContext contextQueue] dispatchOnQueue:^ { + _externalRequestVerification = externalRequestVerification; + } synchronous:true]; +} + +- (MTSignal * _Nullable)performExternalRequestVerificationWithNonce:(NSString * _Nonnull)nonce { + __block MTSignal * _Nonnull (^ _Nullable externalRequestVerification)(NSString * _Nonnull); + [[MTContext contextQueue] dispatchOnQueue:^ { + externalRequestVerification = _externalRequestVerification; + } synchronous:true]; + + if (externalRequestVerification != nil) { + return externalRequestVerification(nonce); + } else { + return [MTSignal single:nil]; + } +} + - (NSTimeInterval)globalTime { return [[NSDate date] timeIntervalSince1970] + [self globalTimeDifference]; @@ -979,14 +999,16 @@ - (MTTransportScheme * _Nullable)chooseTransportSchemeForConnectionToDatacenterI [[MTContext contextQueue] dispatchOnQueue:^ { MTDatacenterAddress *overrideAddress = _apiEnvironment.datacenterAddressOverrides[@(datacenterId)]; + bool isAddressOverride = false; if (overrideAddress != nil) { + isAddressOverride = true; [results addObject:[[MTTransportScheme alloc] initWithTransportClass:[MTTcpTransport class] address:overrideAddress media:false]]; } else { [results addObjectsFromArray:[self _allTransportSchemesForDatacenterWithId:datacenterId]]; } MTDatacenterAddressSet *addressSet = [self addressSetForDatacenterWithId:datacenterId]; - if (addressSet != nil) { + if (addressSet != nil && !isAddressOverride) { MTTransportScheme *manualScheme = _datacenterManuallySelectedSchemeById[[[MTTransportSchemeKey alloc] initWithDatacenterId:datacenterId isProxy:isProxy isMedia:media]]; if (manualScheme != nil) { bool addressValid = false; @@ -1351,6 +1373,9 @@ - (void)invalidateTransportSchemeForDatacenterId:(NSInteger)datacenterId transpo if (_apiEnvironment.networkSettings == nil || _apiEnvironment.networkSettings.reducedBackupDiscoveryTimeout) { delay = 5.0; } + #if DEBUG + delay = 1.0; + #endif [self _beginBackupAddressDiscoveryWithDelay:delay]; }]; } diff --git a/submodules/MtProtoKit/Sources/MTEncryption.m b/submodules/MtProtoKit/Sources/MTEncryption.m index 97713424f40..a595dc695fb 100644 --- a/submodules/MtProtoKit/Sources/MTEncryption.m +++ b/submodules/MtProtoKit/Sources/MTEncryption.m @@ -588,7 +588,7 @@ bool MTCheckIsSafePrime(id provider, NSData *numberBytes, id id bnNumber = [context create]; [context assignBinTo:bnNumber value:numberBytes]; - int result = [context isPrime:bnNumber numberOfChecks:30]; + int result = [context isPrime:bnNumber numberOfChecks:64]; if (result == 1) { id bnNumberOne = [context create]; @@ -600,7 +600,7 @@ bool MTCheckIsSafePrime(id provider, NSData *numberBytes, id id bnNumberMinusOneDivByTwo = [context create]; [context rightShift1Bit:bnNumberMinusOneDivByTwo a:bnNumberMinusOne]; - result = [context isPrime:bnNumberMinusOneDivByTwo numberOfChecks:30]; + result = [context isPrime:bnNumberMinusOneDivByTwo numberOfChecks:64]; } [keychain setObject:@(result == 1) forKey:primeKey group:@"primes"]; diff --git a/submodules/MtProtoKit/Sources/MTRequestErrorContext.m b/submodules/MtProtoKit/Sources/MTRequestErrorContext.m index 31969306688..85db0d3a364 100644 --- a/submodules/MtProtoKit/Sources/MTRequestErrorContext.m +++ b/submodules/MtProtoKit/Sources/MTRequestErrorContext.m @@ -1,5 +1,17 @@ #import +@implementation MTRequestPendingVerificationData + +- (instancetype)initWithNonce:(NSString *)nonce { + self = [super init]; + if (self != nil) { + _nonce = nonce; + } + return self; +} + +@end + @implementation MTRequestErrorContext @end diff --git a/submodules/MtProtoKit/Sources/MTRequestMessageService.m b/submodules/MtProtoKit/Sources/MTRequestMessageService.m index a81adedb34b..7bfdd2f6b24 100644 --- a/submodules/MtProtoKit/Sources/MTRequestMessageService.m +++ b/submodules/MtProtoKit/Sources/MTRequestMessageService.m @@ -17,6 +17,7 @@ #import #import #import +#import #import "MTBuffer.h" #import "MTInternalMessageParser.h" @@ -24,6 +25,26 @@ #import #import "MTDropRpcResultMessage.h" +@interface MTRequestVerificationData : NSObject + +@property (nonatomic, strong, readonly) NSString *nonce; +@property (nonatomic, strong, readonly) NSString *secret; + +@end + +@implementation MTRequestVerificationData + +- (instancetype)initWithNonce:(NSString *)nonce secret:(NSString *)secret { + self = [super init]; + if (self != nil) { + _nonce = nonce; + _secret = secret; + } + return self; +} + +@end + @interface MTRequestMessageService () { MTContext *_context; @@ -381,8 +402,8 @@ - (void)mtProtoApiEnvironmentUpdated:(MTProto *)mtProto apiEnvironment:(MTApiEnv } } -- (NSData *)decorateRequestData:(MTRequest *)request initializeApi:(bool)initializeApi unresolvedDependencyOnRequestInternalId:(__autoreleasing id *)unresolvedDependencyOnRequestInternalId decoratedDebugDescription:(__autoreleasing NSString **)decoratedDebugDescription -{ +- (NSData *)decorateRequestData:(MTRequest *)request initializeApi:(bool)initializeApi requestVerificationData:(MTRequestVerificationData *)requestVerificationData unresolvedDependencyOnRequestInternalId:(__autoreleasing id *)unresolvedDependencyOnRequestInternalId decoratedDebugDescription:(__autoreleasing NSString **)decoratedDebugDescription +{ NSData *currentData = request.payload; NSString *debugDescription = @""; @@ -397,8 +418,6 @@ - (NSData *)decorateRequestData:(MTRequest *)request initializeApi:(bool)initial // invokeWithLayer [buffer appendInt32:(int32_t)0xda9b0d0d]; [buffer appendInt32:(int32_t)[_serialization currentLayer]]; - - //initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X; int32_t flags = 0; if (_apiEnvironment.socksProxySettings.secret != nil) { @@ -482,6 +501,19 @@ - (NSData *)decorateRequestData:(MTRequest *)request initializeApi:(bool)initial } } + if (requestVerificationData != nil) { + MTBuffer *buffer = [[MTBuffer alloc] init]; + + [buffer appendInt32:(int32_t)0xdae54f8]; + [buffer appendTLString:requestVerificationData.nonce]; + [buffer appendTLString:requestVerificationData.secret]; + + [buffer appendBytes:currentData.bytes length:currentData.length]; + currentData = buffer.data; + + debugDescription = [debugDescription stringByAppendingFormat:@", apnsSecret(%@, %@)", requestVerificationData.nonce, requestVerificationData.secret]; + } + if (decoratedDebugDescription != nil) { *decoratedDebugDescription = debugDescription; } @@ -511,6 +543,11 @@ - (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoS if (request.errorContext.waitingForTokenExport) { continue; } + if (request.errorContext.pendingVerificationData != nil) { + if (!request.errorContext.pendingVerificationData.isResolved) { + continue; + } + } bool foundDependency = false; for (MTRequest *anotherRequest in _requests) { @@ -542,7 +579,16 @@ - (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoS messageSeqNo = request.requestContext.messageSeqNo; } - NSData *decoratedRequestData = [self decorateRequestData:request initializeApi:requestsWillInitializeApi unresolvedDependencyOnRequestInternalId:&autoreleasingUnresolvedDependencyOnRequestInternalId decoratedDebugDescription:&decoratedDebugDescription]; + MTRequestVerificationData *requestVerificationData = nil; + if (request.errorContext != nil) { + if (request.errorContext.pendingVerificationData != nil) { + if (request.errorContext.pendingVerificationData.isResolved) { + requestVerificationData = [[MTRequestVerificationData alloc] initWithNonce:request.errorContext.pendingVerificationData.nonce secret:request.errorContext.pendingVerificationData.secret]; + } + } + } + + NSData *decoratedRequestData = [self decorateRequestData:request initializeApi:requestsWillInitializeApi requestVerificationData:requestVerificationData unresolvedDependencyOnRequestInternalId:&autoreleasingUnresolvedDependencyOnRequestInternalId decoratedDebugDescription:&decoratedDebugDescription]; MTOutgoingMessage *outgoingMessage = [[MTOutgoingMessage alloc] initWithData:decoratedRequestData metadata:request.metadata additionalDebugDescription:decoratedDebugDescription shortMetadata:request.shortMetadata messageId:messageId messageSeqNo:messageSeqNo]; outgoingMessage.needsQuickAck = request.acknowledgementReceived != nil; @@ -875,6 +921,34 @@ - (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage * [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector]; }]; + restartRequest = true; + } else if (rpcError.errorCode == 403 && [rpcError.errorDescription rangeOfString:@"APNS_VERIFY_CHECK_"].location != NSNotFound) { + if (request.errorContext == nil) { + request.errorContext = [[MTRequestErrorContext alloc] init]; + } + + NSString *nonce = [rpcError.errorDescription substringFromIndex:[@"APNS_VERIFY_CHECK_" length]]; + request.errorContext.pendingVerificationData = [[MTRequestPendingVerificationData alloc] initWithNonce:nonce]; + + __weak MTRequestMessageService *weakSelf = self; + MTQueue *queue = _queue; + id requestId = request.internalId; + request.errorContext.pendingVerificationData.disposable = [[_context performExternalRequestVerificationWithNonce:nonce] startWithNext:^(id result) { + [queue dispatchOnQueue:^{ + __strong MTRequestMessageService *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + for (MTRequest *request in strongSelf->_requests) { + if (request.internalId == requestId) { + request.errorContext.pendingVerificationData.secret = result; + request.errorContext.pendingVerificationData.isResolved = true; + } + } + [strongSelf->_mtProto requestTransportTransaction]; + }]; + }]; + restartRequest = true; } else if (rpcError.errorCode == 406) { if (_didReceiveSoftAuthResetError) { diff --git a/submodules/MusicAlbumArtResources/BUILD b/submodules/MusicAlbumArtResources/BUILD index 617143cfc77..69130e7beec 100644 --- a/submodules/MusicAlbumArtResources/BUILD +++ b/submodules/MusicAlbumArtResources/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/NotificationMuteSettingsUI/BUILD b/submodules/NotificationMuteSettingsUI/BUILD index bb062cc5302..e9999271249 100644 --- a/submodules/NotificationMuteSettingsUI/BUILD +++ b/submodules/NotificationMuteSettingsUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/NotificationSoundSelectionUI/BUILD b/submodules/NotificationSoundSelectionUI/BUILD index 0f9fe3632f9..c52611a5982 100644 --- a/submodules/NotificationSoundSelectionUI/BUILD +++ b/submodules/NotificationSoundSelectionUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/NotificationsPresentationData/BUILD b/submodules/NotificationsPresentationData/BUILD index 275798a4c3e..1e61e6b4da0 100644 --- a/submodules/NotificationsPresentationData/BUILD +++ b/submodules/NotificationsPresentationData/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/OpenInExternalAppUI/BUILD b/submodules/OpenInExternalAppUI/BUILD index ea1b5232ae8..ddabad5bb3a 100644 --- a/submodules/OpenInExternalAppUI/BUILD +++ b/submodules/OpenInExternalAppUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/OverlayStatusController/BUILD b/submodules/OverlayStatusController/BUILD index 633601d439b..2bd296652b4 100644 --- a/submodules/OverlayStatusController/BUILD +++ b/submodules/OverlayStatusController/BUILD @@ -15,7 +15,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/PasscodeInputFieldNode/BUILD b/submodules/PasscodeInputFieldNode/BUILD index c7fbc40da13..7834f1b7add 100644 --- a/submodules/PasscodeInputFieldNode/BUILD +++ b/submodules/PasscodeInputFieldNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/PasscodeUI/BUILD b/submodules/PasscodeUI/BUILD index fa1a9712bed..0aa165f24e0 100644 --- a/submodules/PasscodeUI/BUILD +++ b/submodules/PasscodeUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/PassportUI/BUILD b/submodules/PassportUI/BUILD index 76aba342692..dc93e2f11dc 100644 --- a/submodules/PassportUI/BUILD +++ b/submodules/PassportUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/PasswordSetupUI/BUILD b/submodules/PasswordSetupUI/BUILD index 00943960e74..43c857fd022 100644 --- a/submodules/PasswordSetupUI/BUILD +++ b/submodules/PasswordSetupUI/BUILD @@ -21,7 +21,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift index 9dbb573c815..539b78af181 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift @@ -1995,7 +1995,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, ASS let changeEmailActionButtonFrame: CGRect let resendCodeActionFrame: CGRect let resendCodeActionButtonFrame: CGRect - if changeEmailActionSize.width + resendCodeActionSize.width > layout.size.width - buttonFrame.minX * 2.0 { + if changeEmailActionSize.width + resendCodeActionSize.width > layout.size.width - buttonFrame.minX * 2.0 - 32.0 { changeEmailActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.minY), size: CGSize(width: buttonFrame.width, height: buttonFrame.height)) changeEmailActionFrame = CGRect(origin: CGPoint(x: changeEmailActionButtonFrame.minX + floor((changeEmailActionButtonFrame.width - changeEmailActionSize.width) / 2.0), y: changeEmailActionButtonFrame.minY + floor((changeEmailActionButtonFrame.height - changeEmailActionSize.height) / 2.0)), size: changeEmailActionSize) resendCodeActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.maxY), size: CGSize(width: buttonFrame.width, height: buttonFrame.height)) diff --git a/submodules/Pasteboard/BUILD b/submodules/Pasteboard/BUILD index ff4364f8aad..e9130ddfe24 100644 --- a/submodules/Pasteboard/BUILD +++ b/submodules/Pasteboard/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/PaymentMethodUI/BUILD b/submodules/PaymentMethodUI/BUILD index 50a49ff478b..ed54f203b27 100644 --- a/submodules/PaymentMethodUI/BUILD +++ b/submodules/PaymentMethodUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Pdf/BUILD b/submodules/Pdf/BUILD index 8b0dc1e6163..1ed1899b656 100644 --- a/submodules/Pdf/BUILD +++ b/submodules/Pdf/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/PeerAvatarGalleryUI/BUILD b/submodules/PeerAvatarGalleryUI/BUILD index 9e7b1b5a5f8..5c3c82e11d1 100644 --- a/submodules/PeerAvatarGalleryUI/BUILD +++ b/submodules/PeerAvatarGalleryUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/PeerInfoAvatarListNode/BUILD b/submodules/PeerInfoAvatarListNode/BUILD index 7ce9aba95ea..975c1d04293 100644 --- a/submodules/PeerInfoAvatarListNode/BUILD +++ b/submodules/PeerInfoAvatarListNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/PeerInfoUI/BUILD b/submodules/PeerInfoUI/BUILD index 74a9533af7b..600713ca150 100644 --- a/submodules/PeerInfoUI/BUILD +++ b/submodules/PeerInfoUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD index b34e2bac800..5aa8f5e80bc 100644 --- a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD +++ b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index 42a9faa2f3f..87ba5d676e5 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -346,7 +346,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry { let arguments = arguments as! ChannelAdminControllerArguments switch self { case let .info(_, _, dateTimeFormat, peer, presence): - return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, memberCount: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in + return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, memberCount: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in }, avatarTapped: { }) case let .rankTitle(_, text, count, limit): @@ -1715,7 +1715,9 @@ public func channelAdminController(context: AccountContext, updatedPresentationD return false }) if let resultItemNode = resultItemNode { - controller.ensureItemNodeVisible(resultItemNode) + Queue.mainQueue().after(0.1) { + controller.ensureItemNodeVisible(resultItemNode, atTop: true) + } } }) } diff --git a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift index 2e0ecd6a3a3..f7b2f1b24a7 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift @@ -215,7 +215,7 @@ private enum ChannelBannedMemberEntry: ItemListNodeEntry { let arguments = arguments as! ChannelBannedMemberControllerArguments switch self { case let .info(_, _, dateTimeFormat, peer, presence): - return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, memberCount: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in + return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, memberCount: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in }, avatarTapped: { }) case let .rightsHeader(_, text): diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index c4daf6d5e22..05d9664877f 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -24,6 +24,8 @@ import UndoUI import GalleryUI import PeerAvatarGalleryUI import Postbox +import ShareController +import ContextUI private enum DeviceContactInfoAction { case sendMessage @@ -33,7 +35,7 @@ private enum DeviceContactInfoAction { } private final class DeviceContactInfoControllerArguments { - let context: AccountContext + let context: ShareControllerAccountContext let isPlain: Bool let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void let updatePhone: (Int64, String) -> Void @@ -50,7 +52,7 @@ private final class DeviceContactInfoControllerArguments { let updateShareViaException: (Bool) -> Void let openAvatar: (EnginePeer) -> Void - init(context: AccountContext, isPlain: Bool, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, updatePhoneLabel: @escaping (Int64, String) -> Void, deletePhone: @escaping (Int64) -> Void, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, addPhoneNumber: @escaping () -> Void, performAction: @escaping (DeviceContactInfoAction) -> Void, toggleSelection: @escaping (DeviceContactInfoDataId) -> Void, callPhone: @escaping (String) -> Void, openUrl: @escaping (String) -> Void, openAddress: @escaping (DeviceContactAddressData) -> Void, displayCopyContextMenu: @escaping (DeviceContactInfoEntryTag, String) -> Void, updateShareViaException: @escaping (Bool) -> Void, openAvatar: @escaping (EnginePeer) -> Void) { + init(context: ShareControllerAccountContext, isPlain: Bool, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, updatePhoneLabel: @escaping (Int64, String) -> Void, deletePhone: @escaping (Int64) -> Void, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, addPhoneNumber: @escaping () -> Void, performAction: @escaping (DeviceContactInfoAction) -> Void, toggleSelection: @escaping (DeviceContactInfoDataId) -> Void, callPhone: @escaping (String) -> Void, openUrl: @escaping (String) -> Void, openAddress: @escaping (DeviceContactAddressData) -> Void, displayCopyContextMenu: @escaping (DeviceContactInfoEntryTag, String) -> Void, updateShareViaException: @escaping (Bool) -> Void, openAvatar: @escaping (EnginePeer) -> Void) { self.context = context self.isPlain = isPlain self.updateEditingName = updateEditingName @@ -404,7 +406,13 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { let arguments = arguments as! DeviceContactInfoControllerArguments switch self { case let .info(_, _, _, dateTimeFormat, peer, state, jobSummary, _, hiddenAvatar): - return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .contact, peer: peer, presence: nil, label: jobSummary, memberCount: nil, state: state, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks(withTopInset: false, withExtendedBottomInset: true), editingNameUpdated: { editingName in + let itemContext: ItemListAvatarAndNameInfoItem.ItemContext + if let context = arguments.context as? ShareControllerAppAccountContext { + itemContext = .accountContext(context.context) + } else { + itemContext = .other(accountPeerId: arguments.context.accountPeerId, postbox: arguments.context.stateManager.postbox, network: arguments.context.stateManager.network) + } + return ItemListAvatarAndNameInfoItem(itemContext: itemContext, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .contact, peer: peer, presence: nil, label: jobSummary, memberCount: nil, state: state, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks(withTopInset: false, withExtendedBottomInset: true), editingNameUpdated: { editingName in arguments.updateEditingName(editingName) }, avatarTapped: { if peer.smallProfileImage != nil { @@ -625,7 +633,7 @@ private func filteredContactData(contactData: DeviceContactExtendedData, exclude return DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumbers: phoneNumbers), middleName: contactData.middleName, prefix: contactData.prefix, suffix: contactData.suffix, organization: includeJob ? contactData.organization : "", jobTitle: includeJob ? contactData.jobTitle : "", department: includeJob ? contactData.department : "", emailAddresses: emailAddresses, urls: urls, addresses: addresses, birthdayDate: includeBirthday ? contactData.birthdayDate : nil, socialProfiles: socialProfiles, instantMessagingProfiles: instantMessagingProfiles, note: includeNote ? contactData.note : "") } -private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, presentationData: PresentationData, peer: EnginePeer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool, hiddenAvatar: TelegramMediaImageRepresentation?) -> [DeviceContactInfoEntry] { +private func deviceContactInfoEntries(context: ShareControllerAccountContext, presentationData: PresentationData, peer: EnginePeer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool, hiddenAvatar: TelegramMediaImageRepresentation?) -> [DeviceContactInfoEntry] { var entries: [DeviceContactInfoEntry] = [] var editingName: ItemListAvatarAndNameInfoItemName? @@ -738,16 +746,20 @@ private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, var addressIndex = 0 for address in contactData.addresses { - let signal = geocodeLocation(address: address.asPostalAddress) - |> mapToSignal { coordinates -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in - if let (latitude, longitude) = coordinates { - let resource = MapSnapshotMediaResource(latitude: latitude, longitude: longitude, width: 90, height: 90) - return chatMapSnapshotImage(engine: engine, resource: resource) - } else { - return .single({ _ in return nil }) + let signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError> + if let context = context as? ShareControllerAppAccountContext { + signal = geocodeLocation(address: address.asPostalAddress) + |> mapToSignal { coordinates -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in + if let (latitude, longitude) = coordinates { + let resource = MapSnapshotMediaResource(latitude: latitude, longitude: longitude, width: 90, height: 90) + return chatMapSnapshotImage(engine: context.context.engine, resource: resource) + } else { + return .single({ _ in return nil }) + } } + } else { + signal = .single({ _ in return nil }) } - entries.append(.address(entries.count, addressIndex, presentationData.theme, localizedGenericContactFieldLabel(label: address.label, strings: presentationData.strings), address, signal, selecting ? !state.excludedComponents.contains(.address(address)) : nil)) addressIndex += 1 } @@ -828,7 +840,7 @@ private final class DeviceContactInfoController: ItemListController, MFMessageCo } } -public func deviceContactInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { +public func deviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { var initialState = DeviceContactInfoState() if case let .create(peer, contactData, _, _, _) = subject { var peerPhoneNumber: String? @@ -876,7 +888,11 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta var displayCopyContextMenuImpl: ((DeviceContactInfoEntryTag, String) -> Void)? + let presentationData = environment.presentationData let callImpl: (String) -> Void = { number in + guard let context = (context as? ShareControllerAppAccountContext)?.context else { + return + } let user: Signal if let peer = subject.peer { user = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) @@ -890,10 +906,10 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } else { user = .single(nil) } + let _ = (user |> deliverOnMainQueue).start(next: { user in if let user = user, let phone = user.phone, formatPhoneNumber(phone) == formatPhoneNumber(number) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = ActionSheetController(presentationData: presentationData) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() @@ -952,6 +968,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta return state } }, updatePhoneLabel: { id, currentLabel in + guard let context = (context as? ShareControllerAppAccountContext)?.context else { + return + } pushControllerImpl?(phoneLabelController(context: context, currentLabel: currentLabel, completion: { value in updateState { state in var state = state @@ -1000,7 +1019,6 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta if subject.contactData.basicData.phoneNumbers.count == 1 { inviteAction(subject.contactData.basicData.phoneNumbers[0].value) } else { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = ActionSheetController(presentationData: presentationData) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() @@ -1020,7 +1038,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } case .createContact: - pushControllerImpl?(deviceContactInfoController(context: context, subject: .create(peer: subject.peer, contactData: subject.contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + pushControllerImpl?(deviceContactInfoController(context: context, environment: environment, subject: .create(peer: subject.peer, contactData: subject.contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in dismissImpl?(false) }), completed: nil, cancelled: nil)) case .addToExisting: @@ -1059,9 +1077,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta }) let hiddenAvatarPromise = Promise(nil) - let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData + let updatedPresentationData = updatedPresentationData?.signal ?? environment.updatedPresentationData let previousEditingPhoneIds = Atomic?>(value: nil) - let signal = combineLatest(presentationData, statePromise.get(), contactData, hiddenAvatarPromise.get()) + let signal = combineLatest(updatedPresentationData, statePromise.get(), contactData, hiddenAvatarPromise.get()) |> map { presentationData, state, peerAndContactData, hiddenAvatar -> (ItemListControllerState, (ItemListNodeState, Any)) in var presentationData = presentationData let updatedTheme = presentationData.theme.withModalBlocksBackground() @@ -1116,6 +1134,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta rightNavigationButton = ItemListNavigationButton(content: .text(isShare ? presentationData.strings.Common_Done : presentationData.strings.Compose_Create), style: .bold, enabled: (isShare || !filteredPhoneNumbers.isEmpty) && composedContactData != nil, action: { if let composedContactData = composedContactData { + guard let context = (context as? ShareControllerAppAccountContext)?.context else { + return + } var addToPrivacyExceptions = false updateState { state in var state = state @@ -1129,7 +1150,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta if share, filteredPhoneNumbers.count <= 1, let peer = peer { addContactDisposable.set((context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions) |> deliverOnMainQueue).start(error: { _ in - presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + presentControllerImpl?(textAlertController(context: context, updatedPresentationData: (environment.presentationData, updatedPresentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) }, completed: { // MARK: Nicegram DB Changes let _ = (contactDataManager.createContactWithData(composedContactData, account: context.account) @@ -1251,7 +1272,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta focusItemTag = DeviceContactInfoEntryTag.editingPhone(insertedPhoneId) } - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deviceContactInfoEntries(account: context.account, engine: context.engine, presentationData: presentationData, peer: peerAndContactData.0, isShare: isShare, shareViaException: shareViaException, contactData: peerAndContactData.2, isContact: peerAndContactData.1 != nil, state: state, selecting: selecting, editingPhoneNumbers: editingPhones, hiddenAvatar: hiddenAvatar), style: isShare ? .blocks : .plain, focusItemTag: focusItemTag) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deviceContactInfoEntries(context: context, presentationData: presentationData, peer: peerAndContactData.0, isShare: isShare, shareViaException: shareViaException, contactData: peerAndContactData.2, isContact: peerAndContactData.1 != nil, state: state, selecting: selecting, editingPhoneNumbers: editingPhones, hiddenAvatar: hiddenAvatar), style: isShare ? .blocks : .plain, focusItemTag: focusItemTag) return (controllerState, (listState, arguments)) } @@ -1259,24 +1280,27 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta actionsDisposable.dispose() } - let controller = DeviceContactInfoController(context: context, state: signal) + let controller = DeviceContactInfoController(presentationData: ItemListPresentationData(environment.presentationData), updatedPresentationData: environment.updatedPresentationData |> map { ItemListPresentationData($0) }, state: signal, tabBarItem: nil) controller.navigationPresentation = .modal addToExistingImpl = { [weak controller] in - guard let controller = controller else { + guard let controller, let accountContext = (context as? ShareControllerAppAccountContext)?.context else { return } - addContactToExisting(context: context, parentController: controller, contactData: subject.contactData, completion: { peer, contactId, contactData in - replaceControllerImpl?(deviceContactInfoController(context: context, subject: .vcard(peer?._asPeer(), contactId, contactData), completed: nil, cancelled: nil)) + addContactToExisting(context: accountContext, parentController: controller, contactData: subject.contactData, completion: { peer, contactId, contactData in + replaceControllerImpl?(deviceContactInfoController(context: context, environment: environment, subject: .vcard(peer?._asPeer(), contactId, contactData), completed: nil, cancelled: nil)) }) } openChatImpl = { [weak controller] peerId in + guard let controller, let context = (context as? ShareControllerAppAccountContext)?.context else { + return + } let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer else { + |> deliverOnMainQueue).start(next: { [weak controller] peer in + guard let peer, let controller else { return } - if let navigationController = (controller?.navigationController as? NavigationController) { + if let navigationController = (controller.navigationController as? NavigationController) { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) } }) @@ -1302,7 +1326,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } } inviteImpl = { [weak controller] numbers in - controller?.inviteContact(presentationData: context.sharedContext.currentPresentationData.with { $0 }, numbers: numbers) + controller?.inviteContact(presentationData: environment.presentationData, numbers: numbers) } openAddressImpl = { [weak controller] address in guard let _ = controller else { @@ -1310,7 +1334,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } } openUrlImpl = { [weak controller] url in - guard let controller = controller else { + guard let controller, let context = (context as? ShareControllerAppAccountContext)?.context else { return } context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: false, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: controller.navigationController as? NavigationController, dismissInput: { [weak controller] in @@ -1320,7 +1344,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta displayCopyContextMenuImpl = { [weak controller] tag, value in if let strongController = controller { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let presentationData = environment.presentationData var resultItemNode: ListViewItemNode? let _ = strongController.frameForItemNode({ itemNode in if let itemNode = itemNode as? ItemListTextWithLabelItemNode { @@ -1351,6 +1375,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } } openAvatarImpl = { [weak controller] peer in + guard let context = (context as? ShareControllerAppAccountContext)?.context else { + return + } let avatarController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { _, _ in }) hiddenAvatarPromise.set( @@ -1386,7 +1413,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie (parentController.navigationController as? NavigationController)?.pushViewController(contactsController) let _ = (contactsController.result |> deliverOnMainQueue).start(next: { result in - if let (peers, _, _, _, _) = result, let peer = peers.first { + if let (peers, _, _, _, _, _) = result, let peer = peers.first { let dataSignal: Signal<(EnginePeer?, DeviceContactStableId?), NoError> switch peer { case let .peer(contact, _, _): @@ -1414,7 +1441,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie let _ = (dataSignal |> deliverOnMainQueue).start(next: { peer, stableId in guard let stableId = stableId else { - parentController.present(deviceContactInfoController(context: context, subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + parentController.present(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in }), completed: nil, cancelled: nil), in: .window(.root)) return } @@ -1460,7 +1487,7 @@ func addContactOptionsController(context: AccountContext, peer: EnginePeer?, con controller.setItemGroups([ ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Profile_CreateNewContact, action: { [weak controller] in - controller?.present(context.sharedContext.makeDeviceContactInfoController(context: context, subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in + controller?.present(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in }), completed: nil, cancelled: nil), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) dismissAction() }), @@ -1477,3 +1504,32 @@ func addContactOptionsController(context: AccountContext, peer: EnginePeer?, con ]) return controller } + +public func pushContactContextOptionsController(context: AccountContext, contextController: ContextControllerProtocol, presentationData: PresentationData, peer: EnginePeer?, contactData: DeviceContactExtendedData, parentController: ViewController, push: @escaping (ViewController) -> Void) { + var items: [ContextMenuItem] = [] + items.append( + .action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in + c?.popItems() + })) + ) + items.append(.separator) + items.append( + .action(ContextMenuActionItem(text: presentationData.strings.Chat_Context_Phone_CreateNewContact, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + + push(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in + }), completed: nil, cancelled: nil)) + })) + ) + items.append( + .action(ContextMenuActionItem(text: presentationData.strings.Chat_Context_Phone_AddToExistingContact, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MoveToContacts"), color: theme.contextMenu.primaryColor) }, action: { [weak parentController] _, f in + f(.default) + guard let parentController else { + return + } + addContactToExisting(context: context, parentController: parentController, contactData: contactData, completion: { peer, contactId, contactData in + }) + })) + ) + contextController.pushItems(items: .single(ContextController.Items(content: .list(items)))) +} diff --git a/submodules/PeerInfoUI/Sources/UserInfoController.swift b/submodules/PeerInfoUI/Sources/UserInfoController.swift index 5a2f13d12c0..3da170adfea 100644 --- a/submodules/PeerInfoUI/Sources/UserInfoController.swift +++ b/submodules/PeerInfoUI/Sources/UserInfoController.swift @@ -58,8 +58,8 @@ public func openAddPersonContactImpl(context: AccountContext, updatedPresentatio if let statusSettings = statusSettings { shareViaException = statusSettings.contains(.addExceptionWhenAddingContact) } - - pushController(deviceContactInfoController(context: context, updatedPresentationData: updatedPresentationData, subject: .create(peer: user, contactData: contactData, isSharing: true, shareViaException: shareViaException, completion: { peer, stableId, contactData in + + pushController(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), updatedPresentationData: updatedPresentationData, subject: .create(peer: user, contactData: contactData, isSharing: true, shareViaException: shareViaException, completion: { peer, stableId, contactData in if let peer = peer as? TelegramUser { completion() diff --git a/submodules/PeerOnlineMarkerNode/BUILD b/submodules/PeerOnlineMarkerNode/BUILD index 609546dfde5..a47c379f080 100644 --- a/submodules/PeerOnlineMarkerNode/BUILD +++ b/submodules/PeerOnlineMarkerNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/PeerPresenceStatusManager/BUILD b/submodules/PeerPresenceStatusManager/BUILD index e5b366749f8..f765480b155 100644 --- a/submodules/PeerPresenceStatusManager/BUILD +++ b/submodules/PeerPresenceStatusManager/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/PeersNearbyIconNode/BUILD b/submodules/PeersNearbyIconNode/BUILD index bcb7f6d667c..66fb7cab6d8 100644 --- a/submodules/PeersNearbyIconNode/BUILD +++ b/submodules/PeersNearbyIconNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/PeersNearbyUI/BUILD b/submodules/PeersNearbyUI/BUILD index 4ebbdcdbb5d..e8a80dd539c 100644 --- a/submodules/PeersNearbyUI/BUILD +++ b/submodules/PeersNearbyUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/PersistentStringHash/BUILD b/submodules/PersistentStringHash/BUILD index e33565769fd..ed452e62e29 100644 --- a/submodules/PersistentStringHash/BUILD +++ b/submodules/PersistentStringHash/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/PhoneInputNode/BUILD b/submodules/PhoneInputNode/BUILD index 3b4251f22ec..7ff5224790d 100644 --- a/submodules/PhoneInputNode/BUILD +++ b/submodules/PhoneInputNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/PhoneNumberFormat/BUILD b/submodules/PhoneNumberFormat/BUILD index 7a4e9493b3f..700c9236086 100644 --- a/submodules/PhoneNumberFormat/BUILD +++ b/submodules/PhoneNumberFormat/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/libphonenumber:libphonenumber", diff --git a/submodules/PhotoResources/BUILD b/submodules/PhotoResources/BUILD index 31205c31e0c..6e69fc0748b 100644 --- a/submodules/PhotoResources/BUILD +++ b/submodules/PhotoResources/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index cfe948a294f..84a72e48975 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -914,7 +914,7 @@ public func chatMessagePhotoThumbnail(account: Account, userLocation: MediaResou var blurredThumbnailImage: CGImage? if let thumbnailImage = thumbnailImage { - if thumbnailIsBlurred { + if thumbnailIsBlurred || blurred { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(blurred ? CGSize(width: 50.0, height: 50.0) : CGSize(width: 150.0, height: 150.0)) if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { @@ -925,6 +925,10 @@ public func chatMessagePhotoThumbnail(account: Account, userLocation: MediaResou imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) if blurred { + if !thumbnailIsBlurred { + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + } imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) adjustSaturationInContext(context: thumbnailContext, saturation: 1.7) } @@ -2799,14 +2803,14 @@ public func chatWebFileImage(account: Account, file: TelegramMediaWebFile) -> Si c.setBlendMode(.normal) } - } else { - context.withFlippedContext { c in - c.setBlendMode(.copy) - c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) - c.fill(arguments.drawingRect) - - c.setBlendMode(.normal) - } + } + } else { + context.withFlippedContext { c in + c.setBlendMode(.copy) + c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) + c.fill(arguments.drawingRect) + + c.setBlendMode(.normal) } } diff --git a/submodules/PlatformRestrictionMatching/BUILD b/submodules/PlatformRestrictionMatching/BUILD index ac593294744..d1c41734861 100644 --- a/submodules/PlatformRestrictionMatching/BUILD +++ b/submodules/PlatformRestrictionMatching/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/Postbox/BUILD b/submodules/Postbox/BUILD index a86c579b542..d2019af7fcc 100644 --- a/submodules/Postbox/BUILD +++ b/submodules/Postbox/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Crc32:Crc32", diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index 06609a09336..9f5f7bea642 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -61,7 +61,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":PremiumUIBundle", @@ -125,6 +125,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/star2.scn b/submodules/PremiumUI/Resources/star2.scn new file mode 100644 index 00000000000..2ed1379f05c Binary files /dev/null and b/submodules/PremiumUI/Resources/star2.scn differ diff --git a/submodules/PremiumUI/Sources/BadgeBusinessView.swift b/submodules/PremiumUI/Sources/BadgeBusinessView.swift index 1127c153662..a98d1b65679 100644 --- a/submodules/PremiumUI/Sources/BadgeBusinessView.swift +++ b/submodules/PremiumUI/Sources/BadgeBusinessView.swift @@ -3,6 +3,7 @@ import UIKit import SceneKit import Display import AppBundle +import PremiumStarComponent private let sceneVersion: Int = 1 diff --git a/submodules/PremiumUI/Sources/BadgeStarsView.swift b/submodules/PremiumUI/Sources/BadgeStarsView.swift index f1374916e56..d5b770ec20b 100644 --- a/submodules/PremiumUI/Sources/BadgeStarsView.swift +++ b/submodules/PremiumUI/Sources/BadgeStarsView.swift @@ -3,6 +3,7 @@ import UIKit import SceneKit import Display import AppBundle +import PremiumStarComponent final class BadgeStarsView: UIView, PhoneDemoDecorationView { private let sceneView: SCNView diff --git a/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift b/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift index fc1216ae68e..566228e5e45 100644 --- a/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift +++ b/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift @@ -11,6 +11,7 @@ import AvatarNode import TelegramCore import MultilineTextComponent import TelegramPresentationData +import PremiumStarComponent private let sceneVersion: Int = 1 diff --git a/submodules/PremiumUI/Sources/BusinessPageComponent.swift b/submodules/PremiumUI/Sources/BusinessPageComponent.swift index f5fe82ec6ff..fba7895323f 100644 --- a/submodules/PremiumUI/Sources/BusinessPageComponent.swift +++ b/submodules/PremiumUI/Sources/BusinessPageComponent.swift @@ -498,7 +498,7 @@ final class BusinessPageComponent: 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 { @@ -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/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index 9976989cf27..57f20ff4297 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -774,7 +774,7 @@ private func createGiveawayControllerEntries( if state.showPrizeDescription { entries.append(.prizeDescriptionText(presentationData.theme, presentationData.strings.BoostGift_AdditionalPrizesPlaceholder, state.prizeDescription, state.subscriptions)) - let monthsString = presentationData.strings.BoostGift_AdditionalPrizesInfoForMonths(state.selectedMonths ?? 3) + let monthsString = presentationData.strings.BoostGift_AdditionalPrizesInfoForMonths(state.selectedMonths ?? 12) if state.prizeDescription.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "") prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", subscriptionsString, monthsString).string @@ -1163,6 +1163,8 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments case .assignFailed: errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .tryLater: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown case .cancelled: break } diff --git a/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift b/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift index daeb42a2e00..6317511c4d6 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 @@ -195,7 +196,17 @@ class CreateGiveawayHeaderItemNode: ItemListControllerHeaderItemNode { self.backgroundNode.update(size: CGSize(width: layout.size.width, height: navigationBarHeight), transition: transition) - let component = AnyComponent(PremiumStarComponent(isIntro: true, isVisible: true, hasIdleAnimations: true)) + let component = AnyComponent(PremiumStarComponent( + theme: self.item.theme, + isIntro: true, + isVisible: true, + hasIdleAnimations: true, + colors: [ + UIColor(rgb: 0x6a94ff), + UIColor(rgb: 0x9472fd), + UIColor(rgb: 0xe26bd3) + ] + )) let containerSize = CGSize(width: min(414.0, layout.size.width), height: 220.0) if let hostView = self.hostView { diff --git a/submodules/PremiumUI/Sources/FasterStarsView.swift b/submodules/PremiumUI/Sources/FasterStarsView.swift index 967fc2213df..baddaba3d87 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 c5ea6cf0ad9..c497b6ad572 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 e45d1a818e8..84a156e022e 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 @@ -231,6 +232,9 @@ class PremiumCoinComponent: Component { self.sceneView.delegate = self let _ = self.sceneView.snapshot() +// self.didSetReady = true +// self._ready.set(.single(true)) +// self.onReady() } private var didSetReady = false diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index de2053e80c5..91671972a03 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -20,29 +20,40 @@ import TelegramUIPreferences public final class PremiumGradientBackgroundComponent: Component { public let colors: [UIColor] + public let cornerRadius: CGFloat + public let topOverscroll: Bool public init( - colors: [UIColor] + colors: [UIColor], + cornerRadius: CGFloat = 10.0, + topOverscroll: Bool = false ) { self.colors = colors + self.cornerRadius = cornerRadius + self.topOverscroll = topOverscroll } public static func ==(lhs: PremiumGradientBackgroundComponent, rhs: PremiumGradientBackgroundComponent) -> Bool { if lhs.colors != rhs.colors { return false } + if lhs.cornerRadius != rhs.cornerRadius { + return false + } + if lhs.topOverscroll != rhs.topOverscroll { + return false + } return true } public final class View: UIView { - private let clipLayer: CALayer + private let clipLayer: CAReplicatorLayer private let gradientLayer: CAGradientLayer private var component: PremiumGradientBackgroundComponent? override init(frame: CGRect) { - self.clipLayer = CALayer() - self.clipLayer.cornerRadius = 10.0 + self.clipLayer = CAReplicatorLayer() self.clipLayer.masksToBounds = true self.gradientLayer = CAGradientLayer() @@ -61,22 +72,36 @@ public final class PremiumGradientBackgroundComponent: Component { func update(component: PremiumGradientBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.clipLayer.frame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: availableSize.height + 10.0)) self.gradientLayer.frame = CGRect(origin: .zero, size: availableSize) - + var locations: [NSNumber] = [] let delta = 1.0 / CGFloat(component.colors.count - 1) for i in 0 ..< component.colors.count { locations.append((delta * CGFloat(i)) as NSNumber) } + self.gradientLayer.locations = locations self.gradientLayer.colors = component.colors.reversed().map { $0.cgColor } self.gradientLayer.type = .radial self.gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0) self.gradientLayer.endPoint = CGPoint(x: -2.0, y: 3.0) + + self.clipLayer.cornerRadius = component.cornerRadius self.component = component self.setupGradientAnimations() + if component.topOverscroll { + self.clipLayer.instanceCount = 2 + var instanceTransform = CATransform3DIdentity + instanceTransform = CATransform3DTranslate(instanceTransform, 0.0, -availableSize.height * 1.5, 0.0) + instanceTransform = CATransform3DScale(instanceTransform, 1.0, -2.0, 1.0) + self.clipLayer.instanceTransform = instanceTransform + self.clipLayer.masksToBounds = false + } else { + self.clipLayer.masksToBounds = true + } + return availableSize } @@ -1082,7 +1107,7 @@ private final class DemoSheetContent: CombinedComponent { id: "background", component: AnyComponent( BlurredBackgroundComponent( - color: UIColor(rgb: 0x888888, alpha: 0.1) + color: UIColor(rgb: 0x888888, alpha: 0.1) ) ) ), diff --git a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift index a6503bac26b..8869e2d91da 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 @@ -265,7 +266,17 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent { ) let star = star.update( - component: PremiumStarComponent(isIntro: false, isVisible: true, hasIdleAnimations: true), + component: PremiumStarComponent( + theme: theme, + isIntro: false, + isVisible: true, + hasIdleAnimations: true, + colors: [ + UIColor(rgb: 0x6a94ff), + UIColor(rgb: 0x9472fd), + UIColor(rgb: 0xe26bd3) + ] + ), availableSize: CGSize(width: context.availableSize.width, height: 200.0), transition: .immediate ) diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index eddb892c99e..8e3668042e7 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? { @@ -621,7 +622,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { let _ = (signal |> deliverOnMainQueue).start(next: { resolvedUrl in context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in - }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, arguments in + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, arguments in controller?.push(c) }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) }) @@ -954,6 +955,8 @@ private final class PremiumGiftScreenComponent: CombinedComponent { errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments case .assignFailed: errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .tryLater: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown case .cancelled: break } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 41ae1775350..38f3578b57c 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -17,7 +17,6 @@ import SolidRoundedButtonComponent import MultilineTextComponent import MultilineTextWithEntitiesComponent import BundleIconComponent -import SolidRoundedButtonComponent import BlurredBackgroundComponent import Markdown import InAppPurchaseManager @@ -38,6 +37,7 @@ import EmojiStatusComponent import EntityKeyboard import EmojiActionIconComponent import ScrollComponent +import PremiumStarComponent public enum PremiumSource: Equatable { public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool { @@ -1809,7 +1809,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } }, tapAction: { _, _ in - shareLink(link) + if !link.isEmpty { + shareLink(link) + } } ), environment: {}, @@ -2020,7 +2022,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { backgroundColor: gradientColors[i], foregroundColor: .white, iconName: perk.iconName - )))), + ))), false), action: { [weak state] _ in var demoSubject: PremiumDemoScreen.Subject switch perk { @@ -2187,7 +2189,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { backgroundColor: gradientColors[min(i, gradientColors.count - 1)], foregroundColor: .white, iconName: perk.iconName - )))), + ))), false), action: { [weak state] _ in let isPremium = state?.isPremium == true if isPremium { @@ -2371,7 +2373,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { backgroundColor: UIColor(rgb: 0x676bff), foregroundColor: .white, iconName: "Premium/BusinessPerk/Status" - )))), + ))), false), icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( context: context.component.context, color: accentColor, @@ -2412,7 +2414,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { backgroundColor: UIColor(rgb: 0x4492ff), foregroundColor: .white, iconName: "Premium/BusinessPerk/Tag" - )))), + ))), false), action: { _ in push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, scrollToTags: true, dismissed: nil)) } @@ -2443,7 +2445,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { backgroundColor: UIColor(rgb: 0x41a6a5), foregroundColor: .white, iconName: "Premium/Perk/Stories" - )))), + ))), false), action: { _ in push(accountContext.sharedContext.makeMyStoriesController(context: accountContext, isArchive: false)) } @@ -2708,7 +2710,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let _ = (signal |> deliverOnMainQueue).start(next: { resolvedUrl in context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in - }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, arguments in + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, arguments in controller?.push(c) }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) }) @@ -3138,6 +3140,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent { errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments case .assignFailed: errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .tryLater: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown case .cancelled: break } @@ -3188,6 +3192,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent { let bottomSeparator = Child(Rectangle.self) let button = Child(SolidRoundedButtonComponent.self) + var updatedInstalled: Bool? + return { context in let environment = context.environment[EnvironmentType.self].value let state = context.state @@ -3233,9 +3239,15 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } else { header = star.update( component: PremiumStarComponent( + theme: environment.theme, isIntro: isIntro, isVisible: starIsVisible, - hasIdleAnimations: state.hasIdleAnimations + hasIdleAnimations: state.hasIdleAnimations, + colors: [ + UIColor(rgb: 0x6a94ff), + UIColor(rgb: 0x9472fd), + UIColor(rgb: 0xe26bd3) + ] ), availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), transition: context.transition @@ -3395,8 +3407,15 @@ private final class PremiumIntroScreenComponent: CombinedComponent { if let emojiFile = state?.emojiFile, let controller = environment?.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController { for attribute in emojiFile.attributes { if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference { - let controller = accountContext.sharedContext.makeStickerPackScreen(context: accountContext, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: loadedEmojiPack.flatMap { [$0] } ?? [], isEditing: false, expandIfNeeded: false, parentNavigationController: navigationController, sendSticker: { _, _, _ in + var loadedPack: LoadedStickerPack? + if let loadedEmojiPack, case let .result(info, items, installed) = loadedEmojiPack { + loadedPack = .result(info: info, items: items, installed: updatedInstalled ?? installed) + } + + let controller = accountContext.sharedContext.makeStickerPackScreen(context: accountContext, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: loadedPack.flatMap { [$0] } ?? [], isEditing: false, expandIfNeeded: false, parentNavigationController: navigationController, sendSticker: { _, _, _ in return false + }, actionPerformed: { added in + updatedInstalled = added }) presentController(controller) break diff --git a/submodules/PremiumUI/Sources/StoriesPageComponent.swift b/submodules/PremiumUI/Sources/StoriesPageComponent.swift index 106d512df09..6b37edbcfbb 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 8c3ce911b09..d4cddb34f9f 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/PresentationDataUtils/BUILD b/submodules/PresentationDataUtils/BUILD index 66b1049608f..290fbf25567 100644 --- a/submodules/PresentationDataUtils/BUILD +++ b/submodules/PresentationDataUtils/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/PresentationDataUtils/Sources/OpenUrl.swift b/submodules/PresentationDataUtils/Sources/OpenUrl.swift index 6a7a7a52b6d..31f70d75d2f 100644 --- a/submodules/PresentationDataUtils/Sources/OpenUrl.swift +++ b/submodules/PresentationDataUtils/Sources/OpenUrl.swift @@ -5,11 +5,15 @@ import Postbox import AccountContext import OverlayStatusController import UrlWhitelist +import TelegramPresentationData -public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: String, concealed: Bool, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void, progress: Promise? = nil) -> Disposable { +public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: String, concealed: Bool, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, forceDark: Bool = false, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void, progress: Promise? = nil, alertDisplayUpdated: ((ViewController?) -> Void)? = nil) -> Disposable { var concealed = concealed - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + var presentationData = context.sharedContext.currentPresentationData.with { $0 } + if forceDark { + presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) + } let openImpl: () -> Disposable = { let disposable = MetaDisposable() @@ -87,9 +91,15 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: var displayUrl = rawDisplayUrl displayUrl = displayUrl.replacingOccurrences(of: "\u{202e}", with: "") let disposable = MetaDisposable() - present(textAlertController(context: context, title: nil, text: presentationData.strings.Generic_OpenHiddenLinkAlert(displayUrl).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { + + let alertController = textAlertController(context: context, forceTheme: forceDark ? presentationData.theme : nil, title: nil, text: presentationData.strings.Generic_OpenHiddenLinkAlert(displayUrl).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { disposable.set(openImpl()) - })])) + })]) + alertController.dismissed = { _ in + alertDisplayUpdated?(nil) + } + present(alertController) + alertDisplayUpdated?(alertController) return disposable } else { return openImpl() diff --git a/submodules/ProgressNavigationButtonNode/BUILD b/submodules/ProgressNavigationButtonNode/BUILD index f6c0c21aa21..14ff2f4c073 100644 --- a/submodules/ProgressNavigationButtonNode/BUILD +++ b/submodules/ProgressNavigationButtonNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/PromptUI/BUILD b/submodules/PromptUI/BUILD index aec3f5594f6..79a9b751022 100644 --- a/submodules/PromptUI/BUILD +++ b/submodules/PromptUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/QrCode/BUILD b/submodules/QrCode/BUILD index 7ece4c25aec..223449d1957 100644 --- a/submodules/QrCode/BUILD +++ b/submodules/QrCode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/QrCodeUI/BUILD b/submodules/QrCodeUI/BUILD index 0308b4224bc..327c122aa99 100644 --- a/submodules/QrCodeUI/BUILD +++ b/submodules/QrCodeUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift index 124ad220c38..3b8370da8b9 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift @@ -915,10 +915,11 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, ASScrollVie navigationController.setViewControllers(viewControllers, animated: false) } })) - }, sendFile: nil, - sendSticker: { _, _, _ in - return false - }, requestMessageActionUrlAuth: nil, + }, + sendFile: nil, + sendSticker: nil, + sendEmoji: nil, + requestMessageActionUrlAuth: nil, joinVoiceChat: { peerId, invite, call in }, present: { [weak self] c, a in self?.controller?.present(c, in: .window(.root), with: a) diff --git a/submodules/RadialStatusNode/BUILD b/submodules/RadialStatusNode/BUILD index 5fb03863251..7fe9de7ea11 100644 --- a/submodules/RadialStatusNode/BUILD +++ b/submodules/RadialStatusNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Reachability/BUILD b/submodules/Reachability/BUILD index c6191f98c08..b8f2b7c49d3 100644 --- a/submodules/Reachability/BUILD +++ b/submodules/Reachability/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Reachability/LegacyReachability:LegacyReachability", diff --git a/submodules/Reachability/LegacyReachability/Sources/LegacyReachability.m b/submodules/Reachability/LegacyReachability/Sources/LegacyReachability.m index d28d36f6c5a..0a8f3a76625 100644 --- a/submodules/Reachability/LegacyReachability/Sources/LegacyReachability.m +++ b/submodules/Reachability/LegacyReachability/Sources/LegacyReachability.m @@ -19,6 +19,7 @@ #import #import #import +#import #pragma mark IPv6 Support //Reachability fully support IPv6. For full details, see ReadMe.md. @@ -117,7 +118,7 @@ - (id)with:(id (^)(id))f { @end -static int32_t nextKey = 1; +static _Atomic int32_t nextKey = 1; static ReachabilityAtomic *contexts() { static ReachabilityAtomic *instance = nil; static dispatch_once_t onceToken; @@ -135,7 +136,7 @@ static void withContext(int32_t key, void (^f)(LegacyReachability *)) { } static int32_t addContext(LegacyReachability *context) { - int32_t key = OSAtomicIncrement32(&nextKey); + int32_t key = atomic_fetch_add_explicit(&nextKey, 1, memory_order_relaxed); [contexts() modify:^id(NSMutableDictionary *dict) { NSMutableDictionary *updatedDict = [[NSMutableDictionary alloc] initWithDictionary:dict]; updatedDict[@(key)] = context; diff --git a/submodules/ReactionSelectionNode/BUILD b/submodules/ReactionSelectionNode/BUILD index b94955dc38a..c1832a00f89 100644 --- a/submodules/ReactionSelectionNode/BUILD +++ b/submodules/ReactionSelectionNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox:Postbox", diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift index 58ce8f1d51e..d537ff150fb 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift @@ -41,6 +41,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode { private let backgroundView: BlurredBackgroundView private(set) var vibrancyEffectView: UIVisualEffectView? + let vibrantExpandedContentContainer: UIView private let maskLayer: SimpleLayer private let backgroundClippingLayer: SimpleLayer @@ -84,6 +85,8 @@ final class ReactionContextBackgroundNode: ASDisplayNode { self.smallCircleLayer.cornerCurve = .circular } + self.vibrantExpandedContentContainer = UIView() + super.init() self.layer.addSublayer(self.backgroundShadowLayer) @@ -146,6 +149,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode { let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect) let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect) self.vibrancyEffectView = vibrancyEffectView + vibrancyEffectView.contentView.addSubview(self.vibrantExpandedContentContainer) self.backgroundView.addSubview(vibrancyEffectView) } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index d413abc2b8a..3183090123a 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -82,9 +82,9 @@ public enum ReactionContextItem: Equatable { } else { return false } - case let .reaction(lhsReaction): - if case let .reaction(rhsReaction) = rhs { - return lhsReaction.reaction == rhsReaction.reaction + case let .reaction(lhsReaction, lhsIcon): + if case let .reaction(rhsReaction, rhsIcon) = rhs { + return lhsReaction.reaction == rhsReaction.reaction && lhsIcon == rhsIcon } else { return false } @@ -98,11 +98,11 @@ public enum ReactionContextItem: Equatable { } case staticEmoji(String) - case reaction(ReactionItem) + case reaction(item: ReactionItem, icon: EmojiPagerContentComponent.Item.Icon) case premium public var reaction: ReactionItem.Reaction? { - if case let .reaction(item) = self { + if case let .reaction(item, _) = self { return item.reaction } else { return nil @@ -386,6 +386,8 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { public var forceDark: Bool = false public var hideBackground: Bool = false + public var isMessageEffects: Bool = false + private var didAnimateIn: Bool = false public private(set) var isAnimatingOut: Bool = false public private(set) var isAnimatingOutToReaction: Bool = false @@ -433,6 +435,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { private var availableReactionsDisposable: Disposable? public let alwaysAllowPremiumReactions: Bool + private var hideExpandedTopPanel: Bool = false private var hasPremium: Bool? private var hasPremiumDisposable: Disposable? @@ -660,6 +663,8 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { return } + strongSelf.hideExpandedTopPanel = emojiContent.panelItemGroups.isEmpty + var emojiContent = emojiContent if let emojiSearchResult = emojiSearchState.result { var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults? @@ -677,6 +682,10 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : .active) } else { strongSelf.stableEmptyResultEmoji = nil + + if emojiSearchState.isSearching { + emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiContent.contentItemGroups, itemContentUniqueId: emojiContent.itemContentUniqueId, emptySearchResults: emojiContent.emptySearchResults, searchState: .searching) + } } strongSelf.emojiContent = emojiContent @@ -702,7 +711,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { var hideTopPanel = false if strongSelf.isReactionSearchActive { hideTopPanel = true - } else if strongSelf.alwaysAllowPremiumReactions { + } else if strongSelf.alwaysAllowPremiumReactions || strongSelf.hideExpandedTopPanel { hideTopPanel = true } @@ -717,7 +726,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { backgroundColor: .clear, separatorColor: strongSelf.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5), hideTopPanel: hideTopPanel, - disableTopPanel: strongSelf.alwaysAllowPremiumReactions, + disableTopPanel: strongSelf.alwaysAllowPremiumReactions || strongSelf.hideExpandedTopPanel, hideTopPanelUpdated: { hideTopPanel, transition in guard let strongSelf = self else { return @@ -826,6 +835,8 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { public func wantsDisplayBelowKeyboard() -> Bool { if let emojiView = self.reactionSelectionComponentHost?.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View { return emojiView.wantsDisplayBelowKeyboard() + } else if let stickersView = self.reactionSelectionComponentHost?.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("stickers"))) as? EmojiPagerContentComponent.View { + return stickersView.wantsDisplayBelowKeyboard() } else { return false } @@ -1044,8 +1055,16 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { itemTransition = .immediate switch self.items[i] { - case let .reaction(item): - itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: item, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: loopIdle, isLocked: self.reactionsLocked) + case let .reaction(item, icon): + var isLocked = self.reactionsLocked + switch icon { + case .locked: + isLocked = true + default: + break + } + + itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: item, icon: icon, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: loopIdle, isLocked: isLocked) maskNode = nil case let .staticEmoji(emoji): itemNode = EmojiItemNode(theme: self.presentationData.theme, emoji: emoji) @@ -1193,7 +1212,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } var baseNextFrame = CGRect(origin: CGPoint(x: self.scrollNode.view.bounds.width - expandItemSize - 9.0, y: self.contentTopInset + containerHeight - contentHeight + floor((contentHeight - expandItemSize) / 2.0)), size: CGSize(width: expandItemSize, height: expandItemSize + self.extensionDistance)) if self.isExpanded { - if self.alwaysAllowPremiumReactions { + if self.alwaysAllowPremiumReactions || self.hideExpandedTopPanel { } else { baseNextFrame.origin.y += 46.0 + 54.0 - 4.0 } @@ -1322,7 +1341,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { var scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: actualBackgroundFrame.size) if self.isExpanded { - if self.alwaysAllowPremiumReactions { + if self.alwaysAllowPremiumReactions || self.hideExpandedTopPanel { scrollFrame.origin.y += 0.0 } else { scrollFrame.origin.y += 46.0 + 54.0 - 4.0 @@ -1347,7 +1366,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { self.updateScrolling(transition: transition) self.emojiContentLayout = EmojiPagerContentComponent.CustomLayout( - topPanelAlwaysHidden: self.alwaysAllowPremiumReactions, + topPanelAlwaysHidden: self.alwaysAllowPremiumReactions || self.hideExpandedTopPanel, itemsPerRow: itemCount, itemSize: itemSize, sideInset: sideInset, @@ -1377,7 +1396,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { var hideTopPanel = false if self.isReactionSearchActive { hideTopPanel = true - } else if self.alwaysAllowPremiumReactions { + } else if self.alwaysAllowPremiumReactions || self.hideExpandedTopPanel { hideTopPanel = true } @@ -1495,6 +1514,9 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { animationOffsetY += 54.0 } else if self.alwaysAllowPremiumReactions { animationOffsetY += 4.0 + } else if self.isMessageEffects { + animationOffsetY += 54.0 + transition.animatePositionAdditive(layer: self.backgroundNode.vibrantExpandedContentContainer.layer, offset: CGPoint(x: 0.0, y: -animationOffsetY + floorToScreenPixels(self.animateFromExtensionDistance / 2.0))) } else { animationOffsetY += 46.0 + 54.0 - 4.0 } @@ -1603,7 +1625,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { if !self.didInitializeEmojiContentHeight { self.didInitializeEmojiContentHeight = true - if emojiContent.contentItemGroups.count == 1 { + if emojiContent.contentItemGroups.count == 1 && emojiContent.contentItemGroups[0].title == nil { let itemCount = emojiContent.contentItemGroups[0].items.count let numRows = (itemCount + (emojiContentLayout.itemsPerRow - 1)) / emojiContentLayout.itemsPerRow let proposedHeight: CGFloat = CGFloat(numRows) * emojiContentLayout.itemSize + CGFloat(numRows - 1) * emojiContentLayout.itemSpacing + emojiContentLayout.itemSpacing * 2.0 + 5.0 @@ -1774,6 +1796,9 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { return } + let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }) + let strings = presentationData.strings + switch query { case .none: self.emojiSearchDisposable.set(nil) @@ -1811,115 +1836,168 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } |> distinctUntilChanged - let remotePacksSignal: Signal<(sets: FoundStickerSets, isFinalResult: Bool), NoError> = .single((FoundStickerSets(), false)) |> then( - context.engine.stickers.searchEmojiSetsRemotely(query: query) |> map { - ($0, true) - } - ) - - let resultSignal = signal - |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in - var allEmoticons: [String: String] = [:] - for keyword in keywords { - for emoticon in keyword.emoticons { - allEmoticons[emoticon] = keyword.keyword - } - } - if isEmojiOnly { - var items: [EmojiPagerContentComponent.Item] = [] - for (_, list) in EmojiPagerContentComponent.staticEmojiMapping { - for emojiString in list { - if allEmoticons[emojiString] != nil { - let item = EmojiPagerContentComponent.Item( - animationData: nil, - content: .staticEmoji(emojiString), - itemFile: nil, - subgroupId: nil, - icon: .none, - tintMode: .none - ) - items.append(item) - } + let resultSignal: Signal<[EmojiPagerContentComponent.ItemGroup], NoError> + if self.isMessageEffects { + resultSignal = signal + |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + var allEmoticons: [String: String] = [:] + for keyword in keywords { + for emoticon in keyword.emoticons { + allEmoticons[emoticon] = keyword.keyword } } - var resultGroups: [EmojiPagerContentComponent.ItemGroup] = [] - resultGroups.append(EmojiPagerContentComponent.ItemGroup( - supergroupId: "search", - groupId: "search", - title: nil, - subtitle: nil, - badge: nil, - actionButtonTitle: nil, - isFeatured: false, - isPremiumLocked: false, - isEmbedded: false, - hasClear: false, - hasEdit: false, - collapsedLineCount: nil, - displayPremiumBadges: false, - headerItem: nil, - fillWithLoadingPlaceholders: false, - items: items - )) - return .single(resultGroups) - } else { + return combineLatest( - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1), - context.engine.stickers.availableReactions() |> take(1), - hasPremium |> take(1), - remotePacksSignal + context.availableMessageEffects |> take(1), + hasPremium |> take(1) ) - |> map { view, availableReactions, hasPremium, foundPacks -> [EmojiPagerContentComponent.ItemGroup] in - var result: [(String, TelegramMediaFile?, String)] = [] + |> mapToSignal { availableMessageEffects, hasPremium -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + guard let availableMessageEffects else { + return .single([]) + } - var allEmoticons: [String: String] = [:] - for keyword in keywords { - for emoticon in keyword.emoticons { - allEmoticons[emoticon] = keyword.keyword + var filteredEffects: [AvailableMessageEffects.MessageEffect] = [] + for messageEffect in availableMessageEffects.messageEffects { + if allEmoticons[messageEffect.emoticon] != nil { + filteredEffects.append(messageEffect) } } - for entry in view.entries { - guard let item = entry.item as? StickerPackItem else { - continue - } - for attribute in item.file.attributes { - switch attribute { - case let .CustomEmoji(_, _, alt, _): - if !item.file.isPremiumEmoji || hasPremium { - if !alt.isEmpty, let keyword = allEmoticons[alt] { - result.append((alt, item.file, keyword)) - } else if alt == query { - result.append((alt, item.file, alt)) - } - } - default: - break - } + var reactionEffects: [AvailableMessageEffects.MessageEffect] = [] + var stickerEffects: [AvailableMessageEffects.MessageEffect] = [] + for messageEffect in filteredEffects { + if messageEffect.effectAnimation != nil { + reactionEffects.append(messageEffect) + } else { + stickerEffects.append(messageEffect) } } - var items: [EmojiPagerContentComponent.Item] = [] + struct ItemGroup { + var supergroupId: AnyHashable + var id: AnyHashable + var title: String? + var subtitle: String? + var actionButtonTitle: String? + var isPremiumLocked: Bool + var isFeatured: Bool + var displayPremiumBadges: Bool + var hasEdit: Bool + var headerItem: EntityKeyboardAnimationData? + var items: [EmojiPagerContentComponent.Item] + } - var existingIds = Set() - for item in result { - if let itemFile = item.1 { - if existingIds.contains(itemFile.fileId) { - continue + var resultGroups: [ItemGroup] = [] + var resultGroupIndexById: [AnyHashable: Int] = [:] + + for i in 0 ..< 2 { + let groupId = i == 0 ? "reactions" : "stickers" + for item in i == 0 ? reactionEffects : stickerEffects { + let itemFile: TelegramMediaFile = item.effectSticker + + var tintMode: EmojiPagerContentComponent.Item.TintMode = .none + if itemFile.isCustomTemplateEmoji { + tintMode = .primary } - existingIds.insert(itemFile.fileId) - let animationData = EntityKeyboardAnimationData(file: itemFile) - let item = EmojiPagerContentComponent.Item( + + let icon: EmojiPagerContentComponent.Item.Icon + if i == 0 { + if !hasPremium && item.isPremium { + icon = .locked + } else { + icon = .none + } + } else { + if !hasPremium && item.isPremium { + icon = .locked + } else if let staticIcon = item.staticIcon { + icon = .customFile(staticIcon) + } else { + icon = .text(item.emoticon) + } + } + + let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none) + let resultItem = EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), - itemFile: itemFile, subgroupId: nil, - icon: .none, - tintMode: animationData.isTemplate ? .primary : .none + itemFile: itemFile, + subgroupId: nil, + icon: icon, + tintMode: tintMode ) - items.append(item) + + if let groupIndex = resultGroupIndexById[groupId] { + resultGroups[groupIndex].items.append(resultItem) + } else { + resultGroupIndexById[groupId] = resultGroups.count + //TODO:localize + resultGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: i == 0 ? nil : strings.Chat_MessageEffectMenu_SectionMessageEffects, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem])) + } } } + let allItemGroups = resultGroups.map { group -> EmojiPagerContentComponent.ItemGroup in + let hasClear = false + let isEmbedded = false + + return EmojiPagerContentComponent.ItemGroup( + supergroupId: group.supergroupId, + groupId: group.id, + title: group.title, + subtitle: group.subtitle, + badge: nil, + actionButtonTitle: group.actionButtonTitle, + isFeatured: group.isFeatured, + isPremiumLocked: group.isPremiumLocked, + isEmbedded: isEmbedded, + hasClear: hasClear, + hasEdit: group.hasEdit, + collapsedLineCount: nil, + displayPremiumBadges: group.displayPremiumBadges, + headerItem: group.headerItem, + fillWithLoadingPlaceholders: false, + items: group.items + ) + } + + return .single(allItemGroups) + } + } + } else { + let remotePacksSignal: Signal<(sets: FoundStickerSets, isFinalResult: Bool), NoError> = .single((FoundStickerSets(), false)) + |> then( + context.engine.stickers.searchEmojiSetsRemotely(query: query) |> map { + ($0, true) + } + ) + let localPacksSignal: Signal = context.engine.stickers.searchEmojiSets(query: query) + + resultSignal = signal + |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + var allEmoticons: [String: String] = [:] + for keyword in keywords { + for emoticon in keyword.emoticons { + allEmoticons[emoticon] = keyword.keyword + } + } + if isEmojiOnly { + var items: [EmojiPagerContentComponent.Item] = [] + for (_, list) in EmojiPagerContentComponent.staticEmojiMapping { + for emojiString in list { + if allEmoticons[emojiString] != nil { + let item = EmojiPagerContentComponent.Item( + animationData: nil, + content: .staticEmoji(emojiString), + itemFile: nil, + subgroupId: nil, + icon: .none, + tintMode: .none + ) + items.append(item) + } + } + } var resultGroups: [EmojiPagerContentComponent.ItemGroup] = [] resultGroups.append(EmojiPagerContentComponent.ItemGroup( supergroupId: "search", @@ -1939,59 +2017,150 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { fillWithLoadingPlaceholders: false, items: items )) - - for (collectionId, info, _, _) in foundPacks.sets.infos { - if let info = info as? StickerPackCollectionInfo { - var topItems: [StickerPackItem] = [] - for e in foundPacks.sets.entries { - if let item = e.item as? StickerPackItem { - if e.index.collectionId == collectionId { - topItems.append(item) + return .single(resultGroups) + } else { + return combineLatest( + context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1), + context.engine.stickers.availableReactions() |> take(1), + hasPremium |> take(1), + remotePacksSignal, + localPacksSignal + ) + |> map { view, availableReactions, hasPremium, foundPacks, foundLocalPacks -> [EmojiPagerContentComponent.ItemGroup] in + var result: [(String, TelegramMediaFile?, String)] = [] + + var allEmoticons: [String: String] = [:] + for keyword in keywords { + for emoticon in keyword.emoticons { + allEmoticons[emoticon] = keyword.keyword + } + } + + for entry in view.entries { + guard let item = entry.item as? StickerPackItem else { + continue + } + for attribute in item.file.attributes { + switch attribute { + case let .CustomEmoji(_, _, alt, _): + if !item.file.isPremiumEmoji || hasPremium { + if !alt.isEmpty, let keyword = allEmoticons[alt] { + result.append((alt, item.file, keyword)) + } else if alt == query { + result.append((alt, item.file, alt)) + } } + default: + break } } - - var groupItems: [EmojiPagerContentComponent.Item] = [] - for item in topItems { - var tintMode: EmojiPagerContentComponent.Item.TintMode = .none - if item.file.isCustomTemplateEmoji { - tintMode = .primary + } + + var items: [EmojiPagerContentComponent.Item] = [] + + var existingIds = Set() + for item in result { + if let itemFile = item.1 { + if existingIds.contains(itemFile.fileId) { + continue } - - let animationData = EntityKeyboardAnimationData(file: item.file) - let resultItem = EmojiPagerContentComponent.Item( + existingIds.insert(itemFile.fileId) + let animationData = EntityKeyboardAnimationData(file: itemFile) + let item = EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), - itemFile: item.file, - subgroupId: nil, + itemFile: itemFile, subgroupId: nil, icon: .none, - tintMode: tintMode + tintMode: animationData.isTemplate ? .primary : .none ) - - groupItems.append(resultItem) + items.append(item) + } + } + + var resultGroups: [EmojiPagerContentComponent.ItemGroup] = [] + resultGroups.append(EmojiPagerContentComponent.ItemGroup( + supergroupId: "search", + groupId: "search", + title: nil, + subtitle: nil, + badge: nil, + actionButtonTitle: nil, + isFeatured: false, + isPremiumLocked: false, + isEmbedded: false, + hasClear: false, + hasEdit: false, + collapsedLineCount: nil, + displayPremiumBadges: false, + headerItem: nil, + fillWithLoadingPlaceholders: false, + items: items + )) + + var combinedSets: FoundStickerSets + combinedSets = foundLocalPacks + combinedSets = combinedSets.merge(with: foundPacks.sets) + + var existingCollectionIds = Set() + for (collectionId, info, _, _) in combinedSets.infos { + if !existingCollectionIds.contains(collectionId) { + existingCollectionIds.insert(collectionId) + } else { + continue } - resultGroups.append(EmojiPagerContentComponent.ItemGroup( - supergroupId: AnyHashable(info.id), - groupId: AnyHashable(info.id), - title: info.title, - subtitle: nil, - badge: nil, - actionButtonTitle: nil, - isFeatured: false, - isPremiumLocked: false, - isEmbedded: false, - hasClear: false, - hasEdit: false, - collapsedLineCount: 3, - displayPremiumBadges: false, - headerItem: nil, - fillWithLoadingPlaceholders: false, - items: groupItems - )) + if let info = info as? StickerPackCollectionInfo { + var topItems: [StickerPackItem] = [] + for e in combinedSets.entries { + if let item = e.item as? StickerPackItem { + if e.index.collectionId == collectionId { + topItems.append(item) + } + } + } + + var groupItems: [EmojiPagerContentComponent.Item] = [] + for item in topItems { + var tintMode: EmojiPagerContentComponent.Item.TintMode = .none + if item.file.isCustomTemplateEmoji { + tintMode = .primary + } + + let animationData = EntityKeyboardAnimationData(file: item.file) + let resultItem = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: item.file, + subgroupId: nil, + icon: .none, + tintMode: tintMode + ) + + groupItems.append(resultItem) + } + + resultGroups.append(EmojiPagerContentComponent.ItemGroup( + supergroupId: AnyHashable(info.id), + groupId: AnyHashable(info.id), + title: info.title, + subtitle: nil, + badge: nil, + actionButtonTitle: nil, + isFeatured: false, + isPremiumLocked: false, + isEmbedded: false, + hasClear: false, + hasEdit: false, + collapsedLineCount: 3, + displayPremiumBadges: false, + headerItem: nil, + fillWithLoadingPlaceholders: false, + items: groupItems + )) + } } + return resultGroups } - return resultGroups } } } @@ -2010,45 +2179,184 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { })) } case let .category(value): - let resultSignal = self.context.engine.stickers.searchEmoji(category: value) - |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in - var items: [EmojiPagerContentComponent.Item] = [] + let context = self.context + let resultSignal: Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> + if self.isMessageEffects { + let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> map { peer -> Bool in + guard case let .user(user) = peer else { + return false + } + return user.isPremium + } + |> distinctUntilChanged - var existingIds = Set() - for itemFile in files { - if existingIds.contains(itemFile.fileId) { - continue + let keywords: Signal<[String], NoError> = .single(value.identifiers) + resultSignal = keywords + |> mapToSignal { keywords -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in + var allEmoticons: [String: String] = [:] + for keyword in keywords { + allEmoticons[keyword] = keyword } - existingIds.insert(itemFile.fileId) - let animationData = EntityKeyboardAnimationData(file: itemFile) - let item = EmojiPagerContentComponent.Item( - animationData: animationData, - content: .animation(animationData), - itemFile: itemFile, subgroupId: nil, - icon: .none, - tintMode: animationData.isTemplate ? .primary : .none + + return combineLatest( + context.availableMessageEffects |> take(1), + hasPremium |> take(1) ) - items.append(item) + |> mapToSignal { availableMessageEffects, hasPremium -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in + guard let availableMessageEffects else { + return .single(([], true)) + } + + var filteredEffects: [AvailableMessageEffects.MessageEffect] = [] + for messageEffect in availableMessageEffects.messageEffects { + if allEmoticons[messageEffect.emoticon] != nil { + filteredEffects.append(messageEffect) + } + } + + var reactionEffects: [AvailableMessageEffects.MessageEffect] = [] + var stickerEffects: [AvailableMessageEffects.MessageEffect] = [] + for messageEffect in filteredEffects { + if messageEffect.effectAnimation != nil { + reactionEffects.append(messageEffect) + } else { + stickerEffects.append(messageEffect) + } + } + + struct ItemGroup { + var supergroupId: AnyHashable + var id: AnyHashable + var title: String? + var subtitle: String? + var actionButtonTitle: String? + var isPremiumLocked: Bool + var isFeatured: Bool + var displayPremiumBadges: Bool + var hasEdit: Bool + var headerItem: EntityKeyboardAnimationData? + var items: [EmojiPagerContentComponent.Item] + } + + var resultGroups: [ItemGroup] = [] + var resultGroupIndexById: [AnyHashable: Int] = [:] + + for i in 0 ..< 2 { + let groupId = i == 0 ? "reactions" : "stickers" + for item in i == 0 ? reactionEffects : stickerEffects { + let itemFile: TelegramMediaFile = item.effectSticker + + var tintMode: EmojiPagerContentComponent.Item.TintMode = .none + if itemFile.isCustomTemplateEmoji { + tintMode = .primary + } + + let icon: EmojiPagerContentComponent.Item.Icon + if i == 0 { + if !hasPremium && item.isPremium { + icon = .locked + } else { + icon = .none + } + } else { + if !hasPremium && item.isPremium { + icon = .locked + } else if let staticIcon = item.staticIcon { + icon = .customFile(staticIcon) + } else { + icon = .text(item.emoticon) + } + } + + let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none) + let resultItem = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: itemFile, + subgroupId: nil, + icon: icon, + tintMode: tintMode + ) + + if let groupIndex = resultGroupIndexById[groupId] { + resultGroups[groupIndex].items.append(resultItem) + } else { + resultGroupIndexById[groupId] = resultGroups.count + //TODO:localize + resultGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: i == 0 ? nil : strings.Chat_MessageEffectMenu_SectionMessageEffects, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem])) + } + } + } + + let allItemGroups = resultGroups.map { group -> EmojiPagerContentComponent.ItemGroup in + let hasClear = false + let isEmbedded = false + + return EmojiPagerContentComponent.ItemGroup( + supergroupId: group.supergroupId, + groupId: group.id, + title: group.title, + subtitle: group.subtitle, + badge: nil, + actionButtonTitle: group.actionButtonTitle, + isFeatured: group.isFeatured, + isPremiumLocked: group.isPremiumLocked, + isEmbedded: isEmbedded, + hasClear: hasClear, + hasEdit: group.hasEdit, + collapsedLineCount: nil, + displayPremiumBadges: group.displayPremiumBadges, + headerItem: group.headerItem, + fillWithLoadingPlaceholders: false, + items: group.items + ) + } + + return .single((allItemGroups, true)) + } + } + } else { + resultSignal = self.context.engine.stickers.searchEmoji(category: value) + |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in + var items: [EmojiPagerContentComponent.Item] = [] + + var existingIds = Set() + for itemFile in files { + if existingIds.contains(itemFile.fileId) { + continue + } + existingIds.insert(itemFile.fileId) + let animationData = EntityKeyboardAnimationData(file: itemFile) + let item = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: itemFile, subgroupId: nil, + icon: .none, + tintMode: animationData.isTemplate ? .primary : .none + ) + items.append(item) + } + + return .single(([EmojiPagerContentComponent.ItemGroup( + supergroupId: "search", + groupId: "search", + title: nil, + subtitle: nil, + badge: nil, + actionButtonTitle: nil, + isFeatured: false, + isPremiumLocked: false, + isEmbedded: false, + hasClear: false, + hasEdit: false, + collapsedLineCount: nil, + displayPremiumBadges: false, + headerItem: nil, + fillWithLoadingPlaceholders: false, + items: items + )], isFinalResult)) } - - return .single(([EmojiPagerContentComponent.ItemGroup( - supergroupId: "search", - groupId: "search", - title: nil, - subtitle: nil, - badge: nil, - actionButtonTitle: nil, - isFeatured: false, - isPremiumLocked: false, - isEmbedded: false, - hasClear: false, - hasEdit: false, - collapsedLineCount: nil, - displayPremiumBadges: false, - headerItem: nil, - fillWithLoadingPlaceholders: false, - items: items - )], isFinalResult)) } var version = 0 @@ -2098,7 +2406,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { peekBehavior: nil, customLayout: emojiContentLayout, externalBackground: self.backgroundNode.vibrancyEffectView == nil ? nil : EmojiPagerContentComponent.ExternalBackground( - effectContainerView: self.backgroundNode.vibrancyEffectView?.contentView + effectContainerView: self.backgroundNode.vibrantExpandedContentContainer ), externalExpansionView: self.view, customContentView: nil, @@ -2187,6 +2495,12 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { continue } itemNode.layer.animateAlpha(from: itemNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + if let selectionView = itemNode.selectionView { + selectionView.layer.animateAlpha(from: selectionView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + if let selectionTintView = itemNode.selectionTintView { + selectionTintView.layer.animateAlpha(from: selectionTintView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + } } if let titleLabelView = self.titleLabelView { @@ -2301,7 +2615,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } if let customReactionSource = self.customReactionSource { - let itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: customReactionSource.item, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: false, isLocked: false, useDirectRendering: false) + let itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: customReactionSource.item, icon: .none, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: false, isLocked: false, useDirectRendering: false) if let contents = customReactionSource.layer.contents { itemNode.setCustomContents(contents: contents) } @@ -2728,11 +3042,13 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { self.isExpandedUpdated(.animated(duration: 0.4, curve: .spring)) } else if let reaction = self.reaction(at: point) { switch reaction { - case let .reaction(reactionItem): + case let .reaction(reactionItem, icon): if case .custom = reactionItem.updateMessageReaction, let hasPremium = self.hasPremium, !hasPremium, !self.allPresetReactionsAreAvailable { self.premiumReactionsSelected?(reactionItem.stillAnimation) } else if self.reactionsLocked { self.premiumReactionsSelected?(reactionItem.stillAnimation) + } else if case .locked = icon { + self.premiumReactionsSelected?(reactionItem.stillAnimation) } else { self.reactionSelected?(reactionItem.updateMessageReaction, false) } @@ -2906,7 +3222,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { if !itemNode.isAnimationLoaded { return nil } - return .reaction(itemNode.item) + return .reaction(item: itemNode.item, icon: itemNode.icon) } else if let itemNode = itemNode as? EmojiItemNode { return .staticEmoji(itemNode.emoji) } else if let _ = itemNode as? PremiumReactionsNode { @@ -2966,13 +3282,13 @@ public final class StandaloneReactionAnimation: ASDisplayNode { self.isUserInteractionEnabled = false } - public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, animationCache: AnimationCache, reaction: ReactionItem, avatarPeers: [EnginePeer], playHaptic: Bool, isLarge: Bool, forceSmallEffectAnimation: Bool = false, hideCenterAnimation: Bool = false, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) { - self.animateReactionSelection(context: context, theme: theme, animationCache: animationCache, reaction: reaction, avatarPeers: avatarPeers, playHaptic: playHaptic, isLarge: isLarge, forceSmallEffectAnimation: forceSmallEffectAnimation, hideCenterAnimation: hideCenterAnimation, targetView: targetView, addStandaloneReactionAnimation: addStandaloneReactionAnimation, currentItemNode: nil, completion: completion) + public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, animationCache: AnimationCache, reaction: ReactionItem, customEffectResource: MediaResource? = nil, avatarPeers: [EnginePeer], playHaptic: Bool, isLarge: Bool, playCenterReaction: Bool = true, forceSmallEffectAnimation: Bool = false, hideCenterAnimation: Bool = false, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, completion: @escaping () -> Void) { + self.animateReactionSelection(context: context, theme: theme, animationCache: animationCache, reaction: reaction, customEffectResource: customEffectResource, avatarPeers: avatarPeers, playHaptic: playHaptic, isLarge: isLarge, playCenterReaction: playCenterReaction, forceSmallEffectAnimation: forceSmallEffectAnimation, hideCenterAnimation: hideCenterAnimation, targetView: targetView, addStandaloneReactionAnimation: addStandaloneReactionAnimation, currentItemNode: nil, completion: completion) } public var currentDismissAnimation: (() -> Void)? - public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, animationCache: AnimationCache, reaction: ReactionItem, avatarPeers: [EnginePeer], playHaptic: Bool, isLarge: Bool, forceSmallEffectAnimation: Bool = false, hideCenterAnimation: Bool = false, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, currentItemNode: ReactionNode?, completion: @escaping () -> Void) { + public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, animationCache: AnimationCache, reaction: ReactionItem, customEffectResource: MediaResource? = nil, avatarPeers: [EnginePeer], playHaptic: Bool, isLarge: Bool, playCenterReaction: Bool = true, forceSmallEffectAnimation: Bool = false, hideCenterAnimation: Bool = false, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, currentItemNode: ReactionNode?, completion: @escaping () -> Void) { guard let sourceSnapshotView = targetView.snapshotContentTree() else { completion() return @@ -2984,28 +3300,36 @@ public final class StandaloneReactionAnimation: ASDisplayNode { self.targetView = targetView - let itemNode: ReactionNode - if let currentItemNode = currentItemNode { - itemNode = currentItemNode + let itemNode: ReactionNode? + if playCenterReaction { + if let currentItemNode = currentItemNode { + itemNode = currentItemNode + } else { + let animationRenderer = MultiAnimationRendererImpl() + itemNode = ReactionNode(context: context, theme: theme, item: reaction, icon: .none, animationCache: animationCache, animationRenderer: animationRenderer, loopIdle: false, isLocked: false) + } + self.itemNode = itemNode } else { - let animationRenderer = MultiAnimationRendererImpl() - itemNode = ReactionNode(context: context, theme: theme, item: reaction, animationCache: animationCache, animationRenderer: animationRenderer, loopIdle: false, isLocked: false) + itemNode = nil } - self.itemNode = itemNode let switchToInlineImmediately: Bool - if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker || itemNode.item.listAnimation.isAnimatedSticker || itemNode.item.listAnimation.isStaticEmoji { - switch itemNode.item.reaction.rawValue { - case .builtin: + if let itemNode { + if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker || itemNode.item.listAnimation.isAnimatedSticker || itemNode.item.listAnimation.isStaticEmoji { + switch itemNode.item.reaction.rawValue { + case .builtin: + switchToInlineImmediately = false + case .custom: + switchToInlineImmediately = true + } + } else { switchToInlineImmediately = false - case .custom: - switchToInlineImmediately = true } } else { switchToInlineImmediately = false } - if !forceSmallEffectAnimation && !switchToInlineImmediately && !hideCenterAnimation { + if let itemNode, !forceSmallEffectAnimation, !switchToInlineImmediately, !hideCenterAnimation { if let targetView = targetView as? ReactionIconView, !isLarge { self.itemNodeIsEmbedded = true targetView.addSubnode(itemNode) @@ -3014,28 +3338,27 @@ public final class StandaloneReactionAnimation: ASDisplayNode { } } - itemNode.expandedAnimationDidBegin = { [weak self, weak targetView] in - guard let strongSelf = self, let targetView = targetView else { - return - } - if let targetView = targetView as? ReactionIconView, !isLarge { - strongSelf.itemNodeIsEmbedded = true - - targetView.updateIsAnimationHidden(isAnimationHidden: true, transition: .immediate) - } else { - targetView.isHidden = true + if let itemNode { + itemNode.expandedAnimationDidBegin = { [weak self, weak targetView] in + guard let strongSelf = self, let targetView = targetView else { + return + } + if let targetView = targetView as? ReactionIconView, !isLarge { + strongSelf.itemNodeIsEmbedded = true + + targetView.updateIsAnimationHidden(isAnimationHidden: true, transition: .immediate) + } else { + targetView.isHidden = true + } } + + itemNode.isExtracted = true } - - itemNode.isExtracted = true var selfTargetBounds = targetView.bounds if let targetView = targetView as? ReactionIconView, let iconFrame = targetView.iconFrame { selfTargetBounds = iconFrame } - /*if case .builtin = itemNode.item.reaction.rawValue { - selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5) - }*/ let selfTargetRect = self.view.convert(selfTargetBounds, from: targetView) @@ -3064,27 +3387,32 @@ public final class StandaloneReactionAnimation: ASDisplayNode { }) } - if self.itemNodeIsEmbedded { - itemNode.frame = selfTargetBounds - } else { - itemNode.frame = expandedFrame + if let itemNode { + if self.itemNodeIsEmbedded { + itemNode.frame = selfTargetBounds + } else { + itemNode.frame = expandedFrame + + itemNode.layer.animateSpring(from: (selfTargetRect.width / expandedFrame.width) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.7) + } - itemNode.layer.animateSpring(from: (selfTargetRect.width / expandedFrame.width) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.7) + itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: isLarge, isPreviewing: false, transition: .immediate) } - itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: isLarge, isPreviewing: false, transition: .immediate) - - let additionalAnimation: TelegramMediaFile? + var additionalAnimationResource: MediaResource? if isLarge && !forceSmallEffectAnimation { - additionalAnimation = itemNode.item.largeApplicationAnimation + additionalAnimationResource = reaction.largeApplicationAnimation?.resource } else { - additionalAnimation = itemNode.item.applicationAnimation + additionalAnimationResource = reaction.applicationAnimation?.resource + } + if additionalAnimationResource == nil, let customEffectResource { + additionalAnimationResource = customEffectResource } let additionalAnimationNode: AnimatedStickerNode? var genericAnimationView: AnimationView? - if let additionalAnimation = additionalAnimation { + if let additionalAnimationResource { let additionalAnimationNodeValue: AnimatedStickerNode if self.useDirectRendering { additionalAnimationNodeValue = DirectAnimatedStickerNode() @@ -3099,15 +3427,13 @@ public final class StandaloneReactionAnimation: ASDisplayNode { } } - var additionalCachePathPrefix: String? - additionalCachePathPrefix = itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id) - additionalCachePathPrefix = nil + let additionalCachePathPrefix: String? = nil - additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 1.33), height: Int(effectFrame.height * 1.33), playbackMode: .once, mode: .direct(cachePathPrefix: additionalCachePathPrefix)) + additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: context.account, resource: additionalAnimationResource), width: Int(effectFrame.width * 1.33), height: Int(effectFrame.height * 1.33), playbackMode: .once, mode: .direct(cachePathPrefix: additionalCachePathPrefix)) additionalAnimationNodeValue.frame = effectFrame additionalAnimationNodeValue.updateLayout(size: effectFrame.size) self.addSubnode(additionalAnimationNodeValue) - } else if itemNode.item.isCustom { + } else if reaction.isCustom { var effectURL: URL? if let genericReactionEffect = self.genericReactionEffect { effectURL = URL(fileURLWithPath: genericReactionEffect) @@ -3157,18 +3483,18 @@ public final class StandaloneReactionAnimation: ASDisplayNode { genericAnimationView = view - let animationCache = itemNode.context.animationCache - let animationRenderer = itemNode.context.animationRenderer + let animationCache = context.animationCache + let animationRenderer = context.animationRenderer for i in 1 ... 7 { let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "placeholder_\(i)")) for animationLayer in allLayers { let baseItemLayer = InlineStickerItemLayer( - context: itemNode.context, + context: context, userLocation: .other, attemptSynchronousLoad: false, - emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: itemNode.item.listAnimation.fileId.id, file: itemNode.item.listAnimation), - file: itemNode.item.listAnimation, + emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: reaction.listAnimation.fileId.id, file: reaction.listAnimation), + file: reaction.listAnimation, cache: animationCache, renderer: animationRenderer, placeholderColor: UIColor(white: 0.0, alpha: 0.0), @@ -3256,15 +3582,6 @@ public final class StandaloneReactionAnimation: ASDisplayNode { return } - /*if switchToInlineImmediately { - targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate) - itemNode.isHidden = true - } else { - targetView.updateIsAnimationHidden(isAnimationHidden: true, transition: .immediate) - targetView.addSubnode(itemNode) - itemNode.frame = selfTargetBounds - }*/ - if forceSmallEffectAnimation { if let additionalAnimationNode = additionalAnimationNode { additionalAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak additionalAnimationNode] _ in @@ -3275,7 +3592,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode { mainAnimationCompleted = true intermediateCompletion() } else { - if isLarge { + if isLarge, let itemNode { let genericReactionEffect = strongSelf.genericReactionEffect strongSelf.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: true, completion: { if let addStandaloneReactionAnimation = addStandaloneReactionAnimation { @@ -3284,10 +3601,10 @@ public final class StandaloneReactionAnimation: ASDisplayNode { addStandaloneReactionAnimation(standaloneReactionAnimation) standaloneReactionAnimation.animateReactionSelection( - context: itemNode.context, - theme: itemNode.context.sharedContext.currentPresentationData.with({ $0 }).theme, + context: context, + theme: context.sharedContext.currentPresentationData.with({ $0 }).theme, animationCache: animationCache, - reaction: itemNode.item, + reaction: reaction, avatarPeers: avatarPeers, playHaptic: false, isLarge: false, @@ -3331,10 +3648,8 @@ public final class StandaloneReactionAnimation: ASDisplayNode { } if forceSmallEffectAnimation { - //itemNode.mainAnimationCompletion = { - mainAnimationCompleted = true - maybeBeginDismissAnimation() - //} + mainAnimationCompleted = true + maybeBeginDismissAnimation() } if let additionalAnimationNode = additionalAnimationNode { diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index 044584d3b4a..9140f9745a2 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -14,6 +14,7 @@ import AnimationCache import MultiAnimationRenderer import ShimmerEffect import GenerateStickerPlaceholderImage +import EntityKeyboard private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? { return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in @@ -52,13 +53,14 @@ protocol ReactionItemNode: ASDisplayNode { func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) } -private let lockedBackgroundImage: UIImage = generateFilledCircleImage(diameter: 12.0, color: .white)!.withRenderingMode(.alwaysTemplate) +private let lockedBackgroundImage: UIImage = generateFilledCircleImage(diameter: 16.0, color: .white)!.withRenderingMode(.alwaysTemplate) private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) public final class ReactionNode: ASDisplayNode, ReactionItemNode { let context: AccountContext let theme: PresentationTheme let item: ReactionItem + let icon: EmojiPagerContentComponent.Item.Icon private let loopIdle: Bool private let isLocked: Bool private let hasAppearAnimation: Bool @@ -102,10 +104,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { return self.staticAnimationNode.currentFrameImage != nil } - public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, loopIdle: Bool, isLocked: Bool, hasAppearAnimation: Bool = true, useDirectRendering: Bool = false) { + public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, icon: EmojiPagerContentComponent.Item.Icon, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, loopIdle: Bool, isLocked: Bool, hasAppearAnimation: Bool = true, useDirectRendering: Bool = false) { self.context = context self.theme = theme self.item = item + self.icon = icon self.loopIdle = loopIdle self.isLocked = isLocked self.hasAppearAnimation = hasAppearAnimation @@ -126,6 +129,12 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { super.init() + if item.stillAnimation.isCustomTemplateEmoji { + if let animationNode = self.staticAnimationNode as? DefaultAnimatedStickerNodeImpl { + animationNode.dynamicColor = theme.chat.inputPanel.panelControlAccentColor + } + } + if let animateInAnimationNode = self.animateInAnimationNode { self.addSubnode(animateInAnimationNode) } @@ -469,11 +478,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { } if let lockBackgroundView = self.lockBackgroundView, let lockIconView = self.lockIconView, let iconImage = lockIconView.image { - let lockSize: CGFloat = 12.0 + let lockSize: CGFloat = 16.0 let iconBackgroundFrame = CGRect(origin: CGPoint(x: animationFrame.maxX - lockSize, y: animationFrame.maxY - lockSize), size: CGSize(width: lockSize, height: lockSize)) transition.updateFrame(view: lockBackgroundView, frame: iconBackgroundFrame) - let iconFactor: CGFloat = 0.7 + let iconFactor: CGFloat = 1.0 let iconImageSize = CGSize(width: floor(iconImage.size.width * iconFactor), height: floor(iconImage.size.height * iconFactor)) transition.updateFrame(view: lockIconView, frame: CGRect(origin: CGPoint(x: iconBackgroundFrame.minX + floorToScreenPixels((iconBackgroundFrame.width - iconImageSize.width) * 0.5), y: iconBackgroundFrame.minY + floorToScreenPixels((iconBackgroundFrame.height - iconImageSize.height) * 0.5)), size: iconImageSize)) diff --git a/submodules/SSignalKit/SwiftSignalKit/BUILD b/submodules/SSignalKit/SwiftSignalKit/BUILD index b3c12dbee1c..64969ad50d9 100644 --- a/submodules/SSignalKit/SwiftSignalKit/BUILD +++ b/submodules/SSignalKit/SwiftSignalKit/BUILD @@ -7,7 +7,7 @@ swift_library( "Source/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift index a3eaabd3ed8..4bbc99d2fe0 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift @@ -220,6 +220,12 @@ public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal, _ s12: Signal, _ s13: Signal, _ s14: Signal, _ s15: Signal, _ s16: Signal, _ s17: Signal, _ s18: Signal, _ s19: Signal, _ s20: Signal, _ s21: Signal, _ s22: Signal, _ s23: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23), E> { + return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13), signalOfAny(s14), signalOfAny(s15), signalOfAny(s16), signalOfAny(s17), signalOfAny(s18), signalOfAny(s19), signalOfAny(s20), signalOfAny(s21), signalOfAny(s22), signalOfAny(s23)], combine: { values in + return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13, values[13] as! T14, values[14] as! T15, values[15] as! T16, values[16] as! T17, values[17] as! T18, values[18] as! T19, values[19] as! T20, values[20] as! T21, values[21] as! T22, values[22] as! T23) + }, initialValues: [:], queue: queue) +} + public func combineLatest(queue: Queue? = nil, _ signals: [Signal]) -> Signal<[T], E> { if signals.count == 0 { return single([T](), E.self) diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_SwiftConcurrency.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_SwiftConcurrency.swift new file mode 100644 index 00000000000..59c323c4e31 --- /dev/null +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_SwiftConcurrency.swift @@ -0,0 +1,63 @@ +// MARK: Nicegram + +public class SignalAsyncStream: AsyncSequence { + public typealias Element = T + public typealias AsyncIterator = SignalAsyncStream + + public func makeAsyncIterator() -> Self { + return self + } + + private let stream: AsyncStream + + private lazy var iterator = stream.makeAsyncIterator() + + public init( + _ upstream: Signal, + bufferingPolicy: AsyncStream.Continuation.BufferingPolicy + ) { + stream = AsyncStream(T.self, bufferingPolicy: bufferingPolicy) { continuation in + let disposable = upstream.start( + next: { value in + continuation.yield(value) + }, + error: { _ in + continuation.finish() + }, + completed: { + continuation.finish() + } + ) + + continuation.onTermination = { _ in + disposable.dispose() + } + } + } +} + +extension SignalAsyncStream: AsyncIteratorProtocol { + public func next() async -> T? { + return await iterator.next() + } +} + +public extension Signal { + func asyncStream( + _ bufferingPolicy: AsyncStream.Continuation.BufferingPolicy + ) -> SignalAsyncStream { + SignalAsyncStream(self, bufferingPolicy: bufferingPolicy) + } +} + + +public extension Signal { + func awaitForFirstValue() async throws -> T { + let stream = self.asyncStream(.unbounded) + for await value in stream { + return value + } + throw CancellationError() + } +} + diff --git a/submodules/SaveToCameraRoll/BUILD b/submodules/SaveToCameraRoll/BUILD index d7db544f62d..b6cca8d3b84 100644 --- a/submodules/SaveToCameraRoll/BUILD +++ b/submodules/SaveToCameraRoll/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ScreenCaptureDetection/BUILD b/submodules/ScreenCaptureDetection/BUILD index 39f82dfb7d5..a30f9625347 100644 --- a/submodules/ScreenCaptureDetection/BUILD +++ b/submodules/ScreenCaptureDetection/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/SearchBarNode/BUILD b/submodules/SearchBarNode/BUILD index 1842fa27e0c..96abef5bf7d 100644 --- a/submodules/SearchBarNode/BUILD +++ b/submodules/SearchBarNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/SearchBarNode/Sources/SearchBarNode.swift b/submodules/SearchBarNode/Sources/SearchBarNode.swift index 3b9ff809508..131323d318a 100644 --- a/submodules/SearchBarNode/Sources/SearchBarNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarNode.swift @@ -16,6 +16,14 @@ 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 generateCashtagIcon(color: UIColor) -> UIImage? { + return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Cashtag"), color: color) +} + private func generateClearIcon(color: UIColor) -> UIImage? { return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color) } @@ -844,6 +852,13 @@ public enum SearchBarStyle { } public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { + public enum Icon { + case loupe + case hashtag + case cashtag + } + public let icon: Icon + public var cancel: (() -> Void)? public var textUpdated: ((String, String?) -> Void)? public var textReturned: ((String) -> Void)? @@ -939,6 +954,15 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { } } + public var autocapitalization: UITextAutocapitalizationType { + get { + return self.textField.autocapitalizationType + } + set { + self.textField.autocapitalizationType = newValue + } + } + private var validLayout: (CGSize, CGFloat, CGFloat)? private let fieldStyle: SearchBarStyle @@ -947,11 +971,12 @@ 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 self.backgroundNode.isHidden = !displayBackground @@ -1036,7 +1061,16 @@ 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) + case .cashtag: + icon = generateCashtagIcon(color: theme.inputIcon) + } + self.iconNode.image = icon self.textField.keyboardAppearance = theme.keyboard.keyboardAppearance self.textField.tintColor = theme.accent @@ -1123,6 +1157,11 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { } self.textBackgroundNode.layer.animateFrame(from: initialTextBackgroundFrame, to: self.textBackgroundNode.frame, duration: duration, timingFunction: timingFunction) + if initialTextBackgroundFrame.height.isZero { + self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + self.textField.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + let textFieldFrame = self.textField.frame var tokensWidth = self.textField.tokensWidth if tokensWidth > 0.0 { @@ -1237,24 +1276,24 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { separatorCompleted = true intermediateCompletion() }) - + self.textBackgroundNode.isHidden = true /*if let accessoryComponentView = node.accessoryComponentView { - let tempContainer = UIView() - - let accessorySize = accessoryComponentView.bounds.size - tempContainer.frame = CGRect(origin: CGPoint(x: self.textBackgroundNode.frame.maxX - accessorySize.width - 4.0, y: floor((self.textBackgroundNode.frame.minY + self.textBackgroundNode.frame.height - accessorySize.height) / 2.0)), size: accessorySize) - - let targetTempContainerFrame = CGRect(origin: CGPoint(x: targetTextBackgroundFrame.maxX - accessorySize.width - 4.0, y: floor((targetTextBackgroundFrame.minY + 8.0 + targetTextBackgroundFrame.height - accessorySize.height) / 2.0)), size: accessorySize) - - tempContainer.layer.animateFrame(from: tempContainer.frame, to: targetTempContainerFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) - - accessoryComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - tempContainer.addSubview(accessoryComponentView) - self.view.addSubview(tempContainer) - }*/ - + let tempContainer = UIView() + + let accessorySize = accessoryComponentView.bounds.size + tempContainer.frame = CGRect(origin: CGPoint(x: self.textBackgroundNode.frame.maxX - accessorySize.width - 4.0, y: floor((self.textBackgroundNode.frame.minY + self.textBackgroundNode.frame.height - accessorySize.height) / 2.0)), size: accessorySize) + + let targetTempContainerFrame = CGRect(origin: CGPoint(x: targetTextBackgroundFrame.maxX - accessorySize.width - 4.0, y: floor((targetTextBackgroundFrame.minY + 8.0 + targetTextBackgroundFrame.height - accessorySize.height) / 2.0)), size: accessorySize) + + tempContainer.layer.animateFrame(from: tempContainer.frame, to: targetTempContainerFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + + accessoryComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + tempContainer.addSubview(accessoryComponentView) + self.view.addSubview(tempContainer) + }*/ + self.textBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak node] _ in textBackgroundCompleted = true intermediateCompletion() @@ -1273,8 +1312,11 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.insertSubnode(transitionBackgroundNode, aboveSubnode: self.textBackgroundNode) transitionBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) - - if let snapshot = node.labelNode.layer.snapshotContentTree() { + + if targetTextBackgroundFrame.height.isZero { + self.iconNode.layer.animateAlpha(from: self.iconNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.textField.layer.animateAlpha(from: self.textField.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + } else if let snapshot = node.labelNode.layer.snapshotContentTree() { snapshot.frame = CGRect(origin: self.textField.placeholderLabel.frame.origin.offsetBy(dx: 0.0, dy: UIScreenPixel), size: node.labelNode.frame.size) self.textField.layer.addSublayer(snapshot) snapshot.animateAlpha(from: 0.0, to: 1.0, duration: duration * 2.0 / 3.0, timingFunction: CAMediaTimingFunctionName.linear.rawValue) diff --git a/submodules/SearchPeerMembers/BUILD b/submodules/SearchPeerMembers/BUILD index 2dfde4f9ac1..8efaa9fbae6 100644 --- a/submodules/SearchPeerMembers/BUILD +++ b/submodules/SearchPeerMembers/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/SearchUI/BUILD b/submodules/SearchUI/BUILD index 7210ffaa383..cd12719981f 100644 --- a/submodules/SearchUI/BUILD +++ b/submodules/SearchUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/SearchUI/Sources/SearchDisplayController.swift b/submodules/SearchUI/Sources/SearchDisplayController.swift index 9da8055e065..50420e6b50d 100644 --- a/submodules/SearchUI/Sources/SearchDisplayController.swift +++ b/submodules/SearchUI/Sources/SearchDisplayController.swift @@ -268,16 +268,20 @@ public final class SearchDisplayController { searchBar?.removeFromSupernode() }) } else { - searchBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak searchBar] _ in - searchBar?.removeFromSupernode() + searchBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak searchBar] finished in + if finished { + searchBar?.removeFromSupernode() + } }) } let backgroundNode = self.backgroundNode let contentNode = self.contentNode if animated { - backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak backgroundNode] _ in - backgroundNode?.removeFromSupernode() + backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak backgroundNode] finished in + if finished { + backgroundNode?.removeFromSupernode() + } }) } else { backgroundNode.removeFromSupernode() diff --git a/submodules/SectionHeaderItem/BUILD b/submodules/SectionHeaderItem/BUILD index 8d7a15d7fce..5a166ec1286 100644 --- a/submodules/SectionHeaderItem/BUILD +++ b/submodules/SectionHeaderItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/SegmentedControlNode/BUILD b/submodules/SegmentedControlNode/BUILD index ff054555a3f..fea3724a986 100644 --- a/submodules/SegmentedControlNode/BUILD +++ b/submodules/SegmentedControlNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/SelectablePeerNode/BUILD b/submodules/SelectablePeerNode/BUILD index b13c4278f4c..0a6c5db5075 100644 --- a/submodules/SelectablePeerNode/BUILD +++ b/submodules/SelectablePeerNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/SemanticStatusNode/BUILD b/submodules/SemanticStatusNode/BUILD index e4a4b8d8403..60f65635b3a 100644 --- a/submodules/SemanticStatusNode/BUILD +++ b/submodules/SemanticStatusNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index ce27f3ef1a3..2b84d1ba93f 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -12,7 +12,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift index f8291770875..59c2896d0c2 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift @@ -229,7 +229,13 @@ func changePhoneNumberCodeController(context: AccountContext, phoneNumber: Strin |> take(1) |> mapToSignal { _ -> Signal in return Signal { subscriber in - return context.engine.accountData.requestNextChangeAccountPhoneNumberVerification(phoneNumber: phoneNumber, phoneCodeHash: data.hash).start(next: { next in + return context.engine.accountData.requestNextChangeAccountPhoneNumberVerification( + phoneNumber: phoneNumber, + phoneCodeHash: data.hash, + apiId: context.sharedContext.networkArguments.apiId, + apiHash: context.sharedContext.networkArguments.apiHash, + firebaseSecretStream: context.sharedContext.firebaseSecretStream + ).start(next: { next in currentDataPromise?.set(.single(next)) }, error: { error in diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift index 7e9b78957dd..12a535cc9dd 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift @@ -36,7 +36,13 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll authorizationPushConfiguration |> castError(RequestChangeAccountPhoneNumberVerificationError.self) |> mapToSignal { authorizationPushConfiguration in - return context.engine.accountData.requestChangeAccountPhoneNumberVerification(phoneNumber: phoneNumber, pushNotificationConfiguration: authorizationPushConfiguration, firebaseSecretStream: context.sharedContext.firebaseSecretStream) + return context.engine.accountData.requestChangeAccountPhoneNumberVerification( + apiId: context.sharedContext.networkArguments.apiId, + apiHash: context.sharedContext.networkArguments.apiHash, + phoneNumber: phoneNumber, + pushNotificationConfiguration: authorizationPushConfiguration, + firebaseSecretStream: context.sharedContext.firebaseSecretStream + ) } |> deliverOnMainQueue).start(next: { [weak controller] next in controller?.inProgress = false diff --git a/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift b/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift index 9f4dca4e0ec..b361db75b4b 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift @@ -167,7 +167,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry { switch self { case let .peer(peer, presence): return ItemListAvatarAndNameInfoItem( - accountContext: arguments.context, + itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), mode: .generic, diff --git a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift index 684258ebd4b..f9a67fe0970 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift @@ -311,7 +311,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo dismissImpl?() context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in - }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in pushControllerImpl?(controller) }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) }) @@ -352,7 +352,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo dismissImpl?() context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in - }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in pushControllerImpl?(controller) }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) }) diff --git a/submodules/SettingsUI/Sources/LogoutOptionsController.swift b/submodules/SettingsUI/Sources/LogoutOptionsController.swift index a399dab7ec0..67aa3a7444b 100644 --- a/submodules/SettingsUI/Sources/LogoutOptionsController.swift +++ b/submodules/SettingsUI/Sources/LogoutOptionsController.swift @@ -218,7 +218,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle dismissImpl?() context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in - }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in pushControllerImpl?(controller) }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) }) diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index 61a47a2228c..37df41205dc 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -1069,7 +1069,7 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList let _ = (cachedFaqInstantPage(context: context) |> deliverOnMainQueue).start(next: { resolvedUrl in context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in - }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in present(.push, controller) }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) }) diff --git a/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift index 7cb21fde383..9639d7bfce8 100644 --- a/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift @@ -210,11 +210,18 @@ private struct ArchivedStickerPacksControllerState: Equatable { } } -private func archivedStickerPacksControllerEntries(context: AccountContext, presentationData: PresentationData, state: ArchivedStickerPacksControllerState, packs: [ArchivedStickerPackItem]?, installedView: CombinedView, stickerSettings: StickerSettings) -> [ArchivedStickerPacksEntry] { +private func archivedStickerPacksControllerEntries(context: AccountContext, mode: ArchivedStickerPacksControllerMode, presentationData: PresentationData, state: ArchivedStickerPacksControllerState, packs: [ArchivedStickerPackItem]?, installedView: CombinedView, stickerSettings: StickerSettings) -> [ArchivedStickerPacksEntry] { var entries: [ArchivedStickerPacksEntry] = [] if let packs = packs { - entries.append(.info(presentationData.theme, presentationData.strings.StickerPacksSettings_ArchivedPacks_Info + "\n\n")) + let info: String + switch mode { + case .emoji: + info = presentationData.strings.EmojiPacksSettings_ArchivedPacks_Info + default: + info = presentationData.strings.StickerPacksSettings_ArchivedPacks_Info + } + entries.append(.info(presentationData.theme, info + "\n\n")) var installedIds = Set() if let view = installedView.views[.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionIdsView, let ids = view.idsByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { @@ -340,7 +347,18 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv } } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in + let title: String + let text: String + switch mode { + case .emoji: + title = presentationData.strings.EmojiPackActionInfo_AddedTitle + text = presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string + default: + title = presentationData.strings.StickerPackActionInfo_AddedTitle + text = presentationData.strings.StickerPackActionInfo_AddedText(info.title).string + } + + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: title, text: text, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return true }), nil) @@ -495,8 +513,17 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv presentControllerImpl?(actionSheet, nil) }), .init(title: presentationData.strings.StickerPacks_ActionUnarchive, isEnabled: selectedCount > 0, action: { let actionSheet = ActionSheetController(presentationData: presentationData) + + let text: String + switch mode { + case .emoji: + text = presentationData.strings.EmojiPacks_UnarchiveEmojiPacksConfirmation(selectedCount) + default: + text = presentationData.strings.StickerPacks_UnarchiveStickerPacksConfirmation(selectedCount) + } + var items: [ActionSheetItem] = [] - items.append(ActionSheetButtonItem(title: presentationData.strings.StickerPacks_UnarchiveStickerPacksConfirmation(selectedCount), color: .destructive, action: { [weak actionSheet] in + items.append(ActionSheetButtonItem(title: text, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() updateState { @@ -550,9 +577,17 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) } - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.StickerPacksSettings_ArchivedPacks), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) + let title: String + switch mode { + case .emoji: + title = presentationData.strings.EmojiPacksSettings_ArchivedPacks + default: + title = presentationData.strings.StickerPacksSettings_ArchivedPacks + } + + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: archivedStickerPacksControllerEntries(context: context, presentationData: presentationData, state: state, packs: packs, installedView: installedView, stickerSettings: stickerSettings), style: .blocks, emptyStateItem: emptyStateItem, toolbarItem: toolbarItem, animateChanges: previous != nil && packs != nil && (previous! != 0 && previous! >= packs!.count - 10)) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: archivedStickerPacksControllerEntries(context: context, mode: mode, presentationData: presentationData, state: state, packs: packs, installedView: installedView, stickerSettings: stickerSettings), style: .blocks, emptyStateItem: emptyStateItem, toolbarItem: toolbarItem, animateChanges: previous != nil && packs != nil && (previous! != 0 && previous! >= packs!.count - 10)) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() diff --git a/submodules/SettingsUI/Sources/ThemePickerController.swift b/submodules/SettingsUI/Sources/ThemePickerController.swift index cb70fa94eae..207d40c1d97 100644 --- a/submodules/SettingsUI/Sources/ThemePickerController.swift +++ b/submodules/SettingsUI/Sources/ThemePickerController.swift @@ -567,7 +567,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme }) }) - c.dismiss(completion: { + c?.dismiss(completion: { pushControllerImpl?(controller) }) }))) @@ -615,14 +615,14 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme }) })) - c.dismiss(completion: { + c?.dismiss(completion: { pushControllerImpl?(controller) }) }) }))) } items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_ShareTheme, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let shareController = ShareController(context: context, subject: .url("https://t.me/addtheme/\(theme.theme.slug)"), preferredAction: .default) shareController.actionCompleted = { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -633,7 +633,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme }))) if !theme.theme.isDefault { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveTheme, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let actionSheet = ActionSheetController(presentationData: presentationData) var items: [ActionSheetItem] = [] items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeConfirmation, color: .destructive, action: { [weak actionSheet] in @@ -682,7 +682,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme } else { items.append(.action(ContextMenuActionItem(text: strings.Theme_Context_ChangeColors, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: reference, create: true)) pushControllerImpl?(controller) }) @@ -815,7 +815,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme }) }) - c.dismiss(completion: { + c?.dismiss(completion: { pushControllerImpl?(controller) }) }))) @@ -868,14 +868,14 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme }) })) - c.dismiss(completion: { + c?.dismiss(completion: { pushControllerImpl?(controller) }) }) }))) } items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_ShareTheme, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let shareController = ShareController(context: context, subject: .url("https://t.me/addtheme/\(cloudTheme.theme.slug)"), preferredAction: .default) shareController.actionCompleted = { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -886,7 +886,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme }))) if cloudThemeExists && !cloudTheme.theme.isDefault { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveTheme, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let actionSheet = ActionSheetController(presentationData: presentationData) var items: [ActionSheetItem] = [] items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeConfirmation, color: .destructive, action: { [weak actionSheet] in diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 3928a01f9dc..5db9c88b6e8 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -653,7 +653,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The }) }) - c.dismiss(completion: { + c?.dismiss(completion: { pushControllerImpl?(controller) }) }))) @@ -701,14 +701,14 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The }) })) - c.dismiss(completion: { + c?.dismiss(completion: { pushControllerImpl?(controller) }) }) }))) } items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_ShareTheme, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let shareController = ShareController(context: context, subject: .url("https://t.me/addtheme/\(theme.theme.slug)"), preferredAction: .default) shareController.actionCompleted = { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -718,7 +718,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The }) }))) items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveTheme, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let actionSheet = ActionSheetController(presentationData: presentationData) var items: [ActionSheetItem] = [] items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeConfirmation, color: .destructive, action: { [weak actionSheet] in @@ -766,7 +766,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The } else { items.append(.action(ContextMenuActionItem(text: strings.Theme_Context_ChangeColors, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let controller = ThemeAccentColorController(context: context, mode: .colors(themeReference: reference, create: true)) pushControllerImpl?(controller) }) @@ -899,7 +899,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The }) }) - c.dismiss(completion: { + c?.dismiss(completion: { pushControllerImpl?(controller) }) }))) @@ -952,14 +952,14 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The }) })) - c.dismiss(completion: { + c?.dismiss(completion: { pushControllerImpl?(controller) }) }) }))) } items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_ShareTheme, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let shareController = ShareController(context: context, subject: .url("https://t.me/addtheme/\(cloudTheme.theme.slug)"), preferredAction: .default) shareController.actionCompleted = { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -970,7 +970,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The }))) if cloudThemeExists { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Appearance_RemoveTheme, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { let actionSheet = ActionSheetController(presentationData: presentationData) var items: [ActionSheetItem] = [] items.append(ActionSheetButtonItem(title: presentationData.strings.Appearance_RemoveThemeConfirmation, color: .destructive, action: { [weak actionSheet] in diff --git a/submodules/ShareController/BUILD b/submodules/ShareController/BUILD index 1bc2c4b24b9..d69f585bc10 100644 --- a/submodules/ShareController/BUILD +++ b/submodules/ShareController/BUILD @@ -11,7 +11,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 35617aecea9..799f962ad19 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -307,18 +307,6 @@ private func collectExternalShareItems(strings: PresentationStrings, dateTimeFor }) } -public protocol ShareControllerEnvironment: AnyObject { - var presentationData: PresentationData { get } - var updatedPresentationData: Signal { get } - var isMainApp: Bool { get } - var energyUsageSettings: EnergyUsageSettings { get } - - var mediaManager: MediaManager? { get } - - func setAccountUserInterfaceInUse(id: AccountRecordId) -> Disposable - func donateSendMessageIntent(account: ShareControllerAccountContext, peerIds: [EnginePeer.Id]) -} - public final class ShareControllerAppEnvironment: ShareControllerEnvironment { // MARK: Nicegram DB, public public let sharedContext: SharedAccountContext @@ -357,19 +345,6 @@ public final class ShareControllerAppEnvironment: ShareControllerEnvironment { } } -public protocol ShareControllerAccountContext: AnyObject { - var accountId: AccountRecordId { get } - var accountPeerId: EnginePeer.Id { get } - var stateManager: AccountStateManager { get } - var engineData: TelegramEngine.EngineData { get } - var animationCache: AnimationCache { get } - var animationRenderer: MultiAnimationRenderer { get } - var contentSettings: ContentSettings { get } - var appConfiguration: AppConfiguration { get } - - func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> -} - public final class ShareControllerAppAccountContext: ShareControllerAccountContext { public let context: AccountContext diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 693baadba33..abec599e04d 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -349,7 +349,6 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate var disabledPeerSelected: ((EnginePeer) -> Void)? let ready = Promise() - private var didSetReady = false private var controllerInteraction: ShareControllerInteraction? diff --git a/submodules/ShareItems/BUILD b/submodules/ShareItems/BUILD index 313b5ce3ccd..734ef872a20 100644 --- a/submodules/ShareItems/BUILD +++ b/submodules/ShareItems/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/ShimmerEffect/BUILD b/submodules/ShimmerEffect/BUILD index 5c632871c5f..cba6a72cfcb 100644 --- a/submodules/ShimmerEffect/BUILD +++ b/submodules/ShimmerEffect/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/SinglePhoneInputNode/BUILD b/submodules/SinglePhoneInputNode/BUILD index f2f4b9b70f7..e8918418728 100644 --- a/submodules/SinglePhoneInputNode/BUILD +++ b/submodules/SinglePhoneInputNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/SlotMachineAnimationNode/BUILD b/submodules/SlotMachineAnimationNode/BUILD index 6c660b314e2..32effc49d68 100644 --- a/submodules/SlotMachineAnimationNode/BUILD +++ b/submodules/SlotMachineAnimationNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/SoftwareVideo/BUILD b/submodules/SoftwareVideo/BUILD index 0d0da4f9db7..c64426234ca 100644 --- a/submodules/SoftwareVideo/BUILD +++ b/submodules/SoftwareVideo/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/SolidRoundedButtonNode/BUILD b/submodules/SolidRoundedButtonNode/BUILD index 4166c5fe106..af9f24c8440 100644 --- a/submodules/SolidRoundedButtonNode/BUILD +++ b/submodules/SolidRoundedButtonNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/SparseItemGrid/BUILD b/submodules/SparseItemGrid/BUILD index 302566dbbb0..c8bd5deffac 100644 --- a/submodules/SparseItemGrid/BUILD +++ b/submodules/SparseItemGrid/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/Speak/BUILD b/submodules/Speak/BUILD index 0906356fd78..2bf9729a6b3 100644 --- a/submodules/Speak/BUILD +++ b/submodules/Speak/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/StatisticsUI/BUILD b/submodules/StatisticsUI/BUILD index 0d09b772344..acaa5c567d2 100644 --- a/submodules/StatisticsUI/BUILD +++ b/submodules/StatisticsUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/StatisticsUI/Sources/BoostHeaderItem.swift b/submodules/StatisticsUI/Sources/BoostHeaderItem.swift index afa0504a356..5b70e642fc4 100644 --- a/submodules/StatisticsUI/Sources/BoostHeaderItem.swift +++ b/submodules/StatisticsUI/Sources/BoostHeaderItem.swift @@ -308,7 +308,9 @@ private final class BoostHeaderComponent: CombinedComponent { UIColor(rgb: 0x6b93ff), UIColor(rgb: 0x8878ff), UIColor(rgb: 0xe46ace) - ] + ], + cornerRadius: 0.0, + topOverscroll: true ), availableSize: size, transition: context.transition diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index af89fcc08b0..222303780aa 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -2024,7 +2024,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD var items: [ContextMenuItem] = [] items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ViewInChannel, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { peer in guard let peer = peer else { diff --git a/submodules/StatisticsUI/Sources/MessageStatsController.swift b/submodules/StatisticsUI/Sources/MessageStatsController.swift index 11463ad99be..aacc3daa492 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsController.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsController.swift @@ -539,7 +539,7 @@ public func messageStatsController(context: AccountContext, updatedPresentationD } items.append(.action(ContextMenuActionItem(text: title, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: iconName), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { peer in guard let peer = peer, let navigationController = controller?.navigationController as? NavigationController else { diff --git a/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift b/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift index 75831f81858..6479c9b7f2f 100644 --- a/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift +++ b/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift @@ -147,7 +147,7 @@ private final class SheetContent: CombinedComponent { showPeer = true case let .withdrawal(status, amount, date, provider, _, transactionUrl): labelColor = theme.list.itemDestructiveColor - amountString = amountAttributedString(formatBalanceText(amount, decimalSeparator: dateTimeFormat.decimalSeparator), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor).mutableCopy() as! NSMutableAttributedString + amountString = amountAttributedString(formatBalanceText(amount, decimalSeparator: dateTimeFormat.groupingSeparator), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor).mutableCopy() as! NSMutableAttributedString dateString = stringForFullDate(timestamp: date, strings: strings, dateTimeFormat: dateTimeFormat) switch status { diff --git a/submodules/StickerPackPreviewUI/BUILD b/submodules/StickerPackPreviewUI/BUILD index 276bd519f3b..dbdcc16e4fe 100644 --- a/submodules/StickerPackPreviewUI/BUILD +++ b/submodules/StickerPackPreviewUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift index 3d629bea087..f47734863d4 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift @@ -122,8 +122,8 @@ final class StickerPackEmojisItemNode: GridItemNode { private var boundsChangeTrackerLayer = SimpleLayer() - private var visibleItemLayers: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemLayer] = [:] - private var visibleItemPlaceholderViews: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:] + private var visibleItemLayers: [EmojiKeyboardItemLayer.Key: EmojiKeyboardItemLayer] = [:] + private var visibleItemPlaceholderViews: [EmojiKeyboardItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:] private let containerNode: ASDisplayNode private let titleNode: ImmediateTextNode @@ -195,7 +195,7 @@ final class StickerPackEmojisItemNode: GridItemNode { func targetItem(at point: CGPoint) -> (TelegramMediaFile, CALayer)? { if let (item, _) = self.item(atPoint: point), let file = item.itemFile { - let itemId = EmojiPagerContentComponent.View.ItemLayer.Key( + let itemId = EmojiKeyboardItemLayer.Key( groupId: 0, itemId: .animation(.file(file.fileId)) ) @@ -237,7 +237,7 @@ final class StickerPackEmojisItemNode: GridItemNode { private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (EmojiPagerContentComponent.Item, CGRect)? { let localPoint = point - var closestItem: (key: EmojiPagerContentComponent.View.ItemLayer.Key, distance: CGFloat)? + var closestItem: (key: EmojiKeyboardItemLayer.Key, distance: CGFloat)? for (key, itemLayer) in self.visibleItemLayers { if extendedHitRange { @@ -308,7 +308,7 @@ final class StickerPackEmojisItemNode: GridItemNode { let animationRenderer = item.animationRenderer let theme = item.theme let items = item.items - var validIds = Set() + var validIds = Set() let itemLayout: ItemLayout if let current = self.itemLayout, current.width == self.size.width && current.itemsCount == items.count { @@ -322,7 +322,7 @@ final class StickerPackEmojisItemNode: GridItemNode { for index in 0 ..< items.count { let item = items[index] - let itemId = EmojiPagerContentComponent.View.ItemLayer.Key( + let itemId = EmojiKeyboardItemLayer.Key( groupId: 0, itemId: .animation(.file(item.file.fileId)) ) @@ -334,7 +334,7 @@ final class StickerPackEmojisItemNode: GridItemNode { var updateItemLayerPlaceholder = false var itemTransition = transition - let itemLayer: EmojiPagerContentComponent.View.ItemLayer + let itemLayer: EmojiKeyboardItemLayer if let current = self.visibleItemLayers[itemId] { itemLayer = current } else { @@ -342,7 +342,7 @@ final class StickerPackEmojisItemNode: GridItemNode { itemTransition = .immediate let animationData = EntityKeyboardAnimationData(file: item.file) - itemLayer = EmojiPagerContentComponent.View.ItemLayer( + itemLayer = EmojiKeyboardItemLayer( item: EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 0fda88450f0..f227846ba49 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -570,7 +570,7 @@ private final class StickerPackContainer: ASDisplayNode { .action(ContextMenuActionItem(text: self.presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c ,f in - c.popItems() + c?.popItems() })), .separator, .action(ContextMenuActionItem(text: self.presentationData.strings.Stickers_Delete_ForEveryone, textColor: .destructive, icon: { _ in return nil }, action: { [weak self] _ ,f in @@ -591,7 +591,7 @@ private final class StickerPackContainer: ASDisplayNode { } })) ] - c.pushItems(items: .single(ContextController.Items(content: .list(contextItems)))) + c?.pushItems(items: .single(ContextController.Items(content: .list(contextItems)))) } }))) } @@ -952,15 +952,13 @@ private final class StickerPackContainer: ASDisplayNode { } let content = StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { [weak self] in - guard let self else { - return - } - guard let controller = self.controller else { + guard let strongSelf = self else { return } - - let premiumController = PremiumIntroScreen(context: context, source: .stickers) - controller.push(premiumController) + let controller = PremiumIntroScreen(context: strongSelf.context, source: .stickers) + let navigationController = strongSelf.controller?.parentNavigationController + strongSelf.controller?.dismiss(animated: false, completion: nil) + navigationController?.pushViewController(controller) }) return (strongSelf.view, itemLayer.convert(itemLayer.bounds, to: strongSelf.view.layer), content) @@ -1164,7 +1162,7 @@ private final class StickerPackContainer: ASDisplayNode { self?.togglePackInstalled() })) ] - c.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil, animated: true) + c?.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil, animated: true) } else { f(.default) self.presentDeletePack() @@ -1259,6 +1257,7 @@ private final class StickerPackContainer: ASDisplayNode { let editorController = context.sharedContext.makeStickerEditorScreen( context: context, source: result, + intro: false, transitionArguments: transitionView.flatMap { ($0, transitionRect, transitionImage) }, completion: { file, emoji, commit in dismissImpl?() @@ -1363,6 +1362,7 @@ private final class StickerPackContainer: ASDisplayNode { let controller = context.sharedContext.makeStickerEditorScreen( context: context, source: (initialFile, emoji), + intro: false, transitionArguments: nil, completion: { file, emoji, commit in let sticker = ImportSticker( @@ -1986,7 +1986,7 @@ private final class StickerPackContainer: ASDisplayNode { actionAreaBottomInset = 2.0 } } - if let (info, _, isInstalled) = self.currentStickerPack, isInstalled, (!info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji)) { + if let (info, _, isInstalled) = self.currentStickerPack, isInstalled, (!info.flags.contains(.isCreator) || info.flags.contains(.isEmoji)) { buttonHeight = 42.0 actionAreaTopInset = 1.0 actionAreaBottomInset = 2.0 diff --git a/submodules/StickerPeekUI/BUILD b/submodules/StickerPeekUI/BUILD index 33f9618a5bc..ba36478b34b 100644 --- a/submodules/StickerPeekUI/BUILD +++ b/submodules/StickerPeekUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/StickerResources/BUILD b/submodules/StickerResources/BUILD index a24e729b2ed..453eed98e4d 100644 --- a/submodules/StickerResources/BUILD +++ b/submodules/StickerResources/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/StringPluralization/BUILD b/submodules/StringPluralization/BUILD index 76103e678b7..376cd6111b9 100644 --- a/submodules/StringPluralization/BUILD +++ b/submodules/StringPluralization/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/NumberPluralizationForm:NumberPluralizationForm", diff --git a/submodules/SwipeToDismissGesture/BUILD b/submodules/SwipeToDismissGesture/BUILD index c60d8cf192c..d8706d12226 100644 --- a/submodules/SwipeToDismissGesture/BUILD +++ b/submodules/SwipeToDismissGesture/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/SwitchNode/BUILD b/submodules/SwitchNode/BUILD index 26a8788fa18..8dd624bbff6 100644 --- a/submodules/SwitchNode/BUILD +++ b/submodules/SwitchNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TabBarUI/BUILD b/submodules/TabBarUI/BUILD index 1abbce21935..0028d9fc1bf 100644 --- a/submodules/TabBarUI/BUILD +++ b/submodules/TabBarUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TabBarUI/Sources/TabBarController.swift b/submodules/TabBarUI/Sources/TabBarController.swift index 300da2f465a..fa2562dca0f 100644 --- a/submodules/TabBarUI/Sources/TabBarController.swift +++ b/submodules/TabBarUI/Sources/TabBarController.swift @@ -141,6 +141,10 @@ open class TabBarControllerImpl: ViewController, TabBarController { public var cameraItemAndAction: (item: UITabBarItem, action: () -> Void)? + // MARK: Nicegram + public var willSelect: ((Int) -> Void)? + // + // MARK: Nicegram (showTabNames) public init(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme, showTabNames: Bool) { self.navigationBarPresentationData = navigationBarPresentationData @@ -303,6 +307,9 @@ open class TabBarControllerImpl: ViewController, TabBarController { } } } else { + // MARK: Nicegram + strongSelf.willSelect?(index) + // strongSelf.selectedIndex = index } } diff --git a/submodules/TelegramAnimatedStickerNode/BUILD b/submodules/TelegramAnimatedStickerNode/BUILD index 8383f9921b6..d3a2f7ba2f6 100644 --- a/submodules/TelegramAnimatedStickerNode/BUILD +++ b/submodules/TelegramAnimatedStickerNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox:Postbox", diff --git a/submodules/TelegramApi/BUILD b/submodules/TelegramApi/BUILD index af514b050bb..ea63b71f0bd 100644 --- a/submodules/TelegramApi/BUILD +++ b/submodules/TelegramApi/BUILD @@ -4,7 +4,7 @@ swift_library( name = "TelegramApi", module_name = "TelegramApi", copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], srcs = glob([ "Sources/**/*.swift", diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index eb40aad7f31..c81a2d32b9c 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -70,6 +70,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1163561432] = { return Api.AutoDownloadSettings.parse_autoDownloadSettings($0) } dict[-2124403385] = { return Api.AutoSaveException.parse_autoSaveException($0) } dict[-934791986] = { return Api.AutoSaveSettings.parse_autoSaveSettings($0) } + dict[-1815879042] = { return Api.AvailableEffect.parse_availableEffect($0) } dict[-1065882623] = { return Api.AvailableReaction.parse_availableReaction($0) } dict[-177732982] = { return Api.BankCardOpenUrl.parse_bankCardOpenUrl($0) } dict[1527845466] = { return Api.BaseTheme.parse_baseThemeArctic($0) } @@ -279,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) } @@ -370,6 +372,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-977967015] = { return Api.InputInvoice.parse_inputInvoiceMessage($0) } dict[-1734841331] = { return Api.InputInvoice.parse_inputInvoicePremiumGiftCode($0) } dict[-1020867857] = { return Api.InputInvoice.parse_inputInvoiceSlug($0) } + dict[497236696] = { return Api.InputInvoice.parse_inputInvoiceStars($0) } dict[-122978821] = { return Api.InputMedia.parse_inputMediaContact($0) } dict[-428884101] = { return Api.InputMedia.parse_inputMediaDice($0) } dict[860303448] = { return Api.InputMedia.parse_inputMediaDocument($0) } @@ -378,7 +381,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-750828557] = { return Api.InputMedia.parse_inputMediaGame($0) } dict[-1759532989] = { return Api.InputMedia.parse_inputMediaGeoLive($0) } dict[-104578748] = { return Api.InputMedia.parse_inputMediaGeoPoint($0) } - dict[-1900697899] = { return Api.InputMedia.parse_inputMediaInvoice($0) } + dict[1080028941] = { return Api.InputMedia.parse_inputMediaInvoice($0) } dict[-1279654347] = { return Api.InputMedia.parse_inputMediaPhoto($0) } dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) } dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) } @@ -458,6 +461,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1551868097] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiftCode($0) } dict[369444042] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) } dict[-1502273946] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumSubscription($0) } + dict[1326377183] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentStars($0) } dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) } dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) } dict[-1881255857] = { return Api.InputThemeSettings.parse_inputThemeSettings($0) } @@ -512,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[592953125] = { 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) } @@ -560,7 +564,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1205698681] = { return Api.MessageAction.parse_messageActionWebViewDataSentMe($0) } dict[546203849] = { return Api.MessageEntity.parse_inputMessageEntityMentionName($0) } dict[1981704948] = { return Api.MessageEntity.parse_messageEntityBankCard($0) } - dict[34469328] = { return Api.MessageEntity.parse_messageEntityBlockquote($0) } + dict[-238245204] = { return Api.MessageEntity.parse_messageEntityBlockquote($0) } dict[-1117713463] = { return Api.MessageEntity.parse_messageEntityBold($0) } dict[1827637959] = { return Api.MessageEntity.parse_messageEntityBotCommand($0) } dict[1280209983] = { return Api.MessageEntity.parse_messageEntityCashtag($0) } @@ -865,6 +869,14 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-425595208] = { return Api.SmsJob.parse_smsJob($0) } dict[-1108478618] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } + dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($0) } + dict[-865044046] = { return Api.StarsTransaction.parse_starsTransaction($0) } + dict[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) } + dict[-1269320843] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAppStore($0) } + dict[-382740222] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerFragment($0) } + dict[2069236235] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerPlayMarket($0) } + dict[621656824] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerPremiumBot($0) } + dict[-1779253276] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerUnsupported($0) } dict[-884757282] = { return Api.StatsAbsValueAndPrev.parse_statsAbsValueAndPrev($0) } dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($0) } dict[-1901828938] = { return Api.StatsGraph.parse_statsGraph($0) } @@ -928,7 +940,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-997782967] = { return Api.Update.parse_updateBotStopped($0) } dict[-2095595325] = { return Api.Update.parse_updateBotWebhookJSON($0) } dict[-1684914010] = { return Api.Update.parse_updateBotWebhookJSONQuery($0) } - dict[1550177112] = { return Api.Update.parse_updateBroadcastRevenueTransactions($0) } + dict[-539401739] = { return Api.Update.parse_updateBroadcastRevenueTransactions($0) } dict[1666927625] = { return Api.Update.parse_updateChannel($0) } dict[-1304443240] = { return Api.Update.parse_updateChannelAvailableMessages($0) } dict[-761649164] = { return Api.Update.parse_updateChannelMessageForwards($0) } @@ -1029,6 +1041,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[2103604867] = { return Api.Update.parse_updateSentStoryReaction($0) } dict[-337352679] = { return Api.Update.parse_updateServiceNotification($0) } dict[-245208620] = { return Api.Update.parse_updateSmsJob($0) } + dict[263737752] = { return Api.Update.parse_updateStarsBalance($0) } dict[834816008] = { return Api.Update.parse_updateStickerSets($0) } dict[196268545] = { return Api.Update.parse_updateStickerSetsOrder($0) } dict[738741697] = { return Api.Update.parse_updateStoriesStealthMode($0) } @@ -1133,7 +1146,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) } dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) } dict[-196020837] = { return Api.auth.SentCodeType.parse_sentCodeTypeEmailCode($0) } - dict[-444918734] = { return Api.auth.SentCodeType.parse_sentCodeTypeFirebaseSms($0) } + dict[331943703] = { return Api.auth.SentCodeType.parse_sentCodeTypeFirebaseSms($0) } dict[-1425815847] = { return Api.auth.SentCodeType.parse_sentCodeTypeFlashCall($0) } dict[-648651719] = { return Api.auth.SentCodeType.parse_sentCodeTypeFragmentSms($0) } dict[-2113903484] = { return Api.auth.SentCodeType.parse_sentCodeTypeMissedCall($0) } @@ -1204,6 +1217,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-843329861] = { return Api.messages.AllStickers.parse_allStickers($0) } dict[-395967805] = { return Api.messages.AllStickers.parse_allStickersNotModified($0) } dict[1338747336] = { return Api.messages.ArchivedStickers.parse_archivedStickers($0) } + dict[-1109696146] = { return Api.messages.AvailableEffects.parse_availableEffects($0) } + dict[-772957605] = { return Api.messages.AvailableEffects.parse_availableEffectsNotModified($0) } dict[1989032621] = { return Api.messages.AvailableReactions.parse_availableReactions($0) } dict[-1626924713] = { return Api.messages.AvailableReactions.parse_availableReactionsNotModified($0) } dict[-347034123] = { return Api.messages.BotApp.parse_botApp($0) } @@ -1285,10 +1300,13 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1130879648] = { return Api.payments.GiveawayInfo.parse_giveawayInfo($0) } dict[13456752] = { return Api.payments.GiveawayInfo.parse_giveawayInfoResults($0) } dict[-1610250415] = { return Api.payments.PaymentForm.parse_paymentForm($0) } + dict[2079764828] = { return Api.payments.PaymentForm.parse_paymentFormStars($0) } dict[1891958275] = { return Api.payments.PaymentReceipt.parse_paymentReceipt($0) } + dict[-625215430] = { return Api.payments.PaymentReceipt.parse_paymentReceiptStars($0) } dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) } dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) } dict[-74456004] = { return Api.payments.SavedInfo.parse_savedInfo($0) } + dict[-1930105248] = { return Api.payments.StarsStatus.parse_starsStatus($0) } dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) } dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) } dict[-1636664659] = { return Api.phone.GroupCall.parse_groupCall($0) } @@ -1428,6 +1446,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.AutoSaveSettings: _1.serialize(buffer, boxed) + case let _1 as Api.AvailableEffect: + _1.serialize(buffer, boxed) case let _1 as Api.AvailableReaction: _1.serialize(buffer, boxed) case let _1 as Api.BankCardOpenUrl: @@ -1588,6 +1608,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: @@ -1948,6 +1970,12 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.SponsoredMessageReportOption: _1.serialize(buffer, boxed) + case let _1 as Api.StarsTopupOption: + _1.serialize(buffer, boxed) + case let _1 as Api.StarsTransaction: + _1.serialize(buffer, boxed) + case let _1 as Api.StarsTransactionPeer: + _1.serialize(buffer, boxed) case let _1 as Api.StatsAbsValueAndPrev: _1.serialize(buffer, boxed) case let _1 as Api.StatsDateRangeDays: @@ -2180,6 +2208,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.ArchivedStickers: _1.serialize(buffer, boxed) + case let _1 as Api.messages.AvailableEffects: + _1.serialize(buffer, boxed) case let _1 as Api.messages.AvailableReactions: _1.serialize(buffer, boxed) case let _1 as Api.messages.BotApp: @@ -2296,6 +2326,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.payments.SavedInfo: _1.serialize(buffer, boxed) + case let _1 as Api.payments.StarsStatus: + _1.serialize(buffer, boxed) case let _1 as Api.payments.ValidatedRequestedInfo: _1.serialize(buffer, boxed) case let _1 as Api.phone.ExportedGroupCallInvite: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 0b3798ac9ff..425a83a5333 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -650,6 +650,62 @@ public extension Api { } } +public extension Api { + enum AvailableEffect: TypeConstructorDescription { + case availableEffect(flags: Int32, id: Int64, emoticon: String, staticIconId: Int64?, effectStickerId: Int64, effectAnimationId: Int64?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .availableEffect(let flags, let id, let emoticon, let staticIconId, let effectStickerId, let effectAnimationId): + if boxed { + buffer.appendInt32(-1815879042) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeString(emoticon, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(staticIconId!, buffer: buffer, boxed: false)} + serializeInt64(effectStickerId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt64(effectAnimationId!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .availableEffect(let flags, let id, let emoticon, let staticIconId, let effectStickerId, let effectAnimationId): + return ("availableEffect", [("flags", flags as Any), ("id", id as Any), ("emoticon", emoticon as Any), ("staticIconId", staticIconId as Any), ("effectStickerId", effectStickerId as Any), ("effectAnimationId", effectAnimationId as Any)]) + } + } + + public static func parse_availableEffect(_ reader: BufferReader) -> AvailableEffect? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt64() } + var _5: Int64? + _5 = reader.readInt64() + var _6: Int64? + if Int(_1!) & Int(1 << 1) != 0 {_6 = reader.readInt64() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.AvailableEffect.availableEffect(flags: _1!, id: _2!, emoticon: _3!, staticIconId: _4, effectStickerId: _5!, effectAnimationId: _6) + } + else { + return nil + } + } + + } +} public extension Api { enum AvailableReaction: TypeConstructorDescription { case availableReaction(flags: Int32, reaction: String, title: String, staticIcon: Api.Document, appearAnimation: Api.Document, selectAnimation: Api.Document, activateAnimation: Api.Document, effectAnimation: Api.Document, aroundAnimation: Api.Document?, centerIcon: Api.Document?) @@ -1140,43 +1196,3 @@ public extension Api { } } -public extension Api { - enum BotCommand: TypeConstructorDescription { - case botCommand(command: String, description: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botCommand(let command, let description): - if boxed { - buffer.appendInt32(-1032140601) - } - serializeString(command, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .botCommand(let command, let description): - return ("botCommand", [("command", command as Any), ("description", description as Any)]) - } - } - - public static func parse_botCommand(_ reader: BufferReader) -> BotCommand? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.BotCommand.botCommand(command: _1!, description: _2!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index 9f236b57f27..59a0fae1234 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -1,3 +1,591 @@ +public extension Api { + indirect enum InputMedia: TypeConstructorDescription { + case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String) + case inputMediaDice(emoticon: String) + case inputMediaDocument(flags: Int32, id: Api.InputDocument, ttlSeconds: Int32?, query: String?) + case inputMediaDocumentExternal(flags: Int32, url: String, ttlSeconds: Int32?) + case inputMediaEmpty + case inputMediaGame(id: Api.InputGame) + case inputMediaGeoLive(flags: Int32, geoPoint: Api.InputGeoPoint, heading: Int32?, period: Int32?, proximityNotificationRadius: Int32?) + case inputMediaGeoPoint(geoPoint: Api.InputGeoPoint) + case inputMediaInvoice(flags: Int32, title: String, description: String, photo: Api.InputWebDocument?, invoice: Api.Invoice, payload: Buffer, provider: String?, providerData: Api.DataJSON, startParam: String?, extendedMedia: Api.InputMedia?) + case inputMediaPhoto(flags: Int32, id: Api.InputPhoto, ttlSeconds: Int32?) + case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?) + case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?) + case inputMediaStory(peer: Api.InputPeer, id: Int32) + case inputMediaUploadedDocument(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, mimeType: String, attributes: [Api.DocumentAttribute], stickers: [Api.InputDocument]?, ttlSeconds: Int32?) + case inputMediaUploadedPhoto(flags: Int32, file: Api.InputFile, stickers: [Api.InputDocument]?, ttlSeconds: Int32?) + case inputMediaVenue(geoPoint: Api.InputGeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) + case inputMediaWebPage(flags: Int32, url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputMediaContact(let phoneNumber, let firstName, let lastName, let vcard): + if boxed { + buffer.appendInt32(-122978821) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeString(vcard, buffer: buffer, boxed: false) + break + case .inputMediaDice(let emoticon): + if boxed { + buffer.appendInt32(-428884101) + } + serializeString(emoticon, buffer: buffer, boxed: false) + break + case .inputMediaDocument(let flags, let id, let ttlSeconds, let query): + if boxed { + buffer.appendInt32(860303448) + } + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(query!, buffer: buffer, boxed: false)} + break + case .inputMediaDocumentExternal(let flags, let url, let ttlSeconds): + if boxed { + buffer.appendInt32(-78455655) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} + break + case .inputMediaEmpty: + if boxed { + buffer.appendInt32(-1771768449) + } + + break + case .inputMediaGame(let id): + if boxed { + buffer.appendInt32(-750828557) + } + id.serialize(buffer, true) + break + case .inputMediaGeoLive(let flags, let geoPoint, let heading, let period, let proximityNotificationRadius): + if boxed { + buffer.appendInt32(-1759532989) + } + serializeInt32(flags, buffer: buffer, boxed: false) + geoPoint.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(heading!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(period!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(proximityNotificationRadius!, buffer: buffer, boxed: false)} + break + case .inputMediaGeoPoint(let geoPoint): + if boxed { + buffer.appendInt32(-104578748) + } + geoPoint.serialize(buffer, true) + break + case .inputMediaInvoice(let flags, let title, let description, let photo, let invoice, let payload, let provider, let providerData, let startParam, let extendedMedia): + if boxed { + buffer.appendInt32(1080028941) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {photo!.serialize(buffer, true)} + invoice.serialize(buffer, true) + serializeBytes(payload, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {serializeString(provider!, buffer: buffer, boxed: false)} + providerData.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {extendedMedia!.serialize(buffer, true)} + break + case .inputMediaPhoto(let flags, let id, let ttlSeconds): + if boxed { + buffer.appendInt32(-1279654347) + } + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} + break + case .inputMediaPhotoExternal(let flags, let url, let ttlSeconds): + if boxed { + buffer.appendInt32(-440664550) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} + break + case .inputMediaPoll(let flags, let poll, let correctAnswers, let solution, let solutionEntities): + if boxed { + buffer.appendInt32(261416433) + } + serializeInt32(flags, buffer: buffer, boxed: false) + poll.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(correctAnswers!.count)) + for item in correctAnswers! { + serializeBytes(item, buffer: buffer, boxed: false) + }} + if Int(flags) & Int(1 << 1) != 0 {serializeString(solution!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(solutionEntities!.count)) + for item in solutionEntities! { + item.serialize(buffer, true) + }} + break + case .inputMediaStory(let peer, let id): + if boxed { + buffer.appendInt32(-1979852936) + } + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + break + case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let ttlSeconds): + if boxed { + buffer.appendInt32(1530447553) + } + serializeInt32(flags, buffer: buffer, boxed: false) + file.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 {thumb!.serialize(buffer, true)} + serializeString(mimeType, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers!.count)) + for item in stickers! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} + break + case .inputMediaUploadedPhoto(let flags, let file, let stickers, let ttlSeconds): + if boxed { + buffer.appendInt32(505969924) + } + serializeInt32(flags, buffer: buffer, boxed: false) + file.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers!.count)) + for item in stickers! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} + break + case .inputMediaVenue(let geoPoint, let title, let address, let provider, let venueId, let venueType): + if boxed { + buffer.appendInt32(-1052959727) + } + geoPoint.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + serializeString(address, buffer: buffer, boxed: false) + serializeString(provider, buffer: buffer, boxed: false) + serializeString(venueId, buffer: buffer, boxed: false) + serializeString(venueType, buffer: buffer, boxed: false) + break + case .inputMediaWebPage(let flags, let url): + if boxed { + buffer.appendInt32(-1038383031) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputMediaContact(let phoneNumber, let firstName, let lastName, let vcard): + return ("inputMediaContact", [("phoneNumber", phoneNumber as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("vcard", vcard as Any)]) + case .inputMediaDice(let emoticon): + return ("inputMediaDice", [("emoticon", emoticon as Any)]) + case .inputMediaDocument(let flags, let id, let ttlSeconds, let query): + return ("inputMediaDocument", [("flags", flags as Any), ("id", id as Any), ("ttlSeconds", ttlSeconds as Any), ("query", query as Any)]) + case .inputMediaDocumentExternal(let flags, let url, let ttlSeconds): + return ("inputMediaDocumentExternal", [("flags", flags as Any), ("url", url as Any), ("ttlSeconds", ttlSeconds as Any)]) + case .inputMediaEmpty: + return ("inputMediaEmpty", []) + case .inputMediaGame(let id): + return ("inputMediaGame", [("id", id as Any)]) + case .inputMediaGeoLive(let flags, let geoPoint, let heading, let period, let proximityNotificationRadius): + return ("inputMediaGeoLive", [("flags", flags as Any), ("geoPoint", geoPoint as Any), ("heading", heading as Any), ("period", period as Any), ("proximityNotificationRadius", proximityNotificationRadius as Any)]) + case .inputMediaGeoPoint(let geoPoint): + return ("inputMediaGeoPoint", [("geoPoint", geoPoint as Any)]) + case .inputMediaInvoice(let flags, let title, let description, let photo, let invoice, let payload, let provider, let providerData, let startParam, let extendedMedia): + return ("inputMediaInvoice", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("payload", payload as Any), ("provider", provider as Any), ("providerData", providerData as Any), ("startParam", startParam as Any), ("extendedMedia", extendedMedia as Any)]) + case .inputMediaPhoto(let flags, let id, let ttlSeconds): + return ("inputMediaPhoto", [("flags", flags as Any), ("id", id as Any), ("ttlSeconds", ttlSeconds as Any)]) + case .inputMediaPhotoExternal(let flags, let url, let ttlSeconds): + return ("inputMediaPhotoExternal", [("flags", flags as Any), ("url", url as Any), ("ttlSeconds", ttlSeconds as Any)]) + case .inputMediaPoll(let flags, let poll, let correctAnswers, let solution, let solutionEntities): + return ("inputMediaPoll", [("flags", flags as Any), ("poll", poll as Any), ("correctAnswers", correctAnswers as Any), ("solution", solution as Any), ("solutionEntities", solutionEntities as Any)]) + case .inputMediaStory(let peer, let id): + return ("inputMediaStory", [("peer", peer as Any), ("id", id as Any)]) + case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let ttlSeconds): + return ("inputMediaUploadedDocument", [("flags", flags as Any), ("file", file as Any), ("thumb", thumb as Any), ("mimeType", mimeType as Any), ("attributes", attributes as Any), ("stickers", stickers as Any), ("ttlSeconds", ttlSeconds as Any)]) + case .inputMediaUploadedPhoto(let flags, let file, let stickers, let ttlSeconds): + return ("inputMediaUploadedPhoto", [("flags", flags as Any), ("file", file as Any), ("stickers", stickers as Any), ("ttlSeconds", ttlSeconds as Any)]) + case .inputMediaVenue(let geoPoint, let title, let address, let provider, let venueId, let venueType): + return ("inputMediaVenue", [("geoPoint", geoPoint as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)]) + case .inputMediaWebPage(let flags, let url): + return ("inputMediaWebPage", [("flags", flags as Any), ("url", url as Any)]) + } + } + + public static func parse_inputMediaContact(_ reader: BufferReader) -> InputMedia? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputMedia.inputMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, vcard: _4!) + } + else { + return nil + } + } + public static func parse_inputMediaDice(_ reader: BufferReader) -> InputMedia? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputMedia.inputMediaDice(emoticon: _1!) + } + else { + return nil + } + } + public static func parse_inputMediaDocument(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputDocument? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputDocument + } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: String? + if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputMedia.inputMediaDocument(flags: _1!, id: _2!, ttlSeconds: _3, query: _4) + } + else { + return nil + } + } + public static func parse_inputMediaDocumentExternal(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputMedia.inputMediaDocumentExternal(flags: _1!, url: _2!, ttlSeconds: _3) + } + else { + return nil + } + } + public static func parse_inputMediaEmpty(_ reader: BufferReader) -> InputMedia? { + return Api.InputMedia.inputMediaEmpty + } + public static func parse_inputMediaGame(_ reader: BufferReader) -> InputMedia? { + var _1: Api.InputGame? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGame + } + let _c1 = _1 != nil + if _c1 { + return Api.InputMedia.inputMediaGame(id: _1!) + } + else { + return nil + } + } + public static func parse_inputMediaGeoLive(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputGeoPoint? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint + } + var _3: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } + var _4: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + var _5: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.InputMedia.inputMediaGeoLive(flags: _1!, geoPoint: _2!, heading: _3, period: _4, proximityNotificationRadius: _5) + } + else { + return nil + } + } + public static func parse_inputMediaGeoPoint(_ reader: BufferReader) -> InputMedia? { + var _1: Api.InputGeoPoint? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint + } + let _c1 = _1 != nil + if _c1 { + return Api.InputMedia.inputMediaGeoPoint(geoPoint: _1!) + } + else { + return nil + } + } + public static func parse_inputMediaInvoice(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Api.InputWebDocument? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InputWebDocument + } } + var _5: Api.Invoice? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _6: Buffer? + _6 = parseBytes(reader) + var _7: String? + if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) } + var _8: Api.DataJSON? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + var _9: String? + if Int(_1!) & Int(1 << 1) != 0 {_9 = parseString(reader) } + var _10: Api.InputMedia? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.InputMedia + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.InputMedia.inputMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, invoice: _5!, payload: _6!, provider: _7, providerData: _8!, startParam: _9, extendedMedia: _10) + } + else { + return nil + } + } + public static func parse_inputMediaPhoto(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputPhoto? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputPhoto + } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputMedia.inputMediaPhoto(flags: _1!, id: _2!, ttlSeconds: _3) + } + else { + return nil + } + } + public static func parse_inputMediaPhotoExternal(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputMedia.inputMediaPhotoExternal(flags: _1!, url: _2!, ttlSeconds: _3) + } + else { + return nil + } + } + public static func parse_inputMediaPoll(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Poll? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Poll + } + var _3: [Buffer]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + } } + var _4: String? + if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } + var _5: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.InputMedia.inputMediaPoll(flags: _1!, poll: _2!, correctAnswers: _3, solution: _4, solutionEntities: _5) + } + else { + return nil + } + } + public static func parse_inputMediaStory(_ reader: BufferReader) -> InputMedia? { + var _1: Api.InputPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputMedia.inputMediaStory(peer: _1!, id: _2!) + } + else { + return nil + } + } + public static func parse_inputMediaUploadedDocument(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputFile? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputFile + } + var _3: Api.InputFile? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.InputFile + } } + var _4: String? + _4 = parseString(reader) + var _5: [Api.DocumentAttribute]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) + } + var _6: [Api.InputDocument]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) + } } + var _7: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_7 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.InputMedia.inputMediaUploadedDocument(flags: _1!, file: _2!, thumb: _3, mimeType: _4!, attributes: _5!, stickers: _6, ttlSeconds: _7) + } + else { + return nil + } + } + public static func parse_inputMediaUploadedPhoto(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputFile? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputFile + } + var _3: [Api.InputDocument]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) + } } + var _4: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputMedia.inputMediaUploadedPhoto(flags: _1!, file: _2!, stickers: _3, ttlSeconds: _4) + } + else { + return nil + } + } + public static func parse_inputMediaVenue(_ reader: BufferReader) -> InputMedia? { + var _1: Api.InputGeoPoint? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint + } + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.InputMedia.inputMediaVenue(geoPoint: _1!, title: _2!, address: _3!, provider: _4!, venueId: _5!, venueType: _6!) + } + else { + return nil + } + } + public static func parse_inputMediaWebPage(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputMedia.inputMediaWebPage(flags: _1!, url: _2!) + } + else { + return nil + } + } + + } +} public extension Api { enum InputMessage: TypeConstructorDescription { case inputMessageCallbackQuery(id: Int32, queryId: Int64) @@ -468,323 +1056,3 @@ public extension Api { } } -public extension Api { - enum InputPeerNotifySettings: TypeConstructorDescription { - case inputPeerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, sound: Api.NotificationSound?, storiesMuted: Api.Bool?, storiesHideSender: Api.Bool?, storiesSound: Api.NotificationSound?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputPeerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let sound, let storiesMuted, let storiesHideSender, let storiesSound): - if boxed { - buffer.appendInt32(-892638494) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {showPreviews!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {silent!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(muteUntil!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {sound!.serialize(buffer, true)} - if Int(flags) & Int(1 << 6) != 0 {storiesMuted!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {storiesHideSender!.serialize(buffer, true)} - if Int(flags) & Int(1 << 8) != 0 {storiesSound!.serialize(buffer, true)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputPeerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let sound, let storiesMuted, let storiesHideSender, let storiesSound): - return ("inputPeerNotifySettings", [("flags", flags as Any), ("showPreviews", showPreviews as Any), ("silent", silent as Any), ("muteUntil", muteUntil as Any), ("sound", sound as Any), ("storiesMuted", storiesMuted as Any), ("storiesHideSender", storiesHideSender as Any), ("storiesSound", storiesSound as Any)]) - } - } - - public static func parse_inputPeerNotifySettings(_ reader: BufferReader) -> InputPeerNotifySettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _3: Api.Bool? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } - var _5: Api.NotificationSound? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } } - var _6: Api.Bool? - if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _7: Api.Bool? - if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _8: Api.NotificationSound? - if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 6) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 8) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, sound: _5, storiesMuted: _6, storiesHideSender: _7, storiesSound: _8) - } - else { - return nil - } - } - - } -} -public extension Api { - enum InputPhoneCall: TypeConstructorDescription { - case inputPhoneCall(id: Int64, accessHash: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputPhoneCall(let id, let accessHash): - if boxed { - buffer.appendInt32(506920429) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputPhoneCall(let id, let accessHash): - return ("inputPhoneCall", [("id", id as Any), ("accessHash", accessHash as Any)]) - } - } - - public static func parse_inputPhoneCall(_ reader: BufferReader) -> InputPhoneCall? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputPhoneCall.inputPhoneCall(id: _1!, accessHash: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum InputPhoto: TypeConstructorDescription { - case inputPhoto(id: Int64, accessHash: Int64, fileReference: Buffer) - case inputPhotoEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputPhoto(let id, let accessHash, let fileReference): - if boxed { - buffer.appendInt32(1001634122) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeBytes(fileReference, buffer: buffer, boxed: false) - break - case .inputPhotoEmpty: - if boxed { - buffer.appendInt32(483901197) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputPhoto(let id, let accessHash, let fileReference): - return ("inputPhoto", [("id", id as Any), ("accessHash", accessHash as Any), ("fileReference", fileReference as Any)]) - case .inputPhotoEmpty: - return ("inputPhotoEmpty", []) - } - } - - public static func parse_inputPhoto(_ reader: BufferReader) -> InputPhoto? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Buffer? - _3 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputPhoto.inputPhoto(id: _1!, accessHash: _2!, fileReference: _3!) - } - else { - return nil - } - } - public static func parse_inputPhotoEmpty(_ reader: BufferReader) -> InputPhoto? { - return Api.InputPhoto.inputPhotoEmpty - } - - } -} -public extension Api { - enum InputPrivacyKey: TypeConstructorDescription { - case inputPrivacyKeyAbout - case inputPrivacyKeyAddedByPhone - case inputPrivacyKeyBirthday - case inputPrivacyKeyChatInvite - case inputPrivacyKeyForwards - case inputPrivacyKeyPhoneCall - case inputPrivacyKeyPhoneNumber - case inputPrivacyKeyPhoneP2P - case inputPrivacyKeyProfilePhoto - case inputPrivacyKeyStatusTimestamp - case inputPrivacyKeyVoiceMessages - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputPrivacyKeyAbout: - if boxed { - buffer.appendInt32(941870144) - } - - break - case .inputPrivacyKeyAddedByPhone: - if boxed { - buffer.appendInt32(-786326563) - } - - break - case .inputPrivacyKeyBirthday: - if boxed { - buffer.appendInt32(-698740276) - } - - break - case .inputPrivacyKeyChatInvite: - if boxed { - buffer.appendInt32(-1107622874) - } - - break - case .inputPrivacyKeyForwards: - if boxed { - buffer.appendInt32(-1529000952) - } - - break - case .inputPrivacyKeyPhoneCall: - if boxed { - buffer.appendInt32(-88417185) - } - - break - case .inputPrivacyKeyPhoneNumber: - if boxed { - buffer.appendInt32(55761658) - } - - break - case .inputPrivacyKeyPhoneP2P: - if boxed { - buffer.appendInt32(-610373422) - } - - break - case .inputPrivacyKeyProfilePhoto: - if boxed { - buffer.appendInt32(1461304012) - } - - break - case .inputPrivacyKeyStatusTimestamp: - if boxed { - buffer.appendInt32(1335282456) - } - - break - case .inputPrivacyKeyVoiceMessages: - if boxed { - buffer.appendInt32(-1360618136) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputPrivacyKeyAbout: - return ("inputPrivacyKeyAbout", []) - case .inputPrivacyKeyAddedByPhone: - return ("inputPrivacyKeyAddedByPhone", []) - case .inputPrivacyKeyBirthday: - return ("inputPrivacyKeyBirthday", []) - case .inputPrivacyKeyChatInvite: - return ("inputPrivacyKeyChatInvite", []) - case .inputPrivacyKeyForwards: - return ("inputPrivacyKeyForwards", []) - case .inputPrivacyKeyPhoneCall: - return ("inputPrivacyKeyPhoneCall", []) - case .inputPrivacyKeyPhoneNumber: - return ("inputPrivacyKeyPhoneNumber", []) - case .inputPrivacyKeyPhoneP2P: - return ("inputPrivacyKeyPhoneP2P", []) - case .inputPrivacyKeyProfilePhoto: - return ("inputPrivacyKeyProfilePhoto", []) - case .inputPrivacyKeyStatusTimestamp: - return ("inputPrivacyKeyStatusTimestamp", []) - case .inputPrivacyKeyVoiceMessages: - return ("inputPrivacyKeyVoiceMessages", []) - } - } - - public static func parse_inputPrivacyKeyAbout(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyAbout - } - public static func parse_inputPrivacyKeyAddedByPhone(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyAddedByPhone - } - public static func parse_inputPrivacyKeyBirthday(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyBirthday - } - public static func parse_inputPrivacyKeyChatInvite(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyChatInvite - } - public static func parse_inputPrivacyKeyForwards(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyForwards - } - public static func parse_inputPrivacyKeyPhoneCall(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyPhoneCall - } - public static func parse_inputPrivacyKeyPhoneNumber(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyPhoneNumber - } - public static func parse_inputPrivacyKeyPhoneP2P(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyPhoneP2P - } - public static func parse_inputPrivacyKeyProfilePhoto(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyProfilePhoto - } - public static func parse_inputPrivacyKeyStatusTimestamp(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyStatusTimestamp - } - public static func parse_inputPrivacyKeyVoiceMessages(_ reader: BufferReader) -> InputPrivacyKey? { - return Api.InputPrivacyKey.inputPrivacyKeyVoiceMessages - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index 2bdaf418d03..8a502fcddf3 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -1,3 +1,323 @@ +public extension Api { + enum InputPeerNotifySettings: TypeConstructorDescription { + case inputPeerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, sound: Api.NotificationSound?, storiesMuted: Api.Bool?, storiesHideSender: Api.Bool?, storiesSound: Api.NotificationSound?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputPeerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let sound, let storiesMuted, let storiesHideSender, let storiesSound): + if boxed { + buffer.appendInt32(-892638494) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {showPreviews!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {silent!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(muteUntil!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {sound!.serialize(buffer, true)} + if Int(flags) & Int(1 << 6) != 0 {storiesMuted!.serialize(buffer, true)} + if Int(flags) & Int(1 << 7) != 0 {storiesHideSender!.serialize(buffer, true)} + if Int(flags) & Int(1 << 8) != 0 {storiesSound!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputPeerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let sound, let storiesMuted, let storiesHideSender, let storiesSound): + return ("inputPeerNotifySettings", [("flags", flags as Any), ("showPreviews", showPreviews as Any), ("silent", silent as Any), ("muteUntil", muteUntil as Any), ("sound", sound as Any), ("storiesMuted", storiesMuted as Any), ("storiesHideSender", storiesHideSender as Any), ("storiesSound", storiesSound as Any)]) + } + } + + public static func parse_inputPeerNotifySettings(_ reader: BufferReader) -> InputPeerNotifySettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _3: Api.Bool? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _4: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } + var _5: Api.NotificationSound? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } } + var _6: Api.Bool? + if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _7: Api.Bool? + if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _8: Api.NotificationSound? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 6) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 8) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, sound: _5, storiesMuted: _6, storiesHideSender: _7, storiesSound: _8) + } + else { + return nil + } + } + + } +} +public extension Api { + enum InputPhoneCall: TypeConstructorDescription { + case inputPhoneCall(id: Int64, accessHash: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputPhoneCall(let id, let accessHash): + if boxed { + buffer.appendInt32(506920429) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputPhoneCall(let id, let accessHash): + return ("inputPhoneCall", [("id", id as Any), ("accessHash", accessHash as Any)]) + } + } + + public static func parse_inputPhoneCall(_ reader: BufferReader) -> InputPhoneCall? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputPhoneCall.inputPhoneCall(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum InputPhoto: TypeConstructorDescription { + case inputPhoto(id: Int64, accessHash: Int64, fileReference: Buffer) + case inputPhotoEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputPhoto(let id, let accessHash, let fileReference): + if boxed { + buffer.appendInt32(1001634122) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) + break + case .inputPhotoEmpty: + if boxed { + buffer.appendInt32(483901197) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputPhoto(let id, let accessHash, let fileReference): + return ("inputPhoto", [("id", id as Any), ("accessHash", accessHash as Any), ("fileReference", fileReference as Any)]) + case .inputPhotoEmpty: + return ("inputPhotoEmpty", []) + } + } + + public static func parse_inputPhoto(_ reader: BufferReader) -> InputPhoto? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputPhoto.inputPhoto(id: _1!, accessHash: _2!, fileReference: _3!) + } + else { + return nil + } + } + public static func parse_inputPhotoEmpty(_ reader: BufferReader) -> InputPhoto? { + return Api.InputPhoto.inputPhotoEmpty + } + + } +} +public extension Api { + enum InputPrivacyKey: TypeConstructorDescription { + case inputPrivacyKeyAbout + case inputPrivacyKeyAddedByPhone + case inputPrivacyKeyBirthday + case inputPrivacyKeyChatInvite + case inputPrivacyKeyForwards + case inputPrivacyKeyPhoneCall + case inputPrivacyKeyPhoneNumber + case inputPrivacyKeyPhoneP2P + case inputPrivacyKeyProfilePhoto + case inputPrivacyKeyStatusTimestamp + case inputPrivacyKeyVoiceMessages + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputPrivacyKeyAbout: + if boxed { + buffer.appendInt32(941870144) + } + + break + case .inputPrivacyKeyAddedByPhone: + if boxed { + buffer.appendInt32(-786326563) + } + + break + case .inputPrivacyKeyBirthday: + if boxed { + buffer.appendInt32(-698740276) + } + + break + case .inputPrivacyKeyChatInvite: + if boxed { + buffer.appendInt32(-1107622874) + } + + break + case .inputPrivacyKeyForwards: + if boxed { + buffer.appendInt32(-1529000952) + } + + break + case .inputPrivacyKeyPhoneCall: + if boxed { + buffer.appendInt32(-88417185) + } + + break + case .inputPrivacyKeyPhoneNumber: + if boxed { + buffer.appendInt32(55761658) + } + + break + case .inputPrivacyKeyPhoneP2P: + if boxed { + buffer.appendInt32(-610373422) + } + + break + case .inputPrivacyKeyProfilePhoto: + if boxed { + buffer.appendInt32(1461304012) + } + + break + case .inputPrivacyKeyStatusTimestamp: + if boxed { + buffer.appendInt32(1335282456) + } + + break + case .inputPrivacyKeyVoiceMessages: + if boxed { + buffer.appendInt32(-1360618136) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputPrivacyKeyAbout: + return ("inputPrivacyKeyAbout", []) + case .inputPrivacyKeyAddedByPhone: + return ("inputPrivacyKeyAddedByPhone", []) + case .inputPrivacyKeyBirthday: + return ("inputPrivacyKeyBirthday", []) + case .inputPrivacyKeyChatInvite: + return ("inputPrivacyKeyChatInvite", []) + case .inputPrivacyKeyForwards: + return ("inputPrivacyKeyForwards", []) + case .inputPrivacyKeyPhoneCall: + return ("inputPrivacyKeyPhoneCall", []) + case .inputPrivacyKeyPhoneNumber: + return ("inputPrivacyKeyPhoneNumber", []) + case .inputPrivacyKeyPhoneP2P: + return ("inputPrivacyKeyPhoneP2P", []) + case .inputPrivacyKeyProfilePhoto: + return ("inputPrivacyKeyProfilePhoto", []) + case .inputPrivacyKeyStatusTimestamp: + return ("inputPrivacyKeyStatusTimestamp", []) + case .inputPrivacyKeyVoiceMessages: + return ("inputPrivacyKeyVoiceMessages", []) + } + } + + public static func parse_inputPrivacyKeyAbout(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyAbout + } + public static func parse_inputPrivacyKeyAddedByPhone(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyAddedByPhone + } + public static func parse_inputPrivacyKeyBirthday(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyBirthday + } + public static func parse_inputPrivacyKeyChatInvite(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyChatInvite + } + public static func parse_inputPrivacyKeyForwards(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyForwards + } + public static func parse_inputPrivacyKeyPhoneCall(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyPhoneCall + } + public static func parse_inputPrivacyKeyPhoneNumber(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyPhoneNumber + } + public static func parse_inputPrivacyKeyPhoneP2P(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyPhoneP2P + } + public static func parse_inputPrivacyKeyProfilePhoto(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyProfilePhoto + } + public static func parse_inputPrivacyKeyStatusTimestamp(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyStatusTimestamp + } + public static func parse_inputPrivacyKeyVoiceMessages(_ reader: BufferReader) -> InputPrivacyKey? { + return Api.InputPrivacyKey.inputPrivacyKeyVoiceMessages + } + + } +} public extension Api { enum InputPrivacyRule: TypeConstructorDescription { case inputPrivacyValueAllowAll @@ -508,355 +828,3 @@ public extension Api { } } -public extension Api { - indirect enum InputSingleMedia: TypeConstructorDescription { - case inputSingleMedia(flags: Int32, media: Api.InputMedia, randomId: Int64, message: String, entities: [Api.MessageEntity]?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputSingleMedia(let flags, let media, let randomId, let message, let entities): - if boxed { - buffer.appendInt32(482797855) - } - serializeInt32(flags, buffer: buffer, boxed: false) - media.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputSingleMedia(let flags, let media, let randomId, let message, let entities): - return ("inputSingleMedia", [("flags", flags as Any), ("media", media as Any), ("randomId", randomId as Any), ("message", message as Any), ("entities", entities as Any)]) - } - } - - public static func parse_inputSingleMedia(_ reader: BufferReader) -> InputSingleMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputMedia? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputMedia - } - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputSingleMedia.inputSingleMedia(flags: _1!, media: _2!, randomId: _3!, message: _4!, entities: _5) - } - else { - return nil - } - } - - } -} -public extension Api { - enum InputStickerSet: TypeConstructorDescription { - case inputStickerSetAnimatedEmoji - case inputStickerSetAnimatedEmojiAnimations - case inputStickerSetDice(emoticon: String) - case inputStickerSetEmojiChannelDefaultStatuses - case inputStickerSetEmojiDefaultStatuses - case inputStickerSetEmojiDefaultTopicIcons - case inputStickerSetEmojiGenericAnimations - case inputStickerSetEmpty - case inputStickerSetID(id: Int64, accessHash: Int64) - case inputStickerSetPremiumGifts - case inputStickerSetShortName(shortName: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputStickerSetAnimatedEmoji: - if boxed { - buffer.appendInt32(42402760) - } - - break - case .inputStickerSetAnimatedEmojiAnimations: - if boxed { - buffer.appendInt32(215889721) - } - - break - case .inputStickerSetDice(let emoticon): - if boxed { - buffer.appendInt32(-427863538) - } - serializeString(emoticon, buffer: buffer, boxed: false) - break - case .inputStickerSetEmojiChannelDefaultStatuses: - if boxed { - buffer.appendInt32(1232373075) - } - - break - case .inputStickerSetEmojiDefaultStatuses: - if boxed { - buffer.appendInt32(701560302) - } - - break - case .inputStickerSetEmojiDefaultTopicIcons: - if boxed { - buffer.appendInt32(1153562857) - } - - break - case .inputStickerSetEmojiGenericAnimations: - if boxed { - buffer.appendInt32(80008398) - } - - break - case .inputStickerSetEmpty: - if boxed { - buffer.appendInt32(-4838507) - } - - break - case .inputStickerSetID(let id, let accessHash): - if boxed { - buffer.appendInt32(-1645763991) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputStickerSetPremiumGifts: - if boxed { - buffer.appendInt32(-930399486) - } - - break - case .inputStickerSetShortName(let shortName): - if boxed { - buffer.appendInt32(-2044933984) - } - serializeString(shortName, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputStickerSetAnimatedEmoji: - return ("inputStickerSetAnimatedEmoji", []) - case .inputStickerSetAnimatedEmojiAnimations: - return ("inputStickerSetAnimatedEmojiAnimations", []) - case .inputStickerSetDice(let emoticon): - return ("inputStickerSetDice", [("emoticon", emoticon as Any)]) - case .inputStickerSetEmojiChannelDefaultStatuses: - return ("inputStickerSetEmojiChannelDefaultStatuses", []) - case .inputStickerSetEmojiDefaultStatuses: - return ("inputStickerSetEmojiDefaultStatuses", []) - case .inputStickerSetEmojiDefaultTopicIcons: - return ("inputStickerSetEmojiDefaultTopicIcons", []) - case .inputStickerSetEmojiGenericAnimations: - return ("inputStickerSetEmojiGenericAnimations", []) - case .inputStickerSetEmpty: - return ("inputStickerSetEmpty", []) - case .inputStickerSetID(let id, let accessHash): - return ("inputStickerSetID", [("id", id as Any), ("accessHash", accessHash as Any)]) - case .inputStickerSetPremiumGifts: - return ("inputStickerSetPremiumGifts", []) - case .inputStickerSetShortName(let shortName): - return ("inputStickerSetShortName", [("shortName", shortName as Any)]) - } - } - - public static func parse_inputStickerSetAnimatedEmoji(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetAnimatedEmoji - } - public static func parse_inputStickerSetAnimatedEmojiAnimations(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetAnimatedEmojiAnimations - } - public static func parse_inputStickerSetDice(_ reader: BufferReader) -> InputStickerSet? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.InputStickerSet.inputStickerSetDice(emoticon: _1!) - } - else { - return nil - } - } - public static func parse_inputStickerSetEmojiChannelDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetEmojiChannelDefaultStatuses - } - public static func parse_inputStickerSetEmojiDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetEmojiDefaultStatuses - } - public static func parse_inputStickerSetEmojiDefaultTopicIcons(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetEmojiDefaultTopicIcons - } - public static func parse_inputStickerSetEmojiGenericAnimations(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetEmojiGenericAnimations - } - public static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetEmpty - } - public static func parse_inputStickerSetID(_ reader: BufferReader) -> InputStickerSet? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputStickerSet.inputStickerSetID(id: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputStickerSetPremiumGifts(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetPremiumGifts - } - public static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.InputStickerSet.inputStickerSetShortName(shortName: _1!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum InputStickerSetItem: TypeConstructorDescription { - case inputStickerSetItem(flags: Int32, document: Api.InputDocument, emoji: String, maskCoords: Api.MaskCoords?, keywords: String?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputStickerSetItem(let flags, let document, let emoji, let maskCoords, let keywords): - if boxed { - buffer.appendInt32(853188252) - } - serializeInt32(flags, buffer: buffer, boxed: false) - document.serialize(buffer, true) - serializeString(emoji, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {maskCoords!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(keywords!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputStickerSetItem(let flags, let document, let emoji, let maskCoords, let keywords): - return ("inputStickerSetItem", [("flags", flags as Any), ("document", document as Any), ("emoji", emoji as Any), ("maskCoords", maskCoords as Any), ("keywords", keywords as Any)]) - } - } - - public static func parse_inputStickerSetItem(_ reader: BufferReader) -> InputStickerSetItem? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputDocument? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputDocument - } - var _3: String? - _3 = parseString(reader) - var _4: Api.MaskCoords? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.MaskCoords - } } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputStickerSetItem.inputStickerSetItem(flags: _1!, document: _2!, emoji: _3!, maskCoords: _4, keywords: _5) - } - else { - return nil - } - } - - } -} -public extension Api { - enum InputStickeredMedia: TypeConstructorDescription { - case inputStickeredMediaDocument(id: Api.InputDocument) - case inputStickeredMediaPhoto(id: Api.InputPhoto) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputStickeredMediaDocument(let id): - if boxed { - buffer.appendInt32(70813275) - } - id.serialize(buffer, true) - break - case .inputStickeredMediaPhoto(let id): - if boxed { - buffer.appendInt32(1251549527) - } - id.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputStickeredMediaDocument(let id): - return ("inputStickeredMediaDocument", [("id", id as Any)]) - case .inputStickeredMediaPhoto(let id): - return ("inputStickeredMediaPhoto", [("id", id as Any)]) - } - } - - public static func parse_inputStickeredMediaDocument(_ reader: BufferReader) -> InputStickeredMedia? { - var _1: Api.InputDocument? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputDocument - } - let _c1 = _1 != nil - if _c1 { - return Api.InputStickeredMedia.inputStickeredMediaDocument(id: _1!) - } - else { - return nil - } - } - public static func parse_inputStickeredMediaPhoto(_ reader: BufferReader) -> InputStickeredMedia? { - var _1: Api.InputPhoto? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputPhoto - } - let _c1 = _1 != nil - if _c1 { - return Api.InputStickeredMedia.inputStickeredMediaPhoto(id: _1!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index 3dd97bb3199..b318ced6ef0 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -1,9 +1,362 @@ +public extension Api { + indirect enum InputSingleMedia: TypeConstructorDescription { + case inputSingleMedia(flags: Int32, media: Api.InputMedia, randomId: Int64, message: String, entities: [Api.MessageEntity]?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputSingleMedia(let flags, let media, let randomId, let message, let entities): + if boxed { + buffer.appendInt32(482797855) + } + serializeInt32(flags, buffer: buffer, boxed: false) + media.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputSingleMedia(let flags, let media, let randomId, let message, let entities): + return ("inputSingleMedia", [("flags", flags as Any), ("media", media as Any), ("randomId", randomId as Any), ("message", message as Any), ("entities", entities as Any)]) + } + } + + public static func parse_inputSingleMedia(_ reader: BufferReader) -> InputSingleMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputMedia? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputMedia + } + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.InputSingleMedia.inputSingleMedia(flags: _1!, media: _2!, randomId: _3!, message: _4!, entities: _5) + } + else { + return nil + } + } + + } +} +public extension Api { + enum InputStickerSet: TypeConstructorDescription { + case inputStickerSetAnimatedEmoji + case inputStickerSetAnimatedEmojiAnimations + case inputStickerSetDice(emoticon: String) + case inputStickerSetEmojiChannelDefaultStatuses + case inputStickerSetEmojiDefaultStatuses + case inputStickerSetEmojiDefaultTopicIcons + case inputStickerSetEmojiGenericAnimations + case inputStickerSetEmpty + case inputStickerSetID(id: Int64, accessHash: Int64) + case inputStickerSetPremiumGifts + case inputStickerSetShortName(shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickerSetAnimatedEmoji: + if boxed { + buffer.appendInt32(42402760) + } + + break + case .inputStickerSetAnimatedEmojiAnimations: + if boxed { + buffer.appendInt32(215889721) + } + + break + case .inputStickerSetDice(let emoticon): + if boxed { + buffer.appendInt32(-427863538) + } + serializeString(emoticon, buffer: buffer, boxed: false) + break + case .inputStickerSetEmojiChannelDefaultStatuses: + if boxed { + buffer.appendInt32(1232373075) + } + + break + case .inputStickerSetEmojiDefaultStatuses: + if boxed { + buffer.appendInt32(701560302) + } + + break + case .inputStickerSetEmojiDefaultTopicIcons: + if boxed { + buffer.appendInt32(1153562857) + } + + break + case .inputStickerSetEmojiGenericAnimations: + if boxed { + buffer.appendInt32(80008398) + } + + break + case .inputStickerSetEmpty: + if boxed { + buffer.appendInt32(-4838507) + } + + break + case .inputStickerSetID(let id, let accessHash): + if boxed { + buffer.appendInt32(-1645763991) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputStickerSetPremiumGifts: + if boxed { + buffer.appendInt32(-930399486) + } + + break + case .inputStickerSetShortName(let shortName): + if boxed { + buffer.appendInt32(-2044933984) + } + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputStickerSetAnimatedEmoji: + return ("inputStickerSetAnimatedEmoji", []) + case .inputStickerSetAnimatedEmojiAnimations: + return ("inputStickerSetAnimatedEmojiAnimations", []) + case .inputStickerSetDice(let emoticon): + return ("inputStickerSetDice", [("emoticon", emoticon as Any)]) + case .inputStickerSetEmojiChannelDefaultStatuses: + return ("inputStickerSetEmojiChannelDefaultStatuses", []) + case .inputStickerSetEmojiDefaultStatuses: + return ("inputStickerSetEmojiDefaultStatuses", []) + case .inputStickerSetEmojiDefaultTopicIcons: + return ("inputStickerSetEmojiDefaultTopicIcons", []) + case .inputStickerSetEmojiGenericAnimations: + return ("inputStickerSetEmojiGenericAnimations", []) + case .inputStickerSetEmpty: + return ("inputStickerSetEmpty", []) + case .inputStickerSetID(let id, let accessHash): + return ("inputStickerSetID", [("id", id as Any), ("accessHash", accessHash as Any)]) + case .inputStickerSetPremiumGifts: + return ("inputStickerSetPremiumGifts", []) + case .inputStickerSetShortName(let shortName): + return ("inputStickerSetShortName", [("shortName", shortName as Any)]) + } + } + + public static func parse_inputStickerSetAnimatedEmoji(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetAnimatedEmoji + } + public static func parse_inputStickerSetAnimatedEmojiAnimations(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetAnimatedEmojiAnimations + } + public static func parse_inputStickerSetDice(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputStickerSet.inputStickerSetDice(emoticon: _1!) + } + else { + return nil + } + } + public static func parse_inputStickerSetEmojiChannelDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmojiChannelDefaultStatuses + } + public static func parse_inputStickerSetEmojiDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmojiDefaultStatuses + } + public static func parse_inputStickerSetEmojiDefaultTopicIcons(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmojiDefaultTopicIcons + } + public static func parse_inputStickerSetEmojiGenericAnimations(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmojiGenericAnimations + } + public static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmpty + } + public static func parse_inputStickerSetID(_ reader: BufferReader) -> InputStickerSet? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputStickerSet.inputStickerSetID(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputStickerSetPremiumGifts(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetPremiumGifts + } + public static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputStickerSet.inputStickerSetShortName(shortName: _1!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum InputStickerSetItem: TypeConstructorDescription { + case inputStickerSetItem(flags: Int32, document: Api.InputDocument, emoji: String, maskCoords: Api.MaskCoords?, keywords: String?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickerSetItem(let flags, let document, let emoji, let maskCoords, let keywords): + if boxed { + buffer.appendInt32(853188252) + } + serializeInt32(flags, buffer: buffer, boxed: false) + document.serialize(buffer, true) + serializeString(emoji, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {maskCoords!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(keywords!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputStickerSetItem(let flags, let document, let emoji, let maskCoords, let keywords): + return ("inputStickerSetItem", [("flags", flags as Any), ("document", document as Any), ("emoji", emoji as Any), ("maskCoords", maskCoords as Any), ("keywords", keywords as Any)]) + } + } + + public static func parse_inputStickerSetItem(_ reader: BufferReader) -> InputStickerSetItem? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputDocument? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputDocument + } + var _3: String? + _3 = parseString(reader) + var _4: Api.MaskCoords? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MaskCoords + } } + var _5: String? + if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.InputStickerSetItem.inputStickerSetItem(flags: _1!, document: _2!, emoji: _3!, maskCoords: _4, keywords: _5) + } + else { + return nil + } + } + + } +} +public extension Api { + enum InputStickeredMedia: TypeConstructorDescription { + case inputStickeredMediaDocument(id: Api.InputDocument) + case inputStickeredMediaPhoto(id: Api.InputPhoto) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickeredMediaDocument(let id): + if boxed { + buffer.appendInt32(70813275) + } + id.serialize(buffer, true) + break + case .inputStickeredMediaPhoto(let id): + if boxed { + buffer.appendInt32(1251549527) + } + id.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputStickeredMediaDocument(let id): + return ("inputStickeredMediaDocument", [("id", id as Any)]) + case .inputStickeredMediaPhoto(let id): + return ("inputStickeredMediaPhoto", [("id", id as Any)]) + } + } + + public static func parse_inputStickeredMediaDocument(_ reader: BufferReader) -> InputStickeredMedia? { + var _1: Api.InputDocument? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputDocument + } + let _c1 = _1 != nil + if _c1 { + return Api.InputStickeredMedia.inputStickeredMediaDocument(id: _1!) + } + else { + return nil + } + } + public static func parse_inputStickeredMediaPhoto(_ reader: BufferReader) -> InputStickeredMedia? { + var _1: Api.InputPhoto? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputPhoto + } + let _c1 = _1 != nil + if _c1 { + return Api.InputStickeredMedia.inputStickeredMediaPhoto(id: _1!) + } + else { + return nil + } + } + + } +} public extension Api { indirect enum InputStorePaymentPurpose: TypeConstructorDescription { case inputStorePaymentGiftPremium(userId: Api.InputUser, currency: String, amount: Int64) case inputStorePaymentPremiumGiftCode(flags: Int32, users: [Api.InputUser], boostPeer: Api.InputPeer?, currency: String, amount: Int64) case inputStorePaymentPremiumGiveaway(flags: Int32, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64) case inputStorePaymentPremiumSubscription(flags: Int32) + case inputStorePaymentStars(flags: Int32, stars: Int64, currency: String, amount: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -57,6 +410,15 @@ public extension Api { } serializeInt32(flags, buffer: buffer, boxed: false) break + case .inputStorePaymentStars(let flags, let stars, let currency, let amount): + if boxed { + buffer.appendInt32(1326377183) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(stars, buffer: buffer, boxed: false) + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + break } } @@ -70,6 +432,8 @@ public extension Api { return ("inputStorePaymentPremiumGiveaway", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("additionalPeers", additionalPeers as Any), ("countriesIso2", countriesIso2 as Any), ("prizeDescription", prizeDescription as Any), ("randomId", randomId as Any), ("untilDate", untilDate as Any), ("currency", currency as Any), ("amount", amount as Any)]) case .inputStorePaymentPremiumSubscription(let flags): return ("inputStorePaymentPremiumSubscription", [("flags", flags as Any)]) + case .inputStorePaymentStars(let flags, let stars, let currency, let amount): + return ("inputStorePaymentStars", [("flags", flags as Any), ("stars", stars as Any), ("currency", currency as Any), ("amount", amount as Any)]) } } @@ -171,6 +535,26 @@ public extension Api { return nil } } + public static func parse_inputStorePaymentStars(_ reader: BufferReader) -> InputStorePaymentPurpose? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputStorePaymentPurpose.inputStorePaymentStars(flags: _1!, stars: _2!, currency: _3!, amount: _4!) + } + else { + return nil + } + } } } @@ -650,735 +1034,3 @@ public extension Api { } } -public extension Api { - enum Invoice: TypeConstructorDescription { - case invoice(flags: Int32, currency: String, prices: [Api.LabeledPrice], maxTipAmount: Int64?, suggestedTipAmounts: [Int64]?, termsUrl: String?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts, let termsUrl): - if boxed { - buffer.appendInt32(1572428309) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(currency, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(prices.count)) - for item in prices { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 8) != 0 {serializeInt64(maxTipAmount!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 8) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(suggestedTipAmounts!.count)) - for item in suggestedTipAmounts! { - serializeInt64(item, buffer: buffer, boxed: false) - }} - if Int(flags) & Int(1 << 10) != 0 {serializeString(termsUrl!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts, let termsUrl): - return ("invoice", [("flags", flags as Any), ("currency", currency as Any), ("prices", prices as Any), ("maxTipAmount", maxTipAmount as Any), ("suggestedTipAmounts", suggestedTipAmounts as Any), ("termsUrl", termsUrl as Any)]) - } - } - - public static func parse_invoice(_ reader: BufferReader) -> Invoice? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: [Api.LabeledPrice]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LabeledPrice.self) - } - var _4: Int64? - if Int(_1!) & Int(1 << 8) != 0 {_4 = reader.readInt64() } - var _5: [Int64]? - if Int(_1!) & Int(1 << 8) != 0 {if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } } - var _6: String? - if Int(_1!) & Int(1 << 10) != 0 {_6 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 10) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Invoice.invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5, termsUrl: _6) - } - else { - return nil - } - } - - } -} -public extension Api { - enum JSONObjectValue: TypeConstructorDescription { - case jsonObjectValue(key: String, value: Api.JSONValue) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .jsonObjectValue(let key, let value): - if boxed { - buffer.appendInt32(-1059185703) - } - serializeString(key, buffer: buffer, boxed: false) - value.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .jsonObjectValue(let key, let value): - return ("jsonObjectValue", [("key", key as Any), ("value", value as Any)]) - } - } - - public static func parse_jsonObjectValue(_ reader: BufferReader) -> JSONObjectValue? { - var _1: String? - _1 = parseString(reader) - var _2: Api.JSONValue? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.JSONValue - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.JSONObjectValue.jsonObjectValue(key: _1!, value: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum JSONValue: TypeConstructorDescription { - case jsonArray(value: [Api.JSONValue]) - case jsonBool(value: Api.Bool) - case jsonNull - case jsonNumber(value: Double) - case jsonObject(value: [Api.JSONObjectValue]) - case jsonString(value: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .jsonArray(let value): - if boxed { - buffer.appendInt32(-146520221) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(value.count)) - for item in value { - item.serialize(buffer, true) - } - break - case .jsonBool(let value): - if boxed { - buffer.appendInt32(-952869270) - } - value.serialize(buffer, true) - break - case .jsonNull: - if boxed { - buffer.appendInt32(1064139624) - } - - break - case .jsonNumber(let value): - if boxed { - buffer.appendInt32(736157604) - } - serializeDouble(value, buffer: buffer, boxed: false) - break - case .jsonObject(let value): - if boxed { - buffer.appendInt32(-1715350371) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(value.count)) - for item in value { - item.serialize(buffer, true) - } - break - case .jsonString(let value): - if boxed { - buffer.appendInt32(-1222740358) - } - serializeString(value, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .jsonArray(let value): - return ("jsonArray", [("value", value as Any)]) - case .jsonBool(let value): - return ("jsonBool", [("value", value as Any)]) - case .jsonNull: - return ("jsonNull", []) - case .jsonNumber(let value): - return ("jsonNumber", [("value", value as Any)]) - case .jsonObject(let value): - return ("jsonObject", [("value", value as Any)]) - case .jsonString(let value): - return ("jsonString", [("value", value as Any)]) - } - } - - public static func parse_jsonArray(_ reader: BufferReader) -> JSONValue? { - var _1: [Api.JSONValue]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.JSONValue.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.JSONValue.jsonArray(value: _1!) - } - else { - return nil - } - } - public static func parse_jsonBool(_ reader: BufferReader) -> JSONValue? { - var _1: Api.Bool? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Bool - } - let _c1 = _1 != nil - if _c1 { - return Api.JSONValue.jsonBool(value: _1!) - } - else { - return nil - } - } - public static func parse_jsonNull(_ reader: BufferReader) -> JSONValue? { - return Api.JSONValue.jsonNull - } - public static func parse_jsonNumber(_ reader: BufferReader) -> JSONValue? { - var _1: Double? - _1 = reader.readDouble() - let _c1 = _1 != nil - if _c1 { - return Api.JSONValue.jsonNumber(value: _1!) - } - else { - return nil - } - } - public static func parse_jsonObject(_ reader: BufferReader) -> JSONValue? { - var _1: [Api.JSONObjectValue]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.JSONObjectValue.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.JSONValue.jsonObject(value: _1!) - } - else { - return nil - } - } - public static func parse_jsonString(_ reader: BufferReader) -> JSONValue? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.JSONValue.jsonString(value: _1!) - } - else { - return nil - } - } - - } -} -public extension Api { - indirect enum KeyboardButton: TypeConstructorDescription { - case inputKeyboardButtonRequestPeer(flags: Int32, text: String, buttonId: Int32, peerType: Api.RequestPeerType, maxQuantity: Int32) - case inputKeyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, bot: Api.InputUser) - case inputKeyboardButtonUserProfile(text: String, userId: Api.InputUser) - case keyboardButton(text: String) - case keyboardButtonBuy(text: String) - case keyboardButtonCallback(flags: Int32, text: String, data: Buffer) - case keyboardButtonGame(text: String) - case keyboardButtonRequestGeoLocation(text: String) - case keyboardButtonRequestPeer(text: String, buttonId: Int32, peerType: Api.RequestPeerType, maxQuantity: Int32) - case keyboardButtonRequestPhone(text: String) - case keyboardButtonRequestPoll(flags: Int32, quiz: Api.Bool?, text: String) - case keyboardButtonSimpleWebView(text: String, url: String) - case keyboardButtonSwitchInline(flags: Int32, text: String, query: String, peerTypes: [Api.InlineQueryPeerType]?) - case keyboardButtonUrl(text: String, url: String) - case keyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, buttonId: Int32) - case keyboardButtonUserProfile(text: String, userId: Int64) - case keyboardButtonWebView(text: String, url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputKeyboardButtonRequestPeer(let flags, let text, let buttonId, let peerType, let maxQuantity): - if boxed { - buffer.appendInt32(-916050683) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - serializeInt32(buttonId, buffer: buffer, boxed: false) - peerType.serialize(buffer, true) - serializeInt32(maxQuantity, buffer: buffer, boxed: false) - break - case .inputKeyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let bot): - if boxed { - buffer.appendInt32(-802258988) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(fwdText!, buffer: buffer, boxed: false)} - serializeString(url, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - break - case .inputKeyboardButtonUserProfile(let text, let userId): - if boxed { - buffer.appendInt32(-376962181) - } - serializeString(text, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - break - case .keyboardButton(let text): - if boxed { - buffer.appendInt32(-1560655744) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonBuy(let text): - if boxed { - buffer.appendInt32(-1344716869) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonCallback(let flags, let text, let data): - if boxed { - buffer.appendInt32(901503851) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - serializeBytes(data, buffer: buffer, boxed: false) - break - case .keyboardButtonGame(let text): - if boxed { - buffer.appendInt32(1358175439) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestGeoLocation(let text): - if boxed { - buffer.appendInt32(-59151553) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestPeer(let text, let buttonId, let peerType, let maxQuantity): - if boxed { - buffer.appendInt32(1406648280) - } - serializeString(text, buffer: buffer, boxed: false) - serializeInt32(buttonId, buffer: buffer, boxed: false) - peerType.serialize(buffer, true) - serializeInt32(maxQuantity, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestPhone(let text): - if boxed { - buffer.appendInt32(-1318425559) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestPoll(let flags, let quiz, let text): - if boxed { - buffer.appendInt32(-1144565411) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {quiz!.serialize(buffer, true)} - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonSimpleWebView(let text, let url): - if boxed { - buffer.appendInt32(-1598009252) - } - serializeString(text, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - case .keyboardButtonSwitchInline(let flags, let text, let query, let peerTypes): - if boxed { - buffer.appendInt32(-1816527947) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - serializeString(query, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peerTypes!.count)) - for item in peerTypes! { - item.serialize(buffer, true) - }} - break - case .keyboardButtonUrl(let text, let url): - if boxed { - buffer.appendInt32(629866245) - } - serializeString(text, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - case .keyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let buttonId): - if boxed { - buffer.appendInt32(280464681) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(fwdText!, buffer: buffer, boxed: false)} - serializeString(url, buffer: buffer, boxed: false) - serializeInt32(buttonId, buffer: buffer, boxed: false) - break - case .keyboardButtonUserProfile(let text, let userId): - if boxed { - buffer.appendInt32(814112961) - } - serializeString(text, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - break - case .keyboardButtonWebView(let text, let url): - if boxed { - buffer.appendInt32(326529584) - } - serializeString(text, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputKeyboardButtonRequestPeer(let flags, let text, let buttonId, let peerType, let maxQuantity): - return ("inputKeyboardButtonRequestPeer", [("flags", flags as Any), ("text", text as Any), ("buttonId", buttonId as Any), ("peerType", peerType as Any), ("maxQuantity", maxQuantity as Any)]) - case .inputKeyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let bot): - return ("inputKeyboardButtonUrlAuth", [("flags", flags as Any), ("text", text as Any), ("fwdText", fwdText as Any), ("url", url as Any), ("bot", bot as Any)]) - case .inputKeyboardButtonUserProfile(let text, let userId): - return ("inputKeyboardButtonUserProfile", [("text", text as Any), ("userId", userId as Any)]) - case .keyboardButton(let text): - return ("keyboardButton", [("text", text as Any)]) - case .keyboardButtonBuy(let text): - return ("keyboardButtonBuy", [("text", text as Any)]) - case .keyboardButtonCallback(let flags, let text, let data): - return ("keyboardButtonCallback", [("flags", flags as Any), ("text", text as Any), ("data", data as Any)]) - case .keyboardButtonGame(let text): - return ("keyboardButtonGame", [("text", text as Any)]) - case .keyboardButtonRequestGeoLocation(let text): - return ("keyboardButtonRequestGeoLocation", [("text", text as Any)]) - case .keyboardButtonRequestPeer(let text, let buttonId, let peerType, let maxQuantity): - return ("keyboardButtonRequestPeer", [("text", text as Any), ("buttonId", buttonId as Any), ("peerType", peerType as Any), ("maxQuantity", maxQuantity as Any)]) - case .keyboardButtonRequestPhone(let text): - return ("keyboardButtonRequestPhone", [("text", text as Any)]) - case .keyboardButtonRequestPoll(let flags, let quiz, let text): - return ("keyboardButtonRequestPoll", [("flags", flags as Any), ("quiz", quiz as Any), ("text", text as Any)]) - case .keyboardButtonSimpleWebView(let text, let url): - return ("keyboardButtonSimpleWebView", [("text", text as Any), ("url", url as Any)]) - case .keyboardButtonSwitchInline(let flags, let text, let query, let peerTypes): - return ("keyboardButtonSwitchInline", [("flags", flags as Any), ("text", text as Any), ("query", query as Any), ("peerTypes", peerTypes as Any)]) - case .keyboardButtonUrl(let text, let url): - return ("keyboardButtonUrl", [("text", text as Any), ("url", url as Any)]) - case .keyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let buttonId): - return ("keyboardButtonUrlAuth", [("flags", flags as Any), ("text", text as Any), ("fwdText", fwdText as Any), ("url", url as Any), ("buttonId", buttonId as Any)]) - case .keyboardButtonUserProfile(let text, let userId): - return ("keyboardButtonUserProfile", [("text", text as Any), ("userId", userId as Any)]) - case .keyboardButtonWebView(let text, let url): - return ("keyboardButtonWebView", [("text", text as Any), ("url", url as Any)]) - } - } - - public static func parse_inputKeyboardButtonRequestPeer(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.RequestPeerType? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.RequestPeerType - } - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.KeyboardButton.inputKeyboardButtonRequestPeer(flags: _1!, text: _2!, buttonId: _3!, peerType: _4!, maxQuantity: _5!) - } - else { - return nil - } - } - public static func parse_inputKeyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } - var _4: String? - _4 = parseString(reader) - var _5: Api.InputUser? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.InputUser - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.KeyboardButton.inputKeyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, bot: _5!) - } - else { - return nil - } - } - public static func parse_inputKeyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: Api.InputUser? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputUser - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.inputKeyboardButtonUserProfile(text: _1!, userId: _2!) - } - else { - return nil - } - } - public static func parse_keyboardButton(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButton(text: _1!) - } - else { - return nil - } - } - public static func parse_keyboardButtonBuy(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonBuy(text: _1!) - } - else { - return nil - } - } - public static func parse_keyboardButtonCallback(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Buffer? - _3 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonCallback(flags: _1!, text: _2!, data: _3!) - } - else { - return nil - } - } - public static func parse_keyboardButtonGame(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonGame(text: _1!) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestGeoLocation(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonRequestGeoLocation(text: _1!) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestPeer(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.RequestPeerType? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.RequestPeerType - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonRequestPeer(text: _1!, buttonId: _2!, peerType: _3!, maxQuantity: _4!) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestPhone(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonRequestPhone(text: _1!) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestPoll(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonRequestPoll(flags: _1!, quiz: _2, text: _3!) - } - else { - return nil - } - } - public static func parse_keyboardButtonSimpleWebView(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonSimpleWebView(text: _1!, url: _2!) - } - else { - return nil - } - } - public static func parse_keyboardButtonSwitchInline(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: [Api.InlineQueryPeerType]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InlineQueryPeerType.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonSwitchInline(flags: _1!, text: _2!, query: _3!, peerTypes: _4) - } - else { - return nil - } - } - public static func parse_keyboardButtonUrl(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonUrl(text: _1!, url: _2!) - } - else { - return nil - } - } - public static func parse_keyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.KeyboardButton.keyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, buttonId: _5!) - } - else { - return nil - } - } - public static func parse_keyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonUserProfile(text: _1!, userId: _2!) - } - else { - return nil - } - } - public static func parse_keyboardButtonWebView(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonWebView(text: _1!, url: _2!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api13.swift b/submodules/TelegramApi/Sources/Api13.swift index d59ba867f96..39e9b04e848 100644 --- a/submodules/TelegramApi/Sources/Api13.swift +++ b/submodules/TelegramApi/Sources/Api13.swift @@ -1,37 +1,63 @@ public extension Api { - enum KeyboardButtonRow: TypeConstructorDescription { - case keyboardButtonRow(buttons: [Api.KeyboardButton]) + enum Invoice: TypeConstructorDescription { + case invoice(flags: Int32, currency: String, prices: [Api.LabeledPrice], maxTipAmount: Int64?, suggestedTipAmounts: [Int64]?, termsUrl: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .keyboardButtonRow(let buttons): + case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts, let termsUrl): if boxed { - buffer.appendInt32(2002815875) + buffer.appendInt32(1572428309) } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(currency, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(buttons.count)) - for item in buttons { + buffer.appendInt32(Int32(prices.count)) + for item in prices { item.serialize(buffer, true) } + if Int(flags) & Int(1 << 8) != 0 {serializeInt64(maxTipAmount!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 8) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(suggestedTipAmounts!.count)) + for item in suggestedTipAmounts! { + serializeInt64(item, buffer: buffer, boxed: false) + }} + if Int(flags) & Int(1 << 10) != 0 {serializeString(termsUrl!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .keyboardButtonRow(let buttons): - return ("keyboardButtonRow", [("buttons", buttons as Any)]) + case .invoice(let flags, let currency, let prices, let maxTipAmount, let suggestedTipAmounts, let termsUrl): + return ("invoice", [("flags", flags as Any), ("currency", currency as Any), ("prices", prices as Any), ("maxTipAmount", maxTipAmount as Any), ("suggestedTipAmounts", suggestedTipAmounts as Any), ("termsUrl", termsUrl as Any)]) } } - public static func parse_keyboardButtonRow(_ reader: BufferReader) -> KeyboardButtonRow? { - var _1: [Api.KeyboardButton]? + public static func parse_invoice(_ reader: BufferReader) -> Invoice? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: [Api.LabeledPrice]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButton.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LabeledPrice.self) } + var _4: Int64? + if Int(_1!) & Int(1 << 8) != 0 {_4 = reader.readInt64() } + var _5: [Int64]? + if Int(_1!) & Int(1 << 8) != 0 {if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } } + var _6: String? + if Int(_1!) & Int(1 << 10) != 0 {_6 = parseString(reader) } let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButtonRow.keyboardButtonRow(buttons: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 10) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Invoice.invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5, termsUrl: _6) } else { return nil @@ -41,37 +67,39 @@ public extension Api { } } public extension Api { - enum LabeledPrice: TypeConstructorDescription { - case labeledPrice(label: String, amount: Int64) + enum JSONObjectValue: TypeConstructorDescription { + case jsonObjectValue(key: String, value: Api.JSONValue) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .labeledPrice(let label, let amount): + case .jsonObjectValue(let key, let value): if boxed { - buffer.appendInt32(-886477832) + buffer.appendInt32(-1059185703) } - serializeString(label, buffer: buffer, boxed: false) - serializeInt64(amount, buffer: buffer, boxed: false) + serializeString(key, buffer: buffer, boxed: false) + value.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .labeledPrice(let label, let amount): - return ("labeledPrice", [("label", label as Any), ("amount", amount as Any)]) + case .jsonObjectValue(let key, let value): + return ("jsonObjectValue", [("key", key as Any), ("value", value as Any)]) } } - public static func parse_labeledPrice(_ reader: BufferReader) -> LabeledPrice? { + public static func parse_jsonObjectValue(_ reader: BufferReader) -> JSONObjectValue? { var _1: String? _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() + var _2: Api.JSONValue? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.JSONValue + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.LabeledPrice.labeledPrice(label: _1!, amount: _2!) + return Api.JSONObjectValue.jsonObjectValue(key: _1!, value: _2!) } else { return nil @@ -81,227 +109,139 @@ public extension Api { } } public extension Api { - enum LangPackDifference: TypeConstructorDescription { - case langPackDifference(langCode: String, fromVersion: Int32, version: Int32, strings: [Api.LangPackString]) + enum JSONValue: TypeConstructorDescription { + case jsonArray(value: [Api.JSONValue]) + case jsonBool(value: Api.Bool) + case jsonNull + case jsonNumber(value: Double) + case jsonObject(value: [Api.JSONObjectValue]) + case jsonString(value: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .langPackDifference(let langCode, let fromVersion, let version, let strings): + case .jsonArray(let value): if boxed { - buffer.appendInt32(-209337866) + buffer.appendInt32(-146520221) } - serializeString(langCode, buffer: buffer, boxed: false) - serializeInt32(fromVersion, buffer: buffer, boxed: false) - serializeInt32(version, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(strings.count)) - for item in strings { + buffer.appendInt32(Int32(value.count)) + for item in value { + item.serialize(buffer, true) + } + break + case .jsonBool(let value): + if boxed { + buffer.appendInt32(-952869270) + } + value.serialize(buffer, true) + break + case .jsonNull: + if boxed { + buffer.appendInt32(1064139624) + } + + break + case .jsonNumber(let value): + if boxed { + buffer.appendInt32(736157604) + } + serializeDouble(value, buffer: buffer, boxed: false) + break + case .jsonObject(let value): + if boxed { + buffer.appendInt32(-1715350371) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(value.count)) + for item in value { item.serialize(buffer, true) } break + case .jsonString(let value): + if boxed { + buffer.appendInt32(-1222740358) + } + serializeString(value, buffer: buffer, boxed: false) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .langPackDifference(let langCode, let fromVersion, let version, let strings): - return ("langPackDifference", [("langCode", langCode as Any), ("fromVersion", fromVersion as Any), ("version", version as Any), ("strings", strings as Any)]) + case .jsonArray(let value): + return ("jsonArray", [("value", value as Any)]) + case .jsonBool(let value): + return ("jsonBool", [("value", value as Any)]) + case .jsonNull: + return ("jsonNull", []) + case .jsonNumber(let value): + return ("jsonNumber", [("value", value as Any)]) + case .jsonObject(let value): + return ("jsonObject", [("value", value as Any)]) + case .jsonString(let value): + return ("jsonString", [("value", value as Any)]) } } - public static func parse_langPackDifference(_ reader: BufferReader) -> LangPackDifference? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Api.LangPackString]? + public static func parse_jsonArray(_ reader: BufferReader) -> JSONValue? { + var _1: [Api.JSONValue]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.JSONValue.self) } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.LangPackDifference.langPackDifference(langCode: _1!, fromVersion: _2!, version: _3!, strings: _4!) + if _c1 { + return Api.JSONValue.jsonArray(value: _1!) } else { return nil } } - - } -} -public extension Api { - enum LangPackLanguage: TypeConstructorDescription { - case langPackLanguage(flags: Int32, name: String, nativeName: String, langCode: String, baseLangCode: String?, pluralCode: String, stringsCount: Int32, translatedCount: Int32, translationsUrl: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .langPackLanguage(let flags, let name, let nativeName, let langCode, let baseLangCode, let pluralCode, let stringsCount, let translatedCount, let translationsUrl): - if boxed { - buffer.appendInt32(-288727837) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(name, buffer: buffer, boxed: false) - serializeString(nativeName, buffer: buffer, boxed: false) - serializeString(langCode, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(baseLangCode!, buffer: buffer, boxed: false)} - serializeString(pluralCode, buffer: buffer, boxed: false) - serializeInt32(stringsCount, buffer: buffer, boxed: false) - serializeInt32(translatedCount, buffer: buffer, boxed: false) - serializeString(translationsUrl, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .langPackLanguage(let flags, let name, let nativeName, let langCode, let baseLangCode, let pluralCode, let stringsCount, let translatedCount, let translationsUrl): - return ("langPackLanguage", [("flags", flags as Any), ("name", name as Any), ("nativeName", nativeName as Any), ("langCode", langCode as Any), ("baseLangCode", baseLangCode as Any), ("pluralCode", pluralCode as Any), ("stringsCount", stringsCount as Any), ("translatedCount", translatedCount as Any), ("translationsUrl", translationsUrl as Any)]) - } - } - - public static func parse_langPackLanguage(_ reader: BufferReader) -> LangPackLanguage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } - var _6: String? - _6 = parseString(reader) - var _7: Int32? - _7 = reader.readInt32() - var _8: Int32? - _8 = reader.readInt32() - var _9: String? - _9 = parseString(reader) + public static func parse_jsonBool(_ reader: BufferReader) -> JSONValue? { + var _1: Api.Bool? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Bool + } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.LangPackLanguage.langPackLanguage(flags: _1!, name: _2!, nativeName: _3!, langCode: _4!, baseLangCode: _5, pluralCode: _6!, stringsCount: _7!, translatedCount: _8!, translationsUrl: _9!) + if _c1 { + return Api.JSONValue.jsonBool(value: _1!) } else { return nil } } - - } -} -public extension Api { - enum LangPackString: TypeConstructorDescription { - case langPackString(key: String, value: String) - case langPackStringDeleted(key: String) - case langPackStringPluralized(flags: Int32, key: String, zeroValue: String?, oneValue: String?, twoValue: String?, fewValue: String?, manyValue: String?, otherValue: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .langPackString(let key, let value): - if boxed { - buffer.appendInt32(-892239370) - } - serializeString(key, buffer: buffer, boxed: false) - serializeString(value, buffer: buffer, boxed: false) - break - case .langPackStringDeleted(let key): - if boxed { - buffer.appendInt32(695856818) - } - serializeString(key, buffer: buffer, boxed: false) - break - case .langPackStringPluralized(let flags, let key, let zeroValue, let oneValue, let twoValue, let fewValue, let manyValue, let otherValue): - if boxed { - buffer.appendInt32(1816636575) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(key, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(zeroValue!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(oneValue!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(twoValue!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeString(fewValue!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeString(manyValue!, buffer: buffer, boxed: false)} - serializeString(otherValue, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .langPackString(let key, let value): - return ("langPackString", [("key", key as Any), ("value", value as Any)]) - case .langPackStringDeleted(let key): - return ("langPackStringDeleted", [("key", key as Any)]) - case .langPackStringPluralized(let flags, let key, let zeroValue, let oneValue, let twoValue, let fewValue, let manyValue, let otherValue): - return ("langPackStringPluralized", [("flags", flags as Any), ("key", key as Any), ("zeroValue", zeroValue as Any), ("oneValue", oneValue as Any), ("twoValue", twoValue as Any), ("fewValue", fewValue as Any), ("manyValue", manyValue as Any), ("otherValue", otherValue as Any)]) - } - } - - public static func parse_langPackString(_ reader: BufferReader) -> LangPackString? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) + public static func parse_jsonNull(_ reader: BufferReader) -> JSONValue? { + return Api.JSONValue.jsonNull + } + public static func parse_jsonNumber(_ reader: BufferReader) -> JSONValue? { + var _1: Double? + _1 = reader.readDouble() let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.LangPackString.langPackString(key: _1!, value: _2!) + if _c1 { + return Api.JSONValue.jsonNumber(value: _1!) } else { return nil } } - public static func parse_langPackStringDeleted(_ reader: BufferReader) -> LangPackString? { - var _1: String? - _1 = parseString(reader) + public static func parse_jsonObject(_ reader: BufferReader) -> JSONValue? { + var _1: [Api.JSONObjectValue]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.JSONObjectValue.self) + } let _c1 = _1 != nil if _c1 { - return Api.LangPackString.langPackStringDeleted(key: _1!) + return Api.JSONValue.jsonObject(value: _1!) } else { return nil } } - public static func parse_langPackStringPluralized(_ reader: BufferReader) -> LangPackString? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - var _5: String? - if Int(_1!) & Int(1 << 2) != 0 {_5 = parseString(reader) } - var _6: String? - if Int(_1!) & Int(1 << 3) != 0 {_6 = parseString(reader) } - var _7: String? - if Int(_1!) & Int(1 << 4) != 0 {_7 = parseString(reader) } - var _8: String? - _8 = parseString(reader) + public static func parse_jsonString(_ reader: BufferReader) -> JSONValue? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.LangPackString.langPackStringPluralized(flags: _1!, key: _2!, zeroValue: _3, oneValue: _4, twoValue: _5, fewValue: _6, manyValue: _7, otherValue: _8!) + if _c1 { + return Api.JSONValue.jsonString(value: _1!) } else { return nil @@ -311,1651 +251,789 @@ public extension Api { } } public extension Api { - enum MaskCoords: TypeConstructorDescription { - case maskCoords(n: Int32, x: Double, y: Double, zoom: Double) + indirect enum KeyboardButton: TypeConstructorDescription { + case inputKeyboardButtonRequestPeer(flags: Int32, text: String, buttonId: Int32, peerType: Api.RequestPeerType, maxQuantity: Int32) + case inputKeyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, bot: Api.InputUser) + case inputKeyboardButtonUserProfile(text: String, userId: Api.InputUser) + case keyboardButton(text: String) + case keyboardButtonBuy(text: String) + case keyboardButtonCallback(flags: Int32, text: String, data: Buffer) + case keyboardButtonGame(text: String) + case keyboardButtonRequestGeoLocation(text: String) + case keyboardButtonRequestPeer(text: String, buttonId: Int32, peerType: Api.RequestPeerType, maxQuantity: Int32) + case keyboardButtonRequestPhone(text: String) + case keyboardButtonRequestPoll(flags: Int32, quiz: Api.Bool?, text: String) + case keyboardButtonSimpleWebView(text: String, url: String) + case keyboardButtonSwitchInline(flags: Int32, text: String, query: String, peerTypes: [Api.InlineQueryPeerType]?) + case keyboardButtonUrl(text: String, url: String) + case keyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, buttonId: Int32) + case keyboardButtonUserProfile(text: String, userId: Int64) + case keyboardButtonWebView(text: String, url: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .maskCoords(let n, let x, let y, let zoom): + case .inputKeyboardButtonRequestPeer(let flags, let text, let buttonId, let peerType, let maxQuantity): if boxed { - buffer.appendInt32(-1361650766) + buffer.appendInt32(-916050683) } - serializeInt32(n, buffer: buffer, boxed: false) - serializeDouble(x, buffer: buffer, boxed: false) - serializeDouble(y, buffer: buffer, boxed: false) - serializeDouble(zoom, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + serializeInt32(buttonId, buffer: buffer, boxed: false) + peerType.serialize(buffer, true) + serializeInt32(maxQuantity, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .maskCoords(let n, let x, let y, let zoom): - return ("maskCoords", [("n", n as Any), ("x", x as Any), ("y", y as Any), ("zoom", zoom as Any)]) - } - } - - public static func parse_maskCoords(_ reader: BufferReader) -> MaskCoords? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Double? - _2 = reader.readDouble() - var _3: Double? - _3 = reader.readDouble() - var _4: Double? - _4 = reader.readDouble() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MaskCoords.maskCoords(n: _1!, x: _2!, y: _3!, zoom: _4!) - } - else { - return nil - } - } - - } -} -public extension Api { - indirect enum MediaArea: TypeConstructorDescription { - case inputMediaAreaChannelPost(coordinates: Api.MediaAreaCoordinates, channel: Api.InputChannel, msgId: Int32) - case inputMediaAreaVenue(coordinates: Api.MediaAreaCoordinates, queryId: Int64, resultId: String) - case mediaAreaChannelPost(coordinates: Api.MediaAreaCoordinates, channelId: Int64, msgId: Int32) - case mediaAreaGeoPoint(coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint) - case mediaAreaSuggestedReaction(flags: Int32, coordinates: Api.MediaAreaCoordinates, reaction: Api.Reaction) - case mediaAreaVenue(coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputMediaAreaChannelPost(let coordinates, let channel, let msgId): + case .inputKeyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let bot): + if boxed { + buffer.appendInt32(-802258988) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(fwdText!, buffer: buffer, boxed: false)} + serializeString(url, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + break + case .inputKeyboardButtonUserProfile(let text, let userId): + if boxed { + buffer.appendInt32(-376962181) + } + serializeString(text, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + break + case .keyboardButton(let text): + if boxed { + buffer.appendInt32(-1560655744) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonBuy(let text): + if boxed { + buffer.appendInt32(-1344716869) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonCallback(let flags, let text, let data): + if boxed { + buffer.appendInt32(901503851) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + serializeBytes(data, buffer: buffer, boxed: false) + break + case .keyboardButtonGame(let text): + if boxed { + buffer.appendInt32(1358175439) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestGeoLocation(let text): + if boxed { + buffer.appendInt32(-59151553) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestPeer(let text, let buttonId, let peerType, let maxQuantity): + if boxed { + buffer.appendInt32(1406648280) + } + serializeString(text, buffer: buffer, boxed: false) + serializeInt32(buttonId, buffer: buffer, boxed: false) + peerType.serialize(buffer, true) + serializeInt32(maxQuantity, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestPhone(let text): + if boxed { + buffer.appendInt32(-1318425559) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestPoll(let flags, let quiz, let text): if boxed { - buffer.appendInt32(577893055) + buffer.appendInt32(-1144565411) } - coordinates.serialize(buffer, true) - channel.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {quiz!.serialize(buffer, true)} + serializeString(text, buffer: buffer, boxed: false) break - case .inputMediaAreaVenue(let coordinates, let queryId, let resultId): + case .keyboardButtonSimpleWebView(let text, let url): if boxed { - buffer.appendInt32(-1300094593) + buffer.appendInt32(-1598009252) } - coordinates.serialize(buffer, true) - serializeInt64(queryId, buffer: buffer, boxed: false) - serializeString(resultId, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) break - case .mediaAreaChannelPost(let coordinates, let channelId, let msgId): + case .keyboardButtonSwitchInline(let flags, let text, let query, let peerTypes): if boxed { - buffer.appendInt32(1996756655) + buffer.appendInt32(-1816527947) } - coordinates.serialize(buffer, true) - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + serializeString(query, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peerTypes!.count)) + for item in peerTypes! { + item.serialize(buffer, true) + }} break - case .mediaAreaGeoPoint(let coordinates, let geo): + case .keyboardButtonUrl(let text, let url): if boxed { - buffer.appendInt32(-544523486) + buffer.appendInt32(629866245) } - coordinates.serialize(buffer, true) - geo.serialize(buffer, true) + serializeString(text, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) break - case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction): + case .keyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let buttonId): if boxed { - buffer.appendInt32(340088945) + buffer.appendInt32(280464681) } serializeInt32(flags, buffer: buffer, boxed: false) - coordinates.serialize(buffer, true) - reaction.serialize(buffer, true) + serializeString(text, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(fwdText!, buffer: buffer, boxed: false)} + serializeString(url, buffer: buffer, boxed: false) + serializeInt32(buttonId, buffer: buffer, boxed: false) + break + case .keyboardButtonUserProfile(let text, let userId): + if boxed { + buffer.appendInt32(814112961) + } + serializeString(text, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) break - case .mediaAreaVenue(let coordinates, let geo, let title, let address, let provider, let venueId, let venueType): + case .keyboardButtonWebView(let text, let url): if boxed { - buffer.appendInt32(-1098720356) + buffer.appendInt32(326529584) } - coordinates.serialize(buffer, true) - geo.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - serializeString(address, buffer: buffer, boxed: false) - serializeString(provider, buffer: buffer, boxed: false) - serializeString(venueId, buffer: buffer, boxed: false) - serializeString(venueType, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .inputMediaAreaChannelPost(let coordinates, let channel, let msgId): - return ("inputMediaAreaChannelPost", [("coordinates", coordinates as Any), ("channel", channel as Any), ("msgId", msgId as Any)]) - case .inputMediaAreaVenue(let coordinates, let queryId, let resultId): - return ("inputMediaAreaVenue", [("coordinates", coordinates as Any), ("queryId", queryId as Any), ("resultId", resultId as Any)]) - case .mediaAreaChannelPost(let coordinates, let channelId, let msgId): - return ("mediaAreaChannelPost", [("coordinates", coordinates as Any), ("channelId", channelId as Any), ("msgId", msgId as Any)]) - case .mediaAreaGeoPoint(let coordinates, let geo): - return ("mediaAreaGeoPoint", [("coordinates", coordinates as Any), ("geo", geo as Any)]) - case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction): - return ("mediaAreaSuggestedReaction", [("flags", flags as Any), ("coordinates", coordinates as Any), ("reaction", reaction as Any)]) - case .mediaAreaVenue(let coordinates, let geo, let title, let address, let provider, let venueId, let venueType): - return ("mediaAreaVenue", [("coordinates", coordinates as Any), ("geo", geo as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)]) + case .inputKeyboardButtonRequestPeer(let flags, let text, let buttonId, let peerType, let maxQuantity): + return ("inputKeyboardButtonRequestPeer", [("flags", flags as Any), ("text", text as Any), ("buttonId", buttonId as Any), ("peerType", peerType as Any), ("maxQuantity", maxQuantity as Any)]) + case .inputKeyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let bot): + return ("inputKeyboardButtonUrlAuth", [("flags", flags as Any), ("text", text as Any), ("fwdText", fwdText as Any), ("url", url as Any), ("bot", bot as Any)]) + case .inputKeyboardButtonUserProfile(let text, let userId): + return ("inputKeyboardButtonUserProfile", [("text", text as Any), ("userId", userId as Any)]) + case .keyboardButton(let text): + return ("keyboardButton", [("text", text as Any)]) + case .keyboardButtonBuy(let text): + return ("keyboardButtonBuy", [("text", text as Any)]) + case .keyboardButtonCallback(let flags, let text, let data): + return ("keyboardButtonCallback", [("flags", flags as Any), ("text", text as Any), ("data", data as Any)]) + case .keyboardButtonGame(let text): + return ("keyboardButtonGame", [("text", text as Any)]) + case .keyboardButtonRequestGeoLocation(let text): + return ("keyboardButtonRequestGeoLocation", [("text", text as Any)]) + case .keyboardButtonRequestPeer(let text, let buttonId, let peerType, let maxQuantity): + return ("keyboardButtonRequestPeer", [("text", text as Any), ("buttonId", buttonId as Any), ("peerType", peerType as Any), ("maxQuantity", maxQuantity as Any)]) + case .keyboardButtonRequestPhone(let text): + return ("keyboardButtonRequestPhone", [("text", text as Any)]) + case .keyboardButtonRequestPoll(let flags, let quiz, let text): + return ("keyboardButtonRequestPoll", [("flags", flags as Any), ("quiz", quiz as Any), ("text", text as Any)]) + case .keyboardButtonSimpleWebView(let text, let url): + return ("keyboardButtonSimpleWebView", [("text", text as Any), ("url", url as Any)]) + case .keyboardButtonSwitchInline(let flags, let text, let query, let peerTypes): + return ("keyboardButtonSwitchInline", [("flags", flags as Any), ("text", text as Any), ("query", query as Any), ("peerTypes", peerTypes as Any)]) + case .keyboardButtonUrl(let text, let url): + return ("keyboardButtonUrl", [("text", text as Any), ("url", url as Any)]) + case .keyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let buttonId): + return ("keyboardButtonUrlAuth", [("flags", flags as Any), ("text", text as Any), ("fwdText", fwdText as Any), ("url", url as Any), ("buttonId", buttonId as Any)]) + case .keyboardButtonUserProfile(let text, let userId): + return ("keyboardButtonUserProfile", [("text", text as Any), ("userId", userId as Any)]) + case .keyboardButtonWebView(let text, let url): + return ("keyboardButtonWebView", [("text", text as Any), ("url", url as Any)]) } } - public static func parse_inputMediaAreaChannelPost(_ reader: BufferReader) -> MediaArea? { - var _1: Api.MediaAreaCoordinates? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates - } - var _2: Api.InputChannel? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputChannel - } + public static func parse_inputKeyboardButtonRequestPeer(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) var _3: Int32? _3 = reader.readInt32() + var _4: Api.RequestPeerType? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.RequestPeerType + } + var _5: Int32? + _5 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MediaArea.inputMediaAreaChannelPost(coordinates: _1!, channel: _2!, msgId: _3!) + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.KeyboardButton.inputKeyboardButtonRequestPeer(flags: _1!, text: _2!, buttonId: _3!, peerType: _4!, maxQuantity: _5!) } else { return nil } } - public static func parse_inputMediaAreaVenue(_ reader: BufferReader) -> MediaArea? { - var _1: Api.MediaAreaCoordinates? + public static func parse_inputKeyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } + var _4: String? + _4 = parseString(reader) + var _5: Api.InputUser? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates + _5 = Api.parse(reader, signature: signature) as? Api.InputUser } - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MediaArea.inputMediaAreaVenue(coordinates: _1!, queryId: _2!, resultId: _3!) + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.KeyboardButton.inputKeyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, bot: _5!) } else { return nil } } - public static func parse_mediaAreaChannelPost(_ reader: BufferReader) -> MediaArea? { - var _1: Api.MediaAreaCoordinates? + public static func parse_inputKeyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + var _2: Api.InputUser? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates + _2 = Api.parse(reader, signature: signature) as? Api.InputUser } - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MediaArea.mediaAreaChannelPost(coordinates: _1!, channelId: _2!, msgId: _3!) + if _c1 && _c2 { + return Api.KeyboardButton.inputKeyboardButtonUserProfile(text: _1!, userId: _2!) } else { return nil } } - public static func parse_mediaAreaGeoPoint(_ reader: BufferReader) -> MediaArea? { - var _1: Api.MediaAreaCoordinates? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates + public static func parse_keyboardButton(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.KeyboardButton.keyboardButton(text: _1!) } - var _2: Api.GeoPoint? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint + else { + return nil } + } + public static func parse_keyboardButtonBuy(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MediaArea.mediaAreaGeoPoint(coordinates: _1!, geo: _2!) + if _c1 { + return Api.KeyboardButton.keyboardButtonBuy(text: _1!) } else { return nil } } - public static func parse_mediaAreaSuggestedReaction(_ reader: BufferReader) -> MediaArea? { + public static func parse_keyboardButtonCallback(_ reader: BufferReader) -> KeyboardButton? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.MediaAreaCoordinates? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates - } - var _3: Api.Reaction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Reaction - } + var _2: String? + _2 = parseString(reader) + var _3: Buffer? + _3 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.MediaArea.mediaAreaSuggestedReaction(flags: _1!, coordinates: _2!, reaction: _3!) + return Api.KeyboardButton.keyboardButtonCallback(flags: _1!, text: _2!, data: _3!) } else { return nil } } - public static func parse_mediaAreaVenue(_ reader: BufferReader) -> MediaArea? { - var _1: Api.MediaAreaCoordinates? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates - } - var _2: Api.GeoPoint? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint - } - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - var _7: String? - _7 = parseString(reader) + public static func parse_keyboardButtonGame(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MediaArea.mediaAreaVenue(coordinates: _1!, geo: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!, venueType: _7!) + if _c1 { + return Api.KeyboardButton.keyboardButtonGame(text: _1!) } else { return nil } } - - } -} -public extension Api { - enum MediaAreaCoordinates: TypeConstructorDescription { - case mediaAreaCoordinates(x: Double, y: Double, w: Double, h: Double, rotation: Double) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .mediaAreaCoordinates(let x, let y, let w, let h, let rotation): - if boxed { - buffer.appendInt32(64088654) - } - serializeDouble(x, buffer: buffer, boxed: false) - serializeDouble(y, buffer: buffer, boxed: false) - serializeDouble(w, buffer: buffer, boxed: false) - serializeDouble(h, buffer: buffer, boxed: false) - serializeDouble(rotation, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .mediaAreaCoordinates(let x, let y, let w, let h, let rotation): - return ("mediaAreaCoordinates", [("x", x as Any), ("y", y as Any), ("w", w as Any), ("h", h as Any), ("rotation", rotation as Any)]) - } - } - - public static func parse_mediaAreaCoordinates(_ reader: BufferReader) -> MediaAreaCoordinates? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() - var _3: Double? - _3 = reader.readDouble() - var _4: Double? - _4 = reader.readDouble() - var _5: Double? - _5 = reader.readDouble() + public static func parse_keyboardButtonRequestGeoLocation(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MediaAreaCoordinates.mediaAreaCoordinates(x: _1!, y: _2!, w: _3!, h: _4!, rotation: _5!) + if _c1 { + return Api.KeyboardButton.keyboardButtonRequestGeoLocation(text: _1!) } else { return nil } } - - } -} -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?) - 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): - if boxed { - buffer.appendInt32(592953125) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(flags2, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 29) != 0 {serializeInt32(fromBoostsApplied!, buffer: buffer, boxed: false)} - peerId.serialize(buffer, true) - if Int(flags) & Int(1 << 28) != 0 {savedPeerId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {serializeInt64(viaBotId!, buffer: buffer, boxed: false)} - if Int(flags2) & Int(1 << 0) != 0 {serializeInt64(viaBusinessBotId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} - serializeInt32(date, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 6) != 0 {replyMarkup!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 23) != 0 {replies!.serialize(buffer, true)} - if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 20) != 0 {reactions!.serialize(buffer, true)} - if Int(flags) & Int(1 << 22) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(restrictionReason!.count)) - for item in restrictionReason! { - item.serialize(buffer, true) - }} - 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)} - break - case .messageEmpty(let flags, let id, let peerId): - if boxed { - buffer.appendInt32(-1868117372) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {peerId!.serialize(buffer, true)} - break - case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let ttlPeriod): - if boxed { - buffer.appendInt32(721967202) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} - peerId.serialize(buffer, true) - if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} - serializeInt32(date, buffer: buffer, boxed: false) - action.serialize(buffer, true) - if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} - break - } - } - - 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): - 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)]) - 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): - return ("messageService", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("peerId", peerId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("action", action as Any), ("ttlPeriod", ttlPeriod as Any)]) - } - } - - public static func parse_message(_ reader: BufferReader) -> Message? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_keyboardButtonRequestPeer(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) var _2: Int32? _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Peer? - if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _5: Int32? - if Int(_1!) & Int(1 << 29) != 0 {_5 = reader.readInt32() } - var _6: Api.Peer? + var _3: Api.RequestPeerType? if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Peer + _3 = Api.parse(reader, signature: signature) as? Api.RequestPeerType } - var _7: Api.Peer? - if Int(_1!) & Int(1 << 28) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _8: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } } - var _9: Int64? - if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt64() } - var _10: Int64? - if Int(_2!) & Int(1 << 0) != 0 {_10 = reader.readInt64() } - var _11: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } } - var _12: Int32? - _12 = reader.readInt32() - var _13: String? - _13 = parseString(reader) - var _14: Api.MessageMedia? - if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _14 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } } - var _15: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup - } } - var _16: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - var _17: Int32? - if Int(_1!) & Int(1 << 10) != 0 {_17 = reader.readInt32() } - var _18: Int32? - if Int(_1!) & Int(1 << 10) != 0 {_18 = reader.readInt32() } - var _19: Api.MessageReplies? - if Int(_1!) & Int(1 << 23) != 0 {if let signature = reader.readInt32() { - _19 = Api.parse(reader, signature: signature) as? Api.MessageReplies - } } - var _20: Int32? - if Int(_1!) & Int(1 << 15) != 0 {_20 = reader.readInt32() } - var _21: String? - if Int(_1!) & Int(1 << 16) != 0 {_21 = parseString(reader) } - var _22: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_22 = reader.readInt64() } - var _23: Api.MessageReactions? - if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() { - _23 = Api.parse(reader, signature: signature) as? Api.MessageReactions - } } - var _24: [Api.RestrictionReason]? - if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() { - _24 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) - } } - var _25: Int32? - if Int(_1!) & Int(1 << 25) != 0 {_25 = reader.readInt32() } - var _26: Int32? - if Int(_1!) & Int(1 << 30) != 0 {_26 = reader.readInt32() } + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 29) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 28) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 11) == 0) || _9 != nil - let _c10 = (Int(_2!) & Int(1 << 0) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = _12 != nil - let _c13 = _13 != nil - let _c14 = (Int(_1!) & Int(1 << 9) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 6) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 7) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 10) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 23) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 15) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 16) == 0) || _21 != nil - let _c22 = (Int(_1!) & Int(1 << 17) == 0) || _22 != nil - let _c23 = (Int(_1!) & Int(1 << 20) == 0) || _23 != nil - let _c24 = (Int(_1!) & Int(1 << 22) == 0) || _24 != nil - let _c25 = (Int(_1!) & Int(1 << 25) == 0) || _25 != nil - let _c26 = (Int(_1!) & Int(1 << 30) == 0) || _26 != 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 { - 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) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.KeyboardButton.keyboardButtonRequestPeer(text: _1!, buttonId: _2!, peerType: _3!, maxQuantity: _4!) + } + else { + return nil + } + } + public static func parse_keyboardButtonRequestPhone(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.KeyboardButton.keyboardButtonRequestPhone(text: _1!) } else { return nil } } - public static func parse_messageEmpty(_ reader: BufferReader) -> Message? { + public static func parse_keyboardButtonRequestPoll(_ reader: BufferReader) -> KeyboardButton? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Peer? + var _2: Api.Bool? if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer + _2 = Api.parse(reader, signature: signature) as? Api.Bool } } + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Message.messageEmpty(flags: _1!, id: _2!, peerId: _3) - } - else { - return nil - } - } - public static func parse_messageService(_ reader: BufferReader) -> Message? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Peer? - if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _5: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } } - var _6: Int32? - _6 = reader.readInt32() - var _7: Api.MessageAction? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.MessageAction - } - var _8: Int32? - if Int(_1!) & Int(1 << 25) != 0 {_8 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 25) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, replyTo: _5, date: _6!, action: _7!, ttlPeriod: _8) - } - else { - return nil - } - } - - } -} -public extension Api { - enum MessageAction: TypeConstructorDescription { - case messageActionBoostApply(boosts: Int32) - case messageActionBotAllowed(flags: Int32, domain: String?, app: Api.BotApp?) - case messageActionChannelCreate(title: String) - case messageActionChannelMigrateFrom(title: String, chatId: Int64) - case messageActionChatAddUser(users: [Int64]) - case messageActionChatCreate(title: String, users: [Int64]) - case messageActionChatDeletePhoto - case messageActionChatDeleteUser(userId: Int64) - case messageActionChatEditPhoto(photo: Api.Photo) - case messageActionChatEditTitle(title: String) - case messageActionChatJoinedByLink(inviterId: Int64) - case messageActionChatJoinedByRequest - case messageActionChatMigrateTo(channelId: Int64) - case messageActionContactSignUp - case messageActionCustomAction(message: String) - case messageActionEmpty - case messageActionGameScore(gameId: Int64, score: Int32) - case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32) - case messageActionGiftCode(flags: Int32, boostPeer: Api.Peer?, months: Int32, slug: String, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?) - case messageActionGiftPremium(flags: Int32, currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?) - case messageActionGiveawayLaunch - case messageActionGiveawayResults(winnersCount: Int32, unclaimedCount: Int32) - case messageActionGroupCall(flags: Int32, call: Api.InputGroupCall, duration: Int32?) - case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32) - case messageActionHistoryClear - case messageActionInviteToGroupCall(call: Api.InputGroupCall, users: [Int64]) - case messageActionPaymentSent(flags: Int32, currency: String, totalAmount: Int64, invoiceSlug: String?) - case messageActionPaymentSentMe(flags: Int32, currency: String, totalAmount: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, charge: Api.PaymentCharge) - case messageActionPhoneCall(flags: Int32, callId: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?) - case messageActionPinMessage - case messageActionRequestedPeer(buttonId: Int32, peers: [Api.Peer]) - case messageActionRequestedPeerSentMe(buttonId: Int32, peers: [Api.RequestedPeer]) - case messageActionScreenshotTaken - case messageActionSecureValuesSent(types: [Api.SecureValueType]) - case messageActionSecureValuesSentMe(values: [Api.SecureValue], credentials: Api.SecureCredentialsEncrypted) - case messageActionSetChatTheme(emoticon: String) - case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper) - case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) - case messageActionSuggestProfilePhoto(photo: Api.Photo) - case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) - case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) - case messageActionWebViewDataSent(text: String) - case messageActionWebViewDataSentMe(text: String, data: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageActionBoostApply(let boosts): - if boxed { - buffer.appendInt32(-872240531) - } - serializeInt32(boosts, buffer: buffer, boxed: false) - break - case .messageActionBotAllowed(let flags, let domain, let app): - if boxed { - buffer.appendInt32(-988359047) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(domain!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {app!.serialize(buffer, true)} - break - case .messageActionChannelCreate(let title): - if boxed { - buffer.appendInt32(-1781355374) - } - serializeString(title, buffer: buffer, boxed: false) - break - case .messageActionChannelMigrateFrom(let title, let chatId): - if boxed { - buffer.appendInt32(-365344535) - } - serializeString(title, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) - break - case .messageActionChatAddUser(let users): - if boxed { - buffer.appendInt32(365886720) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .messageActionChatCreate(let title, let users): - if boxed { - buffer.appendInt32(-1119368275) - } - serializeString(title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .messageActionChatDeletePhoto: - if boxed { - buffer.appendInt32(-1780220945) - } - - break - case .messageActionChatDeleteUser(let userId): - if boxed { - buffer.appendInt32(-1539362612) - } - serializeInt64(userId, buffer: buffer, boxed: false) - break - case .messageActionChatEditPhoto(let photo): - if boxed { - buffer.appendInt32(2144015272) - } - photo.serialize(buffer, true) - break - case .messageActionChatEditTitle(let title): - if boxed { - buffer.appendInt32(-1247687078) - } - serializeString(title, buffer: buffer, boxed: false) - break - case .messageActionChatJoinedByLink(let inviterId): - if boxed { - buffer.appendInt32(51520707) - } - serializeInt64(inviterId, buffer: buffer, boxed: false) - break - case .messageActionChatJoinedByRequest: - if boxed { - buffer.appendInt32(-339958837) - } - - break - case .messageActionChatMigrateTo(let channelId): - if boxed { - buffer.appendInt32(-519864430) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - break - case .messageActionContactSignUp: - if boxed { - buffer.appendInt32(-202219658) - } - - break - case .messageActionCustomAction(let message): - if boxed { - buffer.appendInt32(-85549226) - } - serializeString(message, buffer: buffer, boxed: false) - break - case .messageActionEmpty: - if boxed { - buffer.appendInt32(-1230047312) - } - - break - case .messageActionGameScore(let gameId, let score): - if boxed { - buffer.appendInt32(-1834538890) - } - serializeInt64(gameId, buffer: buffer, boxed: false) - serializeInt32(score, buffer: buffer, boxed: false) - break - case .messageActionGeoProximityReached(let fromId, let toId, let distance): - if boxed { - buffer.appendInt32(-1730095465) - } - fromId.serialize(buffer, true) - toId.serialize(buffer, true) - serializeInt32(distance, buffer: buffer, boxed: false) - break - case .messageActionGiftCode(let flags, let boostPeer, let months, let slug, let currency, let amount, let cryptoCurrency, let cryptoAmount): - if boxed { - buffer.appendInt32(1737240073) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {boostPeer!.serialize(buffer, true)} - serializeInt32(months, buffer: buffer, boxed: false) - serializeString(slug, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeString(currency!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt64(amount!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)} - break - case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount): - if boxed { - buffer.appendInt32(-935499028) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(amount, buffer: buffer, boxed: false) - serializeInt32(months, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)} - break - case .messageActionGiveawayLaunch: - if boxed { - buffer.appendInt32(858499565) - } - - break - case .messageActionGiveawayResults(let winnersCount, let unclaimedCount): - if boxed { - buffer.appendInt32(715107781) - } - serializeInt32(winnersCount, buffer: buffer, boxed: false) - serializeInt32(unclaimedCount, buffer: buffer, boxed: false) - break - case .messageActionGroupCall(let flags, let call, let duration): - if boxed { - buffer.appendInt32(2047704898) - } - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} - break - case .messageActionGroupCallScheduled(let call, let scheduleDate): - if boxed { - buffer.appendInt32(-1281329567) - } - call.serialize(buffer, true) - serializeInt32(scheduleDate, buffer: buffer, boxed: false) - break - case .messageActionHistoryClear: - if boxed { - buffer.appendInt32(-1615153660) - } - - break - case .messageActionInviteToGroupCall(let call, let users): - if boxed { - buffer.appendInt32(1345295095) - } - call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .messageActionPaymentSent(let flags, let currency, let totalAmount, let invoiceSlug): - if boxed { - buffer.appendInt32(-1776926890) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(totalAmount, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(invoiceSlug!, buffer: buffer, boxed: false)} - break - case .messageActionPaymentSentMe(let flags, let currency, let totalAmount, let payload, let info, let shippingOptionId, let charge): - if boxed { - buffer.appendInt32(-1892568281) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(totalAmount, buffer: buffer, boxed: false) - serializeBytes(payload, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(shippingOptionId!, buffer: buffer, boxed: false)} - charge.serialize(buffer, true) - break - case .messageActionPhoneCall(let flags, let callId, let reason, let duration): - if boxed { - buffer.appendInt32(-2132731265) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(callId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {reason!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} - break - case .messageActionPinMessage: - if boxed { - buffer.appendInt32(-1799538451) - } - - break - case .messageActionRequestedPeer(let buttonId, let peers): - if boxed { - buffer.appendInt32(827428507) - } - serializeInt32(buttonId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - break - case .messageActionRequestedPeerSentMe(let buttonId, let peers): - if boxed { - buffer.appendInt32(-1816979384) - } - serializeInt32(buttonId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - break - case .messageActionScreenshotTaken: - if boxed { - buffer.appendInt32(1200788123) - } - - break - case .messageActionSecureValuesSent(let types): - if boxed { - buffer.appendInt32(-648257196) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(types.count)) - for item in types { - item.serialize(buffer, true) - } - break - case .messageActionSecureValuesSentMe(let values, let credentials): - if boxed { - buffer.appendInt32(455635795) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(values.count)) - for item in values { - item.serialize(buffer, true) - } - credentials.serialize(buffer, true) - break - case .messageActionSetChatTheme(let emoticon): - if boxed { - buffer.appendInt32(-1434950843) - } - serializeString(emoticon, buffer: buffer, boxed: false) - break - case .messageActionSetChatWallPaper(let flags, let wallpaper): - if boxed { - buffer.appendInt32(1348510708) - } - serializeInt32(flags, buffer: buffer, boxed: false) - wallpaper.serialize(buffer, true) - break - case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): - if boxed { - buffer.appendInt32(1007897979) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(period, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)} - break - case .messageActionSuggestProfilePhoto(let photo): - if boxed { - buffer.appendInt32(1474192222) - } - photo.serialize(buffer, true) - break - case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): - if boxed { - buffer.appendInt32(228168278) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeInt32(iconColor, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} - break - case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden): - if boxed { - buffer.appendInt32(-1064024032) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {hidden!.serialize(buffer, true)} - break - case .messageActionWebViewDataSent(let text): - if boxed { - buffer.appendInt32(-1262252875) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .messageActionWebViewDataSentMe(let text, let data): - if boxed { - buffer.appendInt32(1205698681) - } - serializeString(text, buffer: buffer, boxed: false) - serializeString(data, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageActionBoostApply(let boosts): - return ("messageActionBoostApply", [("boosts", boosts as Any)]) - case .messageActionBotAllowed(let flags, let domain, let app): - return ("messageActionBotAllowed", [("flags", flags as Any), ("domain", domain as Any), ("app", app as Any)]) - case .messageActionChannelCreate(let title): - return ("messageActionChannelCreate", [("title", title as Any)]) - case .messageActionChannelMigrateFrom(let title, let chatId): - return ("messageActionChannelMigrateFrom", [("title", title as Any), ("chatId", chatId as Any)]) - case .messageActionChatAddUser(let users): - return ("messageActionChatAddUser", [("users", users as Any)]) - case .messageActionChatCreate(let title, let users): - return ("messageActionChatCreate", [("title", title as Any), ("users", users as Any)]) - case .messageActionChatDeletePhoto: - return ("messageActionChatDeletePhoto", []) - case .messageActionChatDeleteUser(let userId): - return ("messageActionChatDeleteUser", [("userId", userId as Any)]) - case .messageActionChatEditPhoto(let photo): - return ("messageActionChatEditPhoto", [("photo", photo as Any)]) - case .messageActionChatEditTitle(let title): - return ("messageActionChatEditTitle", [("title", title as Any)]) - case .messageActionChatJoinedByLink(let inviterId): - return ("messageActionChatJoinedByLink", [("inviterId", inviterId as Any)]) - case .messageActionChatJoinedByRequest: - return ("messageActionChatJoinedByRequest", []) - case .messageActionChatMigrateTo(let channelId): - return ("messageActionChatMigrateTo", [("channelId", channelId as Any)]) - case .messageActionContactSignUp: - return ("messageActionContactSignUp", []) - case .messageActionCustomAction(let message): - return ("messageActionCustomAction", [("message", message as Any)]) - case .messageActionEmpty: - return ("messageActionEmpty", []) - case .messageActionGameScore(let gameId, let score): - return ("messageActionGameScore", [("gameId", gameId as Any), ("score", score as Any)]) - case .messageActionGeoProximityReached(let fromId, let toId, let distance): - return ("messageActionGeoProximityReached", [("fromId", fromId as Any), ("toId", toId as Any), ("distance", distance as Any)]) - case .messageActionGiftCode(let flags, let boostPeer, let months, let slug, let currency, let amount, let cryptoCurrency, let cryptoAmount): - return ("messageActionGiftCode", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("months", months as Any), ("slug", slug as Any), ("currency", currency as Any), ("amount", amount as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any)]) - case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount): - return ("messageActionGiftPremium", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("months", months as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any)]) - case .messageActionGiveawayLaunch: - return ("messageActionGiveawayLaunch", []) - case .messageActionGiveawayResults(let winnersCount, let unclaimedCount): - return ("messageActionGiveawayResults", [("winnersCount", winnersCount as Any), ("unclaimedCount", unclaimedCount as Any)]) - case .messageActionGroupCall(let flags, let call, let duration): - return ("messageActionGroupCall", [("flags", flags as Any), ("call", call as Any), ("duration", duration as Any)]) - case .messageActionGroupCallScheduled(let call, let scheduleDate): - return ("messageActionGroupCallScheduled", [("call", call as Any), ("scheduleDate", scheduleDate as Any)]) - case .messageActionHistoryClear: - return ("messageActionHistoryClear", []) - case .messageActionInviteToGroupCall(let call, let users): - return ("messageActionInviteToGroupCall", [("call", call as Any), ("users", users as Any)]) - case .messageActionPaymentSent(let flags, let currency, let totalAmount, let invoiceSlug): - return ("messageActionPaymentSent", [("flags", flags as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("invoiceSlug", invoiceSlug as Any)]) - case .messageActionPaymentSentMe(let flags, let currency, let totalAmount, let payload, let info, let shippingOptionId, let charge): - return ("messageActionPaymentSentMe", [("flags", flags as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("payload", payload as Any), ("info", info as Any), ("shippingOptionId", shippingOptionId as Any), ("charge", charge as Any)]) - case .messageActionPhoneCall(let flags, let callId, let reason, let duration): - return ("messageActionPhoneCall", [("flags", flags as Any), ("callId", callId as Any), ("reason", reason as Any), ("duration", duration as Any)]) - case .messageActionPinMessage: - return ("messageActionPinMessage", []) - case .messageActionRequestedPeer(let buttonId, let peers): - return ("messageActionRequestedPeer", [("buttonId", buttonId as Any), ("peers", peers as Any)]) - case .messageActionRequestedPeerSentMe(let buttonId, let peers): - return ("messageActionRequestedPeerSentMe", [("buttonId", buttonId as Any), ("peers", peers as Any)]) - case .messageActionScreenshotTaken: - return ("messageActionScreenshotTaken", []) - case .messageActionSecureValuesSent(let types): - return ("messageActionSecureValuesSent", [("types", types as Any)]) - case .messageActionSecureValuesSentMe(let values, let credentials): - return ("messageActionSecureValuesSentMe", [("values", values as Any), ("credentials", credentials as Any)]) - case .messageActionSetChatTheme(let emoticon): - return ("messageActionSetChatTheme", [("emoticon", emoticon as Any)]) - case .messageActionSetChatWallPaper(let flags, let wallpaper): - return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)]) - case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): - return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) - case .messageActionSuggestProfilePhoto(let photo): - return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) - case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): - return ("messageActionTopicCreate", [("flags", flags as Any), ("title", title as Any), ("iconColor", iconColor as Any), ("iconEmojiId", iconEmojiId as Any)]) - case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden): - return ("messageActionTopicEdit", [("flags", flags as Any), ("title", title as Any), ("iconEmojiId", iconEmojiId as Any), ("closed", closed as Any), ("hidden", hidden as Any)]) - case .messageActionWebViewDataSent(let text): - return ("messageActionWebViewDataSent", [("text", text as Any)]) - case .messageActionWebViewDataSentMe(let text, let data): - return ("messageActionWebViewDataSentMe", [("text", text as Any), ("data", data as Any)]) - } - } - - public static func parse_messageActionBoostApply(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionBoostApply(boosts: _1!) - } - else { - return nil - } - } - public static func parse_messageActionBotAllowed(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - var _3: Api.BotApp? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.BotApp - } } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionBotAllowed(flags: _1!, domain: _2, app: _3) - } - else { - return nil - } - } - public static func parse_messageActionChannelCreate(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChannelCreate(title: _1!) - } - else { - return nil - } - } - public static func parse_messageActionChannelMigrateFrom(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionChannelMigrateFrom(title: _1!, chatId: _2!) - } - else { - return nil - } - } - public static func parse_messageActionChatAddUser(_ reader: BufferReader) -> MessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatAddUser(users: _1!) - } - else { - return nil - } - } - public static func parse_messageActionChatCreate(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - var _2: [Int64]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionChatCreate(title: _1!, users: _2!) - } - else { - return nil - } - } - public static func parse_messageActionChatDeletePhoto(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionChatDeletePhoto - } - public static func parse_messageActionChatDeleteUser(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatDeleteUser(userId: _1!) - } - else { - return nil - } - } - public static func parse_messageActionChatEditPhoto(_ reader: BufferReader) -> MessageAction? { - var _1: Api.Photo? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Photo - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatEditPhoto(photo: _1!) - } - else { - return nil - } - } - public static func parse_messageActionChatEditTitle(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatEditTitle(title: _1!) - } - else { - return nil - } - } - public static func parse_messageActionChatJoinedByLink(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatJoinedByLink(inviterId: _1!) - } - else { - return nil - } - } - public static func parse_messageActionChatJoinedByRequest(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionChatJoinedByRequest - } - public static func parse_messageActionChatMigrateTo(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatMigrateTo(channelId: _1!) + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.KeyboardButton.keyboardButtonRequestPoll(flags: _1!, quiz: _2, text: _3!) } else { return nil } } - public static func parse_messageActionContactSignUp(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionContactSignUp - } - public static func parse_messageActionCustomAction(_ reader: BufferReader) -> MessageAction? { + public static func parse_keyboardButtonSimpleWebView(_ reader: BufferReader) -> KeyboardButton? { var _1: String? _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionCustomAction(message: _1!) - } - else { - return nil - } - } - public static func parse_messageActionEmpty(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionEmpty - } - public static func parse_messageActionGameScore(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageAction.messageActionGameScore(gameId: _1!, score: _2!) - } - else { - return nil - } - } - public static func parse_messageActionGeoProximityReached(_ reader: BufferReader) -> MessageAction? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionGeoProximityReached(fromId: _1!, toId: _2!, distance: _3!) - } - else { - return nil - } - } - public static func parse_messageActionGiftCode(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: String? - if Int(_1!) & Int(1 << 2) != 0 {_5 = parseString(reader) } - var _6: Int64? - if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt64() } - var _7: String? - if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) } - var _8: Int64? - if Int(_1!) & Int(1 << 3) != 0 {_8 = reader.readInt64() } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.MessageAction.messageActionGiftCode(flags: _1!, boostPeer: _2, months: _3!, slug: _4!, currency: _5, amount: _6, cryptoCurrency: _7, cryptoAmount: _8) + return Api.KeyboardButton.keyboardButtonSimpleWebView(text: _1!, url: _2!) } else { return nil } } - public static func parse_messageActionGiftPremium(_ reader: BufferReader) -> MessageAction? { + public static func parse_keyboardButtonSwitchInline(_ reader: BufferReader) -> KeyboardButton? { var _1: Int32? _1 = reader.readInt32() var _2: String? _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: String? - if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) } - var _6: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt64() } + var _3: String? + _3 = parseString(reader) + var _4: [Api.InlineQueryPeerType]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InlineQueryPeerType.self) + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageAction.messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, months: _4!, cryptoCurrency: _5, cryptoAmount: _6) - } - else { - return nil - } - } - public static func parse_messageActionGiveawayLaunch(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionGiveawayLaunch - } - public static func parse_messageActionGiveawayResults(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionGiveawayResults(winnersCount: _1!, unclaimedCount: _2!) - } - else { - return nil - } - } - public static func parse_messageActionGroupCall(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputGroupCall? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionGroupCall(flags: _1!, call: _2!, duration: _3) - } - else { - return nil - } - } - public static func parse_messageActionGroupCallScheduled(_ reader: BufferReader) -> MessageAction? { - var _1: Api.InputGroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionGroupCallScheduled(call: _1!, scheduleDate: _2!) - } - else { - return nil - } - } - public static func parse_messageActionHistoryClear(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionHistoryClear - } - public static func parse_messageActionInviteToGroupCall(_ reader: BufferReader) -> MessageAction? { - var _1: Api.InputGroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _2: [Int64]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionInviteToGroupCall(call: _1!, users: _2!) + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.KeyboardButton.keyboardButtonSwitchInline(flags: _1!, text: _2!, query: _3!, peerTypes: _4) } else { return nil } } - public static func parse_messageActionPaymentSent(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_keyboardButtonUrl(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) var _2: String? _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionPaymentSent(flags: _1!, currency: _2!, totalAmount: _3!, invoiceSlug: _4) + if _c1 && _c2 { + return Api.KeyboardButton.keyboardButtonUrl(text: _1!, url: _2!) } else { return nil } } - public static func parse_messageActionPaymentSentMe(_ reader: BufferReader) -> MessageAction? { + public static func parse_keyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { var _1: Int32? _1 = reader.readInt32() var _2: String? _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: Buffer? - _4 = parseBytes(reader) - var _5: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } } - var _6: String? - if Int(_1!) & Int(1 << 1) != 0 {_6 = parseString(reader) } - var _7: Api.PaymentCharge? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PaymentCharge - } + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageAction.messageActionPaymentSentMe(flags: _1!, currency: _2!, totalAmount: _3!, payload: _4!, info: _5, shippingOptionId: _6, charge: _7!) + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.KeyboardButton.keyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, buttonId: _5!) } else { return nil } } - public static func parse_messageActionPhoneCall(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_keyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) var _2: Int64? _2 = reader.readInt64() - var _3: Api.PhoneCallDiscardReason? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.PhoneCallDiscardReason - } } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionPhoneCall(flags: _1!, callId: _2!, reason: _3, duration: _4) - } - else { - return nil - } - } - public static func parse_messageActionPinMessage(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionPinMessage - } - public static func parse_messageActionRequestedPeer(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Peer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageAction.messageActionRequestedPeer(buttonId: _1!, peers: _2!) + return Api.KeyboardButton.keyboardButtonUserProfile(text: _1!, userId: _2!) } else { return nil } } - public static func parse_messageActionRequestedPeerSentMe(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.RequestedPeer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RequestedPeer.self) - } + public static func parse_keyboardButtonWebView(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageAction.messageActionRequestedPeerSentMe(buttonId: _1!, peers: _2!) - } - else { - return nil - } - } - public static func parse_messageActionScreenshotTaken(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionScreenshotTaken - } - public static func parse_messageActionSecureValuesSent(_ reader: BufferReader) -> MessageAction? { - var _1: [Api.SecureValueType]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValueType.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSecureValuesSent(types: _1!) + return Api.KeyboardButton.keyboardButtonWebView(text: _1!, url: _2!) } else { return nil } } - public static func parse_messageActionSecureValuesSentMe(_ reader: BufferReader) -> MessageAction? { - var _1: [Api.SecureValue]? + + } +} +public extension Api { + enum KeyboardButtonRow: TypeConstructorDescription { + case keyboardButtonRow(buttons: [Api.KeyboardButton]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .keyboardButtonRow(let buttons): + if boxed { + buffer.appendInt32(2002815875) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(buttons.count)) + for item in buttons { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .keyboardButtonRow(let buttons): + return ("keyboardButtonRow", [("buttons", buttons as Any)]) + } + } + + public static func parse_keyboardButtonRow(_ reader: BufferReader) -> KeyboardButtonRow? { + var _1: [Api.KeyboardButton]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) - } - var _2: Api.SecureCredentialsEncrypted? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.SecureCredentialsEncrypted + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButton.self) } let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionSecureValuesSentMe(values: _1!, credentials: _2!) + if _c1 { + return Api.KeyboardButtonRow.keyboardButtonRow(buttons: _1!) } else { return nil } } - public static func parse_messageActionSetChatTheme(_ reader: BufferReader) -> MessageAction? { + + } +} +public extension Api { + enum LabeledPrice: TypeConstructorDescription { + case labeledPrice(label: String, amount: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .labeledPrice(let label, let amount): + if boxed { + buffer.appendInt32(-886477832) + } + serializeString(label, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .labeledPrice(let label, let amount): + return ("labeledPrice", [("label", label as Any), ("amount", amount as Any)]) + } + } + + public static func parse_labeledPrice(_ reader: BufferReader) -> LabeledPrice? { var _1: String? _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSetChatTheme(emoticon: _1!) - } - else { - return nil - } - } - public static func parse_messageActionSetChatWallPaper(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.WallPaper? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.WallPaper - } + var _2: Int64? + _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageAction.messageActionSetChatWallPaper(flags: _1!, wallpaper: _2!) + return Api.LabeledPrice.labeledPrice(label: _1!, amount: _2!) } else { return nil } } - public static func parse_messageActionSetMessagesTTL(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() + + } +} +public extension Api { + enum LangPackDifference: TypeConstructorDescription { + case langPackDifference(langCode: String, fromVersion: Int32, version: Int32, strings: [Api.LangPackString]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .langPackDifference(let langCode, let fromVersion, let version, let strings): + if boxed { + buffer.appendInt32(-209337866) + } + serializeString(langCode, buffer: buffer, boxed: false) + serializeInt32(fromVersion, buffer: buffer, boxed: false) + serializeInt32(version, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(strings.count)) + for item in strings { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .langPackDifference(let langCode, let fromVersion, let version, let strings): + return ("langPackDifference", [("langCode", langCode as Any), ("fromVersion", fromVersion as Any), ("version", version as Any), ("strings", strings as Any)]) + } + } + + public static func parse_langPackDifference(_ reader: BufferReader) -> LangPackDifference? { + var _1: String? + _1 = parseString(reader) var _2: Int32? _2 = reader.readInt32() - var _3: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt64() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionSetMessagesTTL(flags: _1!, period: _2!, autoSettingFrom: _3) - } - else { - return nil - } - } - public static func parse_messageActionSuggestProfilePhoto(_ reader: BufferReader) -> MessageAction? { - var _1: Api.Photo? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Photo + var _3: Int32? + _3 = reader.readInt32() + var _4: [Api.LangPackString]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self) } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSuggestProfilePhoto(photo: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.LangPackDifference.langPackDifference(langCode: _1!, fromVersion: _2!, version: _3!, strings: _4!) } else { return nil } } - public static func parse_messageActionTopicCreate(_ reader: BufferReader) -> MessageAction? { + + } +} +public extension Api { + enum LangPackLanguage: TypeConstructorDescription { + case langPackLanguage(flags: Int32, name: String, nativeName: String, langCode: String, baseLangCode: String?, pluralCode: String, stringsCount: Int32, translatedCount: Int32, translationsUrl: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .langPackLanguage(let flags, let name, let nativeName, let langCode, let baseLangCode, let pluralCode, let stringsCount, let translatedCount, let translationsUrl): + if boxed { + buffer.appendInt32(-288727837) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(name, buffer: buffer, boxed: false) + serializeString(nativeName, buffer: buffer, boxed: false) + serializeString(langCode, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(baseLangCode!, buffer: buffer, boxed: false)} + serializeString(pluralCode, buffer: buffer, boxed: false) + serializeInt32(stringsCount, buffer: buffer, boxed: false) + serializeInt32(translatedCount, buffer: buffer, boxed: false) + serializeString(translationsUrl, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .langPackLanguage(let flags, let name, let nativeName, let langCode, let baseLangCode, let pluralCode, let stringsCount, let translatedCount, let translationsUrl): + return ("langPackLanguage", [("flags", flags as Any), ("name", name as Any), ("nativeName", nativeName as Any), ("langCode", langCode as Any), ("baseLangCode", baseLangCode as Any), ("pluralCode", pluralCode as Any), ("stringsCount", stringsCount as Any), ("translatedCount", translatedCount as Any), ("translationsUrl", translationsUrl as Any)]) + } + } + + public static func parse_langPackLanguage(_ reader: BufferReader) -> LangPackLanguage? { var _1: Int32? _1 = reader.readInt32() var _2: String? _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt64() } + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } + var _6: String? + _6 = parseString(reader) + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: String? + _9 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionTopicCreate(flags: _1!, title: _2!, iconColor: _3!, iconEmojiId: _4) + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.LangPackLanguage.langPackLanguage(flags: _1!, name: _2!, nativeName: _3!, langCode: _4!, baseLangCode: _5, pluralCode: _6!, stringsCount: _7!, translatedCount: _8!, translationsUrl: _9!) } else { return nil } } - public static func parse_messageActionTopicEdit(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() + + } +} +public extension Api { + enum LangPackString: TypeConstructorDescription { + case langPackString(key: String, value: String) + case langPackStringDeleted(key: String) + case langPackStringPluralized(flags: Int32, key: String, zeroValue: String?, oneValue: String?, twoValue: String?, fewValue: String?, manyValue: String?, otherValue: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .langPackString(let key, let value): + if boxed { + buffer.appendInt32(-892239370) + } + serializeString(key, buffer: buffer, boxed: false) + serializeString(value, buffer: buffer, boxed: false) + break + case .langPackStringDeleted(let key): + if boxed { + buffer.appendInt32(695856818) + } + serializeString(key, buffer: buffer, boxed: false) + break + case .langPackStringPluralized(let flags, let key, let zeroValue, let oneValue, let twoValue, let fewValue, let manyValue, let otherValue): + if boxed { + buffer.appendInt32(1816636575) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(key, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(zeroValue!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(oneValue!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(twoValue!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeString(fewValue!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeString(manyValue!, buffer: buffer, boxed: false)} + serializeString(otherValue, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .langPackString(let key, let value): + return ("langPackString", [("key", key as Any), ("value", value as Any)]) + case .langPackStringDeleted(let key): + return ("langPackStringDeleted", [("key", key as Any)]) + case .langPackStringPluralized(let flags, let key, let zeroValue, let oneValue, let twoValue, let fewValue, let manyValue, let otherValue): + return ("langPackStringPluralized", [("flags", flags as Any), ("key", key as Any), ("zeroValue", zeroValue as Any), ("oneValue", oneValue as Any), ("twoValue", twoValue as Any), ("fewValue", fewValue as Any), ("manyValue", manyValue as Any), ("otherValue", otherValue as Any)]) + } + } + + public static func parse_langPackString(_ reader: BufferReader) -> LangPackString? { + var _1: String? + _1 = parseString(reader) var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - var _3: Int64? - if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt64() } - var _4: Api.Bool? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _5: Api.Bool? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Bool - } } + _2 = parseString(reader) let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageAction.messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4, hidden: _5) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.LangPackString.langPackString(key: _1!, value: _2!) } else { return nil } } - public static func parse_messageActionWebViewDataSent(_ reader: BufferReader) -> MessageAction? { + public static func parse_langPackStringDeleted(_ reader: BufferReader) -> LangPackString? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.MessageAction.messageActionWebViewDataSent(text: _1!) + return Api.LangPackString.langPackStringDeleted(key: _1!) } else { return nil } } - public static func parse_messageActionWebViewDataSentMe(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) + public static func parse_langPackStringPluralized(_ reader: BufferReader) -> LangPackString? { + var _1: Int32? + _1 = reader.readInt32() var _2: String? _2 = parseString(reader) + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: String? + if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } + var _5: String? + if Int(_1!) & Int(1 << 2) != 0 {_5 = parseString(reader) } + var _6: String? + if Int(_1!) & Int(1 << 3) != 0 {_6 = parseString(reader) } + var _7: String? + if Int(_1!) & Int(1 << 4) != 0 {_7 = parseString(reader) } + var _8: String? + _8 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionWebViewDataSentMe(text: _1!, data: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.LangPackString.langPackStringPluralized(flags: _1!, key: _2!, zeroValue: _3, oneValue: _4, twoValue: _5, fewValue: _6, manyValue: _7, otherValue: _8!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index f82c179069d..f9b2095a7c6 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -1,537 +1,573 @@ public extension Api { - indirect enum MessageEntity: TypeConstructorDescription { - case inputMessageEntityMentionName(offset: Int32, length: Int32, userId: Api.InputUser) - case messageEntityBankCard(offset: Int32, length: Int32) - case messageEntityBlockquote(offset: Int32, length: Int32) - case messageEntityBold(offset: Int32, length: Int32) - case messageEntityBotCommand(offset: Int32, length: Int32) - case messageEntityCashtag(offset: Int32, length: Int32) - case messageEntityCode(offset: Int32, length: Int32) - case messageEntityCustomEmoji(offset: Int32, length: Int32, documentId: Int64) - case messageEntityEmail(offset: Int32, length: Int32) - case messageEntityHashtag(offset: Int32, length: Int32) - case messageEntityItalic(offset: Int32, length: Int32) - case messageEntityMention(offset: Int32, length: Int32) - case messageEntityMentionName(offset: Int32, length: Int32, userId: Int64) - case messageEntityPhone(offset: Int32, length: Int32) - case messageEntityPre(offset: Int32, length: Int32, language: String) - case messageEntitySpoiler(offset: Int32, length: Int32) - case messageEntityStrike(offset: Int32, length: Int32) - case messageEntityTextUrl(offset: Int32, length: Int32, url: String) - case messageEntityUnderline(offset: Int32, length: Int32) - case messageEntityUnknown(offset: Int32, length: Int32) - case messageEntityUrl(offset: Int32, length: Int32) + enum MaskCoords: TypeConstructorDescription { + case maskCoords(n: Int32, x: Double, y: Double, zoom: Double) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .inputMessageEntityMentionName(let offset, let length, let userId): + case .maskCoords(let n, let x, let y, let zoom): if boxed { - buffer.appendInt32(546203849) + buffer.appendInt32(-1361650766) } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - userId.serialize(buffer, true) + serializeInt32(n, buffer: buffer, boxed: false) + serializeDouble(x, buffer: buffer, boxed: false) + serializeDouble(y, buffer: buffer, boxed: false) + serializeDouble(zoom, buffer: buffer, boxed: false) break - case .messageEntityBankCard(let offset, let length): - if boxed { - buffer.appendInt32(1981704948) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBlockquote(let offset, let length): - if boxed { - buffer.appendInt32(34469328) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBold(let offset, let length): - if boxed { - buffer.appendInt32(-1117713463) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBotCommand(let offset, let length): - if boxed { - buffer.appendInt32(1827637959) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityCashtag(let offset, let length): - if boxed { - buffer.appendInt32(1280209983) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityCode(let offset, let length): - if boxed { - buffer.appendInt32(681706865) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityCustomEmoji(let offset, let length, let documentId): - if boxed { - buffer.appendInt32(-925956616) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeInt64(documentId, buffer: buffer, boxed: false) - break - case .messageEntityEmail(let offset, let length): - if boxed { - buffer.appendInt32(1692693954) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityHashtag(let offset, let length): - if boxed { - buffer.appendInt32(1868782349) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityItalic(let offset, let length): - if boxed { - buffer.appendInt32(-2106619040) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityMention(let offset, let length): - if boxed { - buffer.appendInt32(-100378723) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityMentionName(let offset, let length, let userId): - if boxed { - buffer.appendInt32(-595914432) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - break - case .messageEntityPhone(let offset, let length): - if boxed { - buffer.appendInt32(-1687559349) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityPre(let offset, let length, let language): - if boxed { - buffer.appendInt32(1938967520) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeString(language, buffer: buffer, boxed: false) - break - case .messageEntitySpoiler(let offset, let length): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .maskCoords(let n, let x, let y, let zoom): + return ("maskCoords", [("n", n as Any), ("x", x as Any), ("y", y as Any), ("zoom", zoom as Any)]) + } + } + + public static func parse_maskCoords(_ reader: BufferReader) -> MaskCoords? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Double? + _2 = reader.readDouble() + var _3: Double? + _3 = reader.readDouble() + var _4: Double? + _4 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MaskCoords.maskCoords(n: _1!, x: _2!, y: _3!, zoom: _4!) + } + else { + return nil + } + } + + } +} +public extension Api { + indirect enum MediaArea: TypeConstructorDescription { + case inputMediaAreaChannelPost(coordinates: Api.MediaAreaCoordinates, channel: Api.InputChannel, msgId: Int32) + case inputMediaAreaVenue(coordinates: Api.MediaAreaCoordinates, queryId: Int64, resultId: String) + case mediaAreaChannelPost(coordinates: Api.MediaAreaCoordinates, channelId: Int64, msgId: Int32) + case mediaAreaGeoPoint(coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint) + case mediaAreaSuggestedReaction(flags: Int32, coordinates: Api.MediaAreaCoordinates, reaction: Api.Reaction) + case mediaAreaVenue(coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputMediaAreaChannelPost(let coordinates, let channel, let msgId): if boxed { - buffer.appendInt32(852137487) + buffer.appendInt32(577893055) } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) + coordinates.serialize(buffer, true) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) break - case .messageEntityStrike(let offset, let length): + case .inputMediaAreaVenue(let coordinates, let queryId, let resultId): if boxed { - buffer.appendInt32(-1090087980) + buffer.appendInt32(-1300094593) } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) + coordinates.serialize(buffer, true) + serializeInt64(queryId, buffer: buffer, boxed: false) + serializeString(resultId, buffer: buffer, boxed: false) break - case .messageEntityTextUrl(let offset, let length, let url): + case .mediaAreaChannelPost(let coordinates, let channelId, let msgId): if boxed { - buffer.appendInt32(1990644519) + buffer.appendInt32(1996756655) } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) + coordinates.serialize(buffer, true) + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(msgId, buffer: buffer, boxed: false) break - case .messageEntityUnderline(let offset, let length): + case .mediaAreaGeoPoint(let coordinates, let geo): if boxed { - buffer.appendInt32(-1672577397) + buffer.appendInt32(-544523486) } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) + coordinates.serialize(buffer, true) + geo.serialize(buffer, true) break - case .messageEntityUnknown(let offset, let length): + case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction): if boxed { - buffer.appendInt32(-1148011883) + buffer.appendInt32(340088945) } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + coordinates.serialize(buffer, true) + reaction.serialize(buffer, true) break - case .messageEntityUrl(let offset, let length): + case .mediaAreaVenue(let coordinates, let geo, let title, let address, let provider, let venueId, let venueType): if boxed { - buffer.appendInt32(1859134776) + buffer.appendInt32(-1098720356) } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) + coordinates.serialize(buffer, true) + geo.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + serializeString(address, buffer: buffer, boxed: false) + serializeString(provider, buffer: buffer, boxed: false) + serializeString(venueId, buffer: buffer, boxed: false) + serializeString(venueType, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .inputMessageEntityMentionName(let offset, let length, let userId): - return ("inputMessageEntityMentionName", [("offset", offset as Any), ("length", length as Any), ("userId", userId as Any)]) - case .messageEntityBankCard(let offset, let length): - return ("messageEntityBankCard", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityBlockquote(let offset, let length): - return ("messageEntityBlockquote", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityBold(let offset, let length): - return ("messageEntityBold", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityBotCommand(let offset, let length): - return ("messageEntityBotCommand", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityCashtag(let offset, let length): - return ("messageEntityCashtag", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityCode(let offset, let length): - return ("messageEntityCode", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityCustomEmoji(let offset, let length, let documentId): - return ("messageEntityCustomEmoji", [("offset", offset as Any), ("length", length as Any), ("documentId", documentId as Any)]) - case .messageEntityEmail(let offset, let length): - return ("messageEntityEmail", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityHashtag(let offset, let length): - return ("messageEntityHashtag", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityItalic(let offset, let length): - return ("messageEntityItalic", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityMention(let offset, let length): - return ("messageEntityMention", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityMentionName(let offset, let length, let userId): - return ("messageEntityMentionName", [("offset", offset as Any), ("length", length as Any), ("userId", userId as Any)]) - case .messageEntityPhone(let offset, let length): - return ("messageEntityPhone", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityPre(let offset, let length, let language): - return ("messageEntityPre", [("offset", offset as Any), ("length", length as Any), ("language", language as Any)]) - case .messageEntitySpoiler(let offset, let length): - return ("messageEntitySpoiler", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityStrike(let offset, let length): - return ("messageEntityStrike", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityTextUrl(let offset, let length, let url): - return ("messageEntityTextUrl", [("offset", offset as Any), ("length", length as Any), ("url", url as Any)]) - case .messageEntityUnderline(let offset, let length): - return ("messageEntityUnderline", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityUnknown(let offset, let length): - return ("messageEntityUnknown", [("offset", offset as Any), ("length", length as Any)]) - case .messageEntityUrl(let offset, let length): - return ("messageEntityUrl", [("offset", offset as Any), ("length", length as Any)]) + case .inputMediaAreaChannelPost(let coordinates, let channel, let msgId): + return ("inputMediaAreaChannelPost", [("coordinates", coordinates as Any), ("channel", channel as Any), ("msgId", msgId as Any)]) + case .inputMediaAreaVenue(let coordinates, let queryId, let resultId): + return ("inputMediaAreaVenue", [("coordinates", coordinates as Any), ("queryId", queryId as Any), ("resultId", resultId as Any)]) + case .mediaAreaChannelPost(let coordinates, let channelId, let msgId): + return ("mediaAreaChannelPost", [("coordinates", coordinates as Any), ("channelId", channelId as Any), ("msgId", msgId as Any)]) + case .mediaAreaGeoPoint(let coordinates, let geo): + return ("mediaAreaGeoPoint", [("coordinates", coordinates as Any), ("geo", geo as Any)]) + case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction): + return ("mediaAreaSuggestedReaction", [("flags", flags as Any), ("coordinates", coordinates as Any), ("reaction", reaction as Any)]) + case .mediaAreaVenue(let coordinates, let geo, let title, let address, let provider, let venueId, let venueType): + return ("mediaAreaVenue", [("coordinates", coordinates as Any), ("geo", geo as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)]) } } - public static func parse_inputMessageEntityMentionName(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.InputUser? + public static func parse_inputMediaAreaChannelPost(_ reader: BufferReader) -> MediaArea? { + var _1: Api.MediaAreaCoordinates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates + } + var _2: Api.InputChannel? if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.InputUser + _2 = Api.parse(reader, signature: signature) as? Api.InputChannel } + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.MessageEntity.inputMessageEntityMentionName(offset: _1!, length: _2!, userId: _3!) + return Api.MediaArea.inputMediaAreaChannelPost(coordinates: _1!, channel: _2!, msgId: _3!) } else { return nil } } - public static func parse_messageEntityBankCard(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + public static func parse_inputMediaAreaVenue(_ reader: BufferReader) -> MediaArea? { + var _1: Api.MediaAreaCoordinates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates + } + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityBankCard(offset: _1!, length: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MediaArea.inputMediaAreaVenue(coordinates: _1!, queryId: _2!, resultId: _3!) } else { return nil } } - public static func parse_messageEntityBlockquote(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + public static func parse_mediaAreaChannelPost(_ reader: BufferReader) -> MediaArea? { + var _1: Api.MediaAreaCoordinates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates + } + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityBlockquote(offset: _1!, length: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MediaArea.mediaAreaChannelPost(coordinates: _1!, channelId: _2!, msgId: _3!) } else { return nil } } - public static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityBold(offset: _1!, length: _2!) + public static func parse_mediaAreaGeoPoint(_ reader: BufferReader) -> MediaArea? { + var _1: Api.MediaAreaCoordinates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates } - else { - return nil + var _2: Api.GeoPoint? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint } - } - public static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) + return Api.MediaArea.mediaAreaGeoPoint(coordinates: _1!, geo: _2!) } else { return nil } } - public static func parse_messageEntityCashtag(_ reader: BufferReader) -> MessageEntity? { + public static func parse_mediaAreaSuggestedReaction(_ reader: BufferReader) -> MediaArea? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + var _2: Api.MediaAreaCoordinates? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates + } + var _3: Api.Reaction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Reaction + } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityCashtag(offset: _1!, length: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MediaArea.mediaAreaSuggestedReaction(flags: _1!, coordinates: _2!, reaction: _3!) } else { return nil } } - public static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + public static func parse_mediaAreaVenue(_ reader: BufferReader) -> MediaArea? { + var _1: Api.MediaAreaCoordinates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates + } + var _2: Api.GeoPoint? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + var _7: String? + _7 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityCode(offset: _1!, length: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.MediaArea.mediaAreaVenue(coordinates: _1!, geo: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!, venueType: _7!) } else { return nil } } - public static func parse_messageEntityCustomEmoji(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() + + } +} +public extension Api { + enum MediaAreaCoordinates: TypeConstructorDescription { + case mediaAreaCoordinates(x: Double, y: Double, w: Double, h: Double, rotation: Double) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .mediaAreaCoordinates(let x, let y, let w, let h, let rotation): + if boxed { + buffer.appendInt32(64088654) + } + serializeDouble(x, buffer: buffer, boxed: false) + serializeDouble(y, buffer: buffer, boxed: false) + serializeDouble(w, buffer: buffer, boxed: false) + serializeDouble(h, buffer: buffer, boxed: false) + serializeDouble(rotation, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .mediaAreaCoordinates(let x, let y, let w, let h, let rotation): + return ("mediaAreaCoordinates", [("x", x as Any), ("y", y as Any), ("w", w as Any), ("h", h as Any), ("rotation", rotation as Any)]) + } + } + + public static func parse_mediaAreaCoordinates(_ reader: BufferReader) -> MediaAreaCoordinates? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + var _3: Double? + _3 = reader.readDouble() + var _4: Double? + _4 = reader.readDouble() + var _5: Double? + _5 = reader.readDouble() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityCustomEmoji(offset: _1!, length: _2!, documentId: _3!) + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MediaAreaCoordinates.mediaAreaCoordinates(x: _1!, y: _2!, w: _3!, h: _4!, rotation: _5!) } else { return nil } } - public static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { + + } +} +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?, 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, let factcheck): + if boxed { + buffer.appendInt32(-1808510398) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(flags2, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 29) != 0 {serializeInt32(fromBoostsApplied!, buffer: buffer, boxed: false)} + peerId.serialize(buffer, true) + if Int(flags) & Int(1 << 28) != 0 {savedPeerId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} + if Int(flags) & Int(1 << 11) != 0 {serializeInt64(viaBotId!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 0) != 0 {serializeInt64(viaBusinessBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} + if Int(flags) & Int(1 << 6) != 0 {replyMarkup!.serialize(buffer, true)} + if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 23) != 0 {replies!.serialize(buffer, true)} + if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 20) != 0 {reactions!.serialize(buffer, true)} + if Int(flags) & Int(1 << 22) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(restrictionReason!.count)) + for item in restrictionReason! { + item.serialize(buffer, true) + }} + 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 { + buffer.appendInt32(-1868117372) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {peerId!.serialize(buffer, true)} + break + case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let ttlPeriod): + if boxed { + buffer.appendInt32(721967202) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} + peerId.serialize(buffer, true) + if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} + serializeInt32(date, buffer: buffer, boxed: false) + action.serialize(buffer, true) + if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} + break + } + } + + 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, 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): + return ("messageService", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("peerId", peerId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("action", action as Any), ("ttlPeriod", ttlPeriod as Any)]) + } + } + + public static func parse_message(_ reader: BufferReader) -> Message? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Peer? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _5: Int32? + if Int(_1!) & Int(1 << 29) != 0 {_5 = reader.readInt32() } + var _6: Api.Peer? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _7: Api.Peer? + if Int(_1!) & Int(1 << 28) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _8: Api.MessageFwdHeader? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } } + var _9: Int64? + if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt64() } + var _10: Int64? + if Int(_2!) & Int(1 << 0) != 0 {_10 = reader.readInt64() } + var _11: Api.MessageReplyHeader? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } } + var _12: Int32? + _12 = reader.readInt32() + var _13: String? + _13 = parseString(reader) + var _14: Api.MessageMedia? + if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } } + var _15: Api.ReplyMarkup? + if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { + _15 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup + } } + var _16: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { + _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + var _17: Int32? + if Int(_1!) & Int(1 << 10) != 0 {_17 = reader.readInt32() } + var _18: Int32? + if Int(_1!) & Int(1 << 10) != 0 {_18 = reader.readInt32() } + var _19: Api.MessageReplies? + if Int(_1!) & Int(1 << 23) != 0 {if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.MessageReplies + } } + var _20: Int32? + if Int(_1!) & Int(1 << 15) != 0 {_20 = reader.readInt32() } + var _21: String? + if Int(_1!) & Int(1 << 16) != 0 {_21 = parseString(reader) } + var _22: Int64? + if Int(_1!) & Int(1 << 17) != 0 {_22 = reader.readInt64() } + var _23: Api.MessageReactions? + if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() { + _23 = Api.parse(reader, signature: signature) as? Api.MessageReactions + } } + var _24: [Api.RestrictionReason]? + if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() { + _24 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + } } + var _25: Int32? + if Int(_1!) & Int(1 << 25) != 0 {_25 = reader.readInt32() } + var _26: Int32? + 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 - if _c1 && _c2 { - return Api.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 29) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 28) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 11) == 0) || _9 != nil + let _c10 = (Int(_2!) & Int(1 << 0) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil + let _c12 = _12 != nil + let _c13 = _13 != nil + let _c14 = (Int(_1!) & Int(1 << 9) == 0) || _14 != nil + let _c15 = (Int(_1!) & Int(1 << 6) == 0) || _15 != nil + let _c16 = (Int(_1!) & Int(1 << 7) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 10) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 23) == 0) || _19 != nil + let _c20 = (Int(_1!) & Int(1 << 15) == 0) || _20 != nil + let _c21 = (Int(_1!) & Int(1 << 16) == 0) || _21 != nil + let _c22 = (Int(_1!) & Int(1 << 17) == 0) || _22 != nil + let _c23 = (Int(_1!) & Int(1 << 20) == 0) || _23 != nil + let _c24 = (Int(_1!) & Int(1 << 22) == 0) || _24 != nil + 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 + 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 } } - public static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { + public static func parse_messageEmpty(_ reader: BufferReader) -> Message? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() + var _3: Api.Peer? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Message.messageEmpty(flags: _1!, id: _2!, peerId: _3) } else { return nil } } - public static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { + public static func parse_messageService(_ reader: BufferReader) -> Message? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) - } - else { - return nil - } - } - public static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityMention(offset: _1!, length: _2!) - } - else { - return nil - } - } - public static func parse_messageEntityMentionName(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityMentionName(offset: _1!, length: _2!, userId: _3!) - } - else { - return nil - } - } - public static func parse_messageEntityPhone(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityPhone(offset: _1!, length: _2!) - } - else { - return nil - } - } - public static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) - } - else { - return nil - } - } - public static func parse_messageEntitySpoiler(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntitySpoiler(offset: _1!, length: _2!) - } - else { - return nil - } - } - public static func parse_messageEntityStrike(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityStrike(offset: _1!, length: _2!) - } - else { - return nil - } - } - public static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) - } - else { - return nil - } - } - public static func parse_messageEntityUnderline(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityUnderline(offset: _1!, length: _2!) - } - else { - return nil - } - } - public static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) + var _3: Api.Peer? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer } - else { - return nil + var _5: Api.MessageReplyHeader? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } } + var _6: Int32? + _6 = reader.readInt32() + var _7: Api.MessageAction? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.MessageAction } - } - public static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + var _8: Int32? + if Int(_1!) & Int(1 << 25) != 0 {_8 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) + let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1!) & Int(1 << 25) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, replyTo: _5, date: _6!, action: _7!, ttlPeriod: _8) } else { return nil @@ -541,713 +577,1083 @@ public extension Api { } } public extension Api { - indirect enum MessageExtendedMedia: TypeConstructorDescription { - case messageExtendedMedia(media: Api.MessageMedia) - case messageExtendedMediaPreview(flags: Int32, w: Int32?, h: Int32?, thumb: Api.PhotoSize?, videoDuration: Int32?) + enum MessageAction: TypeConstructorDescription { + case messageActionBoostApply(boosts: Int32) + case messageActionBotAllowed(flags: Int32, domain: String?, app: Api.BotApp?) + case messageActionChannelCreate(title: String) + case messageActionChannelMigrateFrom(title: String, chatId: Int64) + case messageActionChatAddUser(users: [Int64]) + case messageActionChatCreate(title: String, users: [Int64]) + case messageActionChatDeletePhoto + case messageActionChatDeleteUser(userId: Int64) + case messageActionChatEditPhoto(photo: Api.Photo) + case messageActionChatEditTitle(title: String) + case messageActionChatJoinedByLink(inviterId: Int64) + case messageActionChatJoinedByRequest + case messageActionChatMigrateTo(channelId: Int64) + case messageActionContactSignUp + case messageActionCustomAction(message: String) + case messageActionEmpty + case messageActionGameScore(gameId: Int64, score: Int32) + case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32) + case messageActionGiftCode(flags: Int32, boostPeer: Api.Peer?, months: Int32, slug: String, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?) + case messageActionGiftPremium(flags: Int32, currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?) + case messageActionGiveawayLaunch + case messageActionGiveawayResults(winnersCount: Int32, unclaimedCount: Int32) + case messageActionGroupCall(flags: Int32, call: Api.InputGroupCall, duration: Int32?) + case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32) + case messageActionHistoryClear + case messageActionInviteToGroupCall(call: Api.InputGroupCall, users: [Int64]) + case messageActionPaymentSent(flags: Int32, currency: String, totalAmount: Int64, invoiceSlug: String?) + case messageActionPaymentSentMe(flags: Int32, currency: String, totalAmount: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, charge: Api.PaymentCharge) + case messageActionPhoneCall(flags: Int32, callId: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?) + case messageActionPinMessage + case messageActionRequestedPeer(buttonId: Int32, peers: [Api.Peer]) + case messageActionRequestedPeerSentMe(buttonId: Int32, peers: [Api.RequestedPeer]) + case messageActionScreenshotTaken + case messageActionSecureValuesSent(types: [Api.SecureValueType]) + case messageActionSecureValuesSentMe(values: [Api.SecureValue], credentials: Api.SecureCredentialsEncrypted) + case messageActionSetChatTheme(emoticon: String) + case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper) + case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) + case messageActionSuggestProfilePhoto(photo: Api.Photo) + case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) + case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) + case messageActionWebViewDataSent(text: String) + case messageActionWebViewDataSentMe(text: String, data: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageExtendedMedia(let media): + case .messageActionBoostApply(let boosts): if boxed { - buffer.appendInt32(-297296796) + buffer.appendInt32(-872240531) } - media.serialize(buffer, true) + serializeInt32(boosts, buffer: buffer, boxed: false) break - case .messageExtendedMediaPreview(let flags, let w, let h, let thumb, let videoDuration): + case .messageActionBotAllowed(let flags, let domain, let app): if boxed { - buffer.appendInt32(-1386050360) + buffer.appendInt32(-988359047) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(w!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(h!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {thumb!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(videoDuration!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeString(domain!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {app!.serialize(buffer, true)} break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageExtendedMedia(let media): - return ("messageExtendedMedia", [("media", media as Any)]) - case .messageExtendedMediaPreview(let flags, let w, let h, let thumb, let videoDuration): - return ("messageExtendedMediaPreview", [("flags", flags as Any), ("w", w as Any), ("h", h as Any), ("thumb", thumb as Any), ("videoDuration", videoDuration as Any)]) - } - } - - public static func parse_messageExtendedMedia(_ reader: BufferReader) -> MessageExtendedMedia? { - var _1: Api.MessageMedia? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageExtendedMedia.messageExtendedMedia(media: _1!) - } - else { - return nil - } - } - public static func parse_messageExtendedMediaPreview(_ reader: BufferReader) -> MessageExtendedMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: Api.PhotoSize? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.PhotoSize - } } - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageExtendedMedia.messageExtendedMediaPreview(flags: _1!, w: _2, h: _3, thumb: _4, videoDuration: _5) - } - else { - return nil - } - } - - } -} -public extension Api { - enum MessageFwdHeader: TypeConstructorDescription { - case messageFwdHeader(flags: Int32, fromId: Api.Peer?, fromName: String?, date: Int32, channelPost: Int32?, postAuthor: String?, savedFromPeer: Api.Peer?, savedFromMsgId: Int32?, savedFromId: Api.Peer?, savedFromName: String?, savedDate: Int32?, psaType: String?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let savedFromId, let savedFromName, let savedDate, let psaType): + case .messageActionChannelCreate(let title): if boxed { - buffer.appendInt32(1313731771) + buffer.appendInt32(-1781355374) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 5) != 0 {serializeString(fromName!, buffer: buffer, boxed: false)} - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(channelPost!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {savedFromPeer!.serialize(buffer, true)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(savedFromMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 8) != 0 {savedFromId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 9) != 0 {serializeString(savedFromName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(savedDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 6) != 0 {serializeString(psaType!, buffer: buffer, boxed: false)} + serializeString(title, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let savedFromId, let savedFromName, let savedDate, let psaType): - return ("messageFwdHeader", [("flags", flags as Any), ("fromId", fromId as Any), ("fromName", fromName as Any), ("date", date as Any), ("channelPost", channelPost as Any), ("postAuthor", postAuthor as Any), ("savedFromPeer", savedFromPeer as Any), ("savedFromMsgId", savedFromMsgId as Any), ("savedFromId", savedFromId as Any), ("savedFromName", savedFromName as Any), ("savedDate", savedDate as Any), ("psaType", psaType as Any)]) - } - } - - public static func parse_messageFwdHeader(_ reader: BufferReader) -> MessageFwdHeader? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _3: String? - if Int(_1!) & Int(1 << 5) != 0 {_3 = parseString(reader) } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } - var _6: String? - if Int(_1!) & Int(1 << 3) != 0 {_6 = parseString(reader) } - var _7: Api.Peer? - if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _8: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() } - var _9: Api.Peer? - if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _10: String? - if Int(_1!) & Int(1 << 9) != 0 {_10 = parseString(reader) } - var _11: Int32? - if Int(_1!) & Int(1 << 10) != 0 {_11 = reader.readInt32() } - var _12: String? - if Int(_1!) & Int(1 << 6) != 0 {_12 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 9) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 10) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.MessageFwdHeader.messageFwdHeader(flags: _1!, fromId: _2, fromName: _3, date: _4!, channelPost: _5, postAuthor: _6, savedFromPeer: _7, savedFromMsgId: _8, savedFromId: _9, savedFromName: _10, savedDate: _11, psaType: _12) - } - else { - return nil - } - } - - } -} -public extension Api { - indirect enum MessageMedia: TypeConstructorDescription { - case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64) - case messageMediaDice(value: Int32, emoticon: String) - case messageMediaDocument(flags: Int32, document: Api.Document?, altDocument: Api.Document?, ttlSeconds: Int32?) - case messageMediaEmpty - case messageMediaGame(game: Api.Game) - case messageMediaGeo(geo: Api.GeoPoint) - case messageMediaGeoLive(flags: Int32, geo: Api.GeoPoint, heading: Int32?, period: Int32, proximityNotificationRadius: Int32?) - case messageMediaGiveaway(flags: Int32, channels: [Int64], countriesIso2: [String]?, prizeDescription: String?, quantity: Int32, months: Int32, untilDate: Int32) - case messageMediaGiveawayResults(flags: Int32, channelId: Int64, additionalPeersCount: Int32?, launchMsgId: Int32, winnersCount: Int32, unclaimedCount: Int32, winners: [Int64], months: Int32, prizeDescription: String?, untilDate: Int32) - case messageMediaInvoice(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?) - case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?) - case messageMediaPoll(poll: Api.Poll, results: Api.PollResults) - case messageMediaStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) - case messageMediaUnsupported - case messageMediaVenue(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) - case messageMediaWebPage(flags: Int32, webpage: Api.WebPage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageMediaContact(let phoneNumber, let firstName, let lastName, let vcard, let userId): + case .messageActionChannelMigrateFrom(let title, let chatId): + if boxed { + buffer.appendInt32(-365344535) + } + serializeString(title, buffer: buffer, boxed: false) + serializeInt64(chatId, buffer: buffer, boxed: false) + break + case .messageActionChatAddUser(let users): + if boxed { + buffer.appendInt32(365886720) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .messageActionChatCreate(let title, let users): + if boxed { + buffer.appendInt32(-1119368275) + } + serializeString(title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .messageActionChatDeletePhoto: + if boxed { + buffer.appendInt32(-1780220945) + } + + break + case .messageActionChatDeleteUser(let userId): if boxed { - buffer.appendInt32(1882335561) + buffer.appendInt32(-1539362612) } - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - serializeString(vcard, buffer: buffer, boxed: false) serializeInt64(userId, buffer: buffer, boxed: false) break - case .messageMediaDice(let value, let emoticon): + case .messageActionChatEditPhoto(let photo): if boxed { - buffer.appendInt32(1065280907) + buffer.appendInt32(2144015272) } - serializeInt32(value, buffer: buffer, boxed: false) - serializeString(emoticon, buffer: buffer, boxed: false) + photo.serialize(buffer, true) break - case .messageMediaDocument(let flags, let document, let altDocument, let ttlSeconds): + case .messageActionChatEditTitle(let title): if boxed { - buffer.appendInt32(1291114285) + buffer.appendInt32(-1247687078) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {document!.serialize(buffer, true)} - if Int(flags) & Int(1 << 5) != 0 {altDocument!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} + serializeString(title, buffer: buffer, boxed: false) + break + case .messageActionChatJoinedByLink(let inviterId): + if boxed { + buffer.appendInt32(51520707) + } + serializeInt64(inviterId, buffer: buffer, boxed: false) break - case .messageMediaEmpty: + case .messageActionChatJoinedByRequest: if boxed { - buffer.appendInt32(1038967584) + buffer.appendInt32(-339958837) } break - case .messageMediaGame(let game): + case .messageActionChatMigrateTo(let channelId): if boxed { - buffer.appendInt32(-38694904) + buffer.appendInt32(-519864430) } - game.serialize(buffer, true) + serializeInt64(channelId, buffer: buffer, boxed: false) break - case .messageMediaGeo(let geo): + case .messageActionContactSignUp: if boxed { - buffer.appendInt32(1457575028) + buffer.appendInt32(-202219658) } - geo.serialize(buffer, true) + break - case .messageMediaGeoLive(let flags, let geo, let heading, let period, let proximityNotificationRadius): + case .messageActionCustomAction(let message): if boxed { - buffer.appendInt32(-1186937242) + buffer.appendInt32(-85549226) } - serializeInt32(flags, buffer: buffer, boxed: false) - geo.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(heading!, buffer: buffer, boxed: false)} - serializeInt32(period, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(proximityNotificationRadius!, buffer: buffer, boxed: false)} + serializeString(message, buffer: buffer, boxed: false) break - case .messageMediaGiveaway(let flags, let channels, let countriesIso2, let prizeDescription, let quantity, let months, let untilDate): + case .messageActionEmpty: if boxed { - buffer.appendInt32(-626162256) + buffer.appendInt32(-1230047312) } - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(channels.count)) - for item in channels { - serializeInt64(item, buffer: buffer, boxed: false) + + break + case .messageActionGameScore(let gameId, let score): + if boxed { + buffer.appendInt32(-1834538890) } - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(countriesIso2!.count)) - for item in countriesIso2! { - serializeString(item, buffer: buffer, boxed: false) - }} - if Int(flags) & Int(1 << 3) != 0 {serializeString(prizeDescription!, buffer: buffer, boxed: false)} - serializeInt32(quantity, buffer: buffer, boxed: false) + serializeInt64(gameId, buffer: buffer, boxed: false) + serializeInt32(score, buffer: buffer, boxed: false) + break + case .messageActionGeoProximityReached(let fromId, let toId, let distance): + if boxed { + buffer.appendInt32(-1730095465) + } + fromId.serialize(buffer, true) + toId.serialize(buffer, true) + serializeInt32(distance, buffer: buffer, boxed: false) + break + case .messageActionGiftCode(let flags, let boostPeer, let months, let slug, let currency, let amount, let cryptoCurrency, let cryptoAmount): + if boxed { + buffer.appendInt32(1737240073) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {boostPeer!.serialize(buffer, true)} serializeInt32(months, buffer: buffer, boxed: false) - serializeInt32(untilDate, buffer: buffer, boxed: false) + serializeString(slug, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeString(currency!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt64(amount!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)} break - case .messageMediaGiveawayResults(let flags, let channelId, let additionalPeersCount, let launchMsgId, let winnersCount, let unclaimedCount, let winners, let months, let prizeDescription, let untilDate): + case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount): if boxed { - buffer.appendInt32(-963047320) + buffer.appendInt32(-935499028) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(additionalPeersCount!, buffer: buffer, boxed: false)} - serializeInt32(launchMsgId, buffer: buffer, boxed: false) + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + serializeInt32(months, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)} + break + case .messageActionGiveawayLaunch: + if boxed { + buffer.appendInt32(858499565) + } + + break + case .messageActionGiveawayResults(let winnersCount, let unclaimedCount): + if boxed { + buffer.appendInt32(715107781) + } serializeInt32(winnersCount, buffer: buffer, boxed: false) serializeInt32(unclaimedCount, buffer: buffer, boxed: false) + break + case .messageActionGroupCall(let flags, let call, let duration): + if boxed { + buffer.appendInt32(2047704898) + } + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} + break + case .messageActionGroupCallScheduled(let call, let scheduleDate): + if boxed { + buffer.appendInt32(-1281329567) + } + call.serialize(buffer, true) + serializeInt32(scheduleDate, buffer: buffer, boxed: false) + break + case .messageActionHistoryClear: + if boxed { + buffer.appendInt32(-1615153660) + } + + break + case .messageActionInviteToGroupCall(let call, let users): + if boxed { + buffer.appendInt32(1345295095) + } + call.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(winners.count)) - for item in winners { + buffer.appendInt32(Int32(users.count)) + for item in users { serializeInt64(item, buffer: buffer, boxed: false) } - serializeInt32(months, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(prizeDescription!, buffer: buffer, boxed: false)} - serializeInt32(untilDate, buffer: buffer, boxed: false) break - case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia): + case .messageActionPaymentSent(let flags, let currency, let totalAmount, let invoiceSlug): if boxed { - buffer.appendInt32(-156940077) + buffer.appendInt32(-1776926890) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {photo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(receiptMsgId!, buffer: buffer, boxed: false)} serializeString(currency, buffer: buffer, boxed: false) serializeInt64(totalAmount, buffer: buffer, boxed: false) - serializeString(startParam, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 4) != 0 {extendedMedia!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {serializeString(invoiceSlug!, buffer: buffer, boxed: false)} break - case .messageMediaPhoto(let flags, let photo, let ttlSeconds): + case .messageActionPaymentSentMe(let flags, let currency, let totalAmount, let payload, let info, let shippingOptionId, let charge): if boxed { - buffer.appendInt32(1766936791) + buffer.appendInt32(-1892568281) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {photo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(totalAmount, buffer: buffer, boxed: false) + serializeBytes(payload, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(shippingOptionId!, buffer: buffer, boxed: false)} + charge.serialize(buffer, true) break - case .messageMediaPoll(let poll, let results): + case .messageActionPhoneCall(let flags, let callId, let reason, let duration): if boxed { - buffer.appendInt32(1272375192) + buffer.appendInt32(-2132731265) } - poll.serialize(buffer, true) - results.serialize(buffer, true) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(callId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {reason!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} break - case .messageMediaStory(let flags, let peer, let id, let story): + case .messageActionPinMessage: if boxed { - buffer.appendInt32(1758159491) + buffer.appendInt32(-1799538451) + } + + break + case .messageActionRequestedPeer(let buttonId, let peers): + if boxed { + buffer.appendInt32(827428507) + } + serializeInt32(buttonId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + break + case .messageActionRequestedPeerSentMe(let buttonId, let peers): + if boxed { + buffer.appendInt32(-1816979384) + } + serializeInt32(buttonId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {story!.serialize(buffer, true)} break - case .messageMediaUnsupported: + case .messageActionScreenshotTaken: if boxed { - buffer.appendInt32(-1618676578) + buffer.appendInt32(1200788123) } break - case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType): + case .messageActionSecureValuesSent(let types): if boxed { - buffer.appendInt32(784356159) + buffer.appendInt32(-648257196) } - geo.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(types.count)) + for item in types { + item.serialize(buffer, true) + } + break + case .messageActionSecureValuesSentMe(let values, let credentials): + if boxed { + buffer.appendInt32(455635795) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(values.count)) + for item in values { + item.serialize(buffer, true) + } + credentials.serialize(buffer, true) + break + case .messageActionSetChatTheme(let emoticon): + if boxed { + buffer.appendInt32(-1434950843) + } + serializeString(emoticon, buffer: buffer, boxed: false) + break + case .messageActionSetChatWallPaper(let flags, let wallpaper): + if boxed { + buffer.appendInt32(1348510708) + } + serializeInt32(flags, buffer: buffer, boxed: false) + wallpaper.serialize(buffer, true) + break + case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): + if boxed { + buffer.appendInt32(1007897979) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(period, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)} + break + case .messageActionSuggestProfilePhoto(let photo): + if boxed { + buffer.appendInt32(1474192222) + } + photo.serialize(buffer, true) + break + case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): + if boxed { + buffer.appendInt32(228168278) + } + serializeInt32(flags, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) - serializeString(address, buffer: buffer, boxed: false) - serializeString(provider, buffer: buffer, boxed: false) - serializeString(venueId, buffer: buffer, boxed: false) - serializeString(venueType, buffer: buffer, boxed: false) + serializeInt32(iconColor, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} break - case .messageMediaWebPage(let flags, let webpage): + case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden): if boxed { - buffer.appendInt32(-571405253) + buffer.appendInt32(-1064024032) } serializeInt32(flags, buffer: buffer, boxed: false) - webpage.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {hidden!.serialize(buffer, true)} + break + case .messageActionWebViewDataSent(let text): + if boxed { + buffer.appendInt32(-1262252875) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .messageActionWebViewDataSentMe(let text, let data): + if boxed { + buffer.appendInt32(1205698681) + } + serializeString(text, buffer: buffer, boxed: false) + serializeString(data, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageMediaContact(let phoneNumber, let firstName, let lastName, let vcard, let userId): - return ("messageMediaContact", [("phoneNumber", phoneNumber as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("vcard", vcard as Any), ("userId", userId as Any)]) - case .messageMediaDice(let value, let emoticon): - return ("messageMediaDice", [("value", value as Any), ("emoticon", emoticon as Any)]) - case .messageMediaDocument(let flags, let document, let altDocument, let ttlSeconds): - return ("messageMediaDocument", [("flags", flags as Any), ("document", document as Any), ("altDocument", altDocument as Any), ("ttlSeconds", ttlSeconds as Any)]) - case .messageMediaEmpty: - return ("messageMediaEmpty", []) - case .messageMediaGame(let game): - return ("messageMediaGame", [("game", game as Any)]) - case .messageMediaGeo(let geo): - return ("messageMediaGeo", [("geo", geo as Any)]) - case .messageMediaGeoLive(let flags, let geo, let heading, let period, let proximityNotificationRadius): - return ("messageMediaGeoLive", [("flags", flags as Any), ("geo", geo as Any), ("heading", heading as Any), ("period", period as Any), ("proximityNotificationRadius", proximityNotificationRadius as Any)]) - case .messageMediaGiveaway(let flags, let channels, let countriesIso2, let prizeDescription, let quantity, let months, let untilDate): - return ("messageMediaGiveaway", [("flags", flags as Any), ("channels", channels as Any), ("countriesIso2", countriesIso2 as Any), ("prizeDescription", prizeDescription as Any), ("quantity", quantity as Any), ("months", months as Any), ("untilDate", untilDate as Any)]) - case .messageMediaGiveawayResults(let flags, let channelId, let additionalPeersCount, let launchMsgId, let winnersCount, let unclaimedCount, let winners, let months, let prizeDescription, let untilDate): - return ("messageMediaGiveawayResults", [("flags", flags as Any), ("channelId", channelId as Any), ("additionalPeersCount", additionalPeersCount as Any), ("launchMsgId", launchMsgId as Any), ("winnersCount", winnersCount as Any), ("unclaimedCount", unclaimedCount as Any), ("winners", winners as Any), ("months", months as Any), ("prizeDescription", prizeDescription as Any), ("untilDate", untilDate as Any)]) - case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia): - return ("messageMediaInvoice", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("receiptMsgId", receiptMsgId as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("startParam", startParam as Any), ("extendedMedia", extendedMedia as Any)]) - case .messageMediaPhoto(let flags, let photo, let ttlSeconds): - return ("messageMediaPhoto", [("flags", flags as Any), ("photo", photo as Any), ("ttlSeconds", ttlSeconds as Any)]) - case .messageMediaPoll(let poll, let results): - return ("messageMediaPoll", [("poll", poll as Any), ("results", results as Any)]) - case .messageMediaStory(let flags, let peer, let id, let story): - return ("messageMediaStory", [("flags", flags as Any), ("peer", peer as Any), ("id", id as Any), ("story", story as Any)]) - case .messageMediaUnsupported: - return ("messageMediaUnsupported", []) - case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType): - return ("messageMediaVenue", [("geo", geo as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)]) - case .messageMediaWebPage(let flags, let webpage): - return ("messageMediaWebPage", [("flags", flags as Any), ("webpage", webpage as Any)]) + case .messageActionBoostApply(let boosts): + return ("messageActionBoostApply", [("boosts", boosts as Any)]) + case .messageActionBotAllowed(let flags, let domain, let app): + return ("messageActionBotAllowed", [("flags", flags as Any), ("domain", domain as Any), ("app", app as Any)]) + case .messageActionChannelCreate(let title): + return ("messageActionChannelCreate", [("title", title as Any)]) + case .messageActionChannelMigrateFrom(let title, let chatId): + return ("messageActionChannelMigrateFrom", [("title", title as Any), ("chatId", chatId as Any)]) + case .messageActionChatAddUser(let users): + return ("messageActionChatAddUser", [("users", users as Any)]) + case .messageActionChatCreate(let title, let users): + return ("messageActionChatCreate", [("title", title as Any), ("users", users as Any)]) + case .messageActionChatDeletePhoto: + return ("messageActionChatDeletePhoto", []) + case .messageActionChatDeleteUser(let userId): + return ("messageActionChatDeleteUser", [("userId", userId as Any)]) + case .messageActionChatEditPhoto(let photo): + return ("messageActionChatEditPhoto", [("photo", photo as Any)]) + case .messageActionChatEditTitle(let title): + return ("messageActionChatEditTitle", [("title", title as Any)]) + case .messageActionChatJoinedByLink(let inviterId): + return ("messageActionChatJoinedByLink", [("inviterId", inviterId as Any)]) + case .messageActionChatJoinedByRequest: + return ("messageActionChatJoinedByRequest", []) + case .messageActionChatMigrateTo(let channelId): + return ("messageActionChatMigrateTo", [("channelId", channelId as Any)]) + case .messageActionContactSignUp: + return ("messageActionContactSignUp", []) + case .messageActionCustomAction(let message): + return ("messageActionCustomAction", [("message", message as Any)]) + case .messageActionEmpty: + return ("messageActionEmpty", []) + case .messageActionGameScore(let gameId, let score): + return ("messageActionGameScore", [("gameId", gameId as Any), ("score", score as Any)]) + case .messageActionGeoProximityReached(let fromId, let toId, let distance): + return ("messageActionGeoProximityReached", [("fromId", fromId as Any), ("toId", toId as Any), ("distance", distance as Any)]) + case .messageActionGiftCode(let flags, let boostPeer, let months, let slug, let currency, let amount, let cryptoCurrency, let cryptoAmount): + return ("messageActionGiftCode", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("months", months as Any), ("slug", slug as Any), ("currency", currency as Any), ("amount", amount as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any)]) + case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount): + return ("messageActionGiftPremium", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("months", months as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any)]) + case .messageActionGiveawayLaunch: + return ("messageActionGiveawayLaunch", []) + case .messageActionGiveawayResults(let winnersCount, let unclaimedCount): + return ("messageActionGiveawayResults", [("winnersCount", winnersCount as Any), ("unclaimedCount", unclaimedCount as Any)]) + case .messageActionGroupCall(let flags, let call, let duration): + return ("messageActionGroupCall", [("flags", flags as Any), ("call", call as Any), ("duration", duration as Any)]) + case .messageActionGroupCallScheduled(let call, let scheduleDate): + return ("messageActionGroupCallScheduled", [("call", call as Any), ("scheduleDate", scheduleDate as Any)]) + case .messageActionHistoryClear: + return ("messageActionHistoryClear", []) + case .messageActionInviteToGroupCall(let call, let users): + return ("messageActionInviteToGroupCall", [("call", call as Any), ("users", users as Any)]) + case .messageActionPaymentSent(let flags, let currency, let totalAmount, let invoiceSlug): + return ("messageActionPaymentSent", [("flags", flags as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("invoiceSlug", invoiceSlug as Any)]) + case .messageActionPaymentSentMe(let flags, let currency, let totalAmount, let payload, let info, let shippingOptionId, let charge): + return ("messageActionPaymentSentMe", [("flags", flags as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("payload", payload as Any), ("info", info as Any), ("shippingOptionId", shippingOptionId as Any), ("charge", charge as Any)]) + case .messageActionPhoneCall(let flags, let callId, let reason, let duration): + return ("messageActionPhoneCall", [("flags", flags as Any), ("callId", callId as Any), ("reason", reason as Any), ("duration", duration as Any)]) + case .messageActionPinMessage: + return ("messageActionPinMessage", []) + case .messageActionRequestedPeer(let buttonId, let peers): + return ("messageActionRequestedPeer", [("buttonId", buttonId as Any), ("peers", peers as Any)]) + case .messageActionRequestedPeerSentMe(let buttonId, let peers): + return ("messageActionRequestedPeerSentMe", [("buttonId", buttonId as Any), ("peers", peers as Any)]) + case .messageActionScreenshotTaken: + return ("messageActionScreenshotTaken", []) + case .messageActionSecureValuesSent(let types): + return ("messageActionSecureValuesSent", [("types", types as Any)]) + case .messageActionSecureValuesSentMe(let values, let credentials): + return ("messageActionSecureValuesSentMe", [("values", values as Any), ("credentials", credentials as Any)]) + case .messageActionSetChatTheme(let emoticon): + return ("messageActionSetChatTheme", [("emoticon", emoticon as Any)]) + case .messageActionSetChatWallPaper(let flags, let wallpaper): + return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)]) + case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): + return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) + case .messageActionSuggestProfilePhoto(let photo): + return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) + case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): + return ("messageActionTopicCreate", [("flags", flags as Any), ("title", title as Any), ("iconColor", iconColor as Any), ("iconEmojiId", iconEmojiId as Any)]) + case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden): + return ("messageActionTopicEdit", [("flags", flags as Any), ("title", title as Any), ("iconEmojiId", iconEmojiId as Any), ("closed", closed as Any), ("hidden", hidden as Any)]) + case .messageActionWebViewDataSent(let text): + return ("messageActionWebViewDataSent", [("text", text as Any)]) + case .messageActionWebViewDataSentMe(let text, let data): + return ("messageActionWebViewDataSentMe", [("text", text as Any), ("data", data as Any)]) } } - public static func parse_messageMediaContact(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionBoostApply(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionBoostApply(boosts: _1!) + } + else { + return nil + } + } + public static func parse_messageActionBotAllowed(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + var _3: Api.BotApp? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.BotApp + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionBotAllowed(flags: _1!, domain: _2, app: _3) + } + else { + return nil + } + } + public static func parse_messageActionChannelCreate(_ reader: BufferReader) -> MessageAction? { var _1: String? _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChannelCreate(title: _1!) + } + else { + return nil + } + } + public static func parse_messageActionChannelMigrateFrom(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionChannelMigrateFrom(title: _1!, chatId: _2!) + } + else { + return nil + } + } + public static func parse_messageActionChatAddUser(_ reader: BufferReader) -> MessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatAddUser(users: _1!) + } + else { + return nil + } + } + public static func parse_messageActionChatCreate(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + var _2: [Int64]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionChatCreate(title: _1!, users: _2!) + } + else { + return nil + } + } + public static func parse_messageActionChatDeletePhoto(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionChatDeletePhoto + } + public static func parse_messageActionChatDeleteUser(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatDeleteUser(userId: _1!) + } + else { + return nil + } + } + public static func parse_messageActionChatEditPhoto(_ reader: BufferReader) -> MessageAction? { + var _1: Api.Photo? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Photo + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatEditPhoto(photo: _1!) + } + else { + return nil + } + } + public static func parse_messageActionChatEditTitle(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatEditTitle(title: _1!) + } + else { + return nil + } + } + public static func parse_messageActionChatJoinedByLink(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatJoinedByLink(inviterId: _1!) + } + else { + return nil + } + } + public static func parse_messageActionChatJoinedByRequest(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionChatJoinedByRequest + } + public static func parse_messageActionChatMigrateTo(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatMigrateTo(channelId: _1!) + } + else { + return nil + } + } + public static func parse_messageActionContactSignUp(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionContactSignUp + } + public static func parse_messageActionCustomAction(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionCustomAction(message: _1!) + } + else { + return nil + } + } + public static func parse_messageActionEmpty(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionEmpty + } + public static func parse_messageActionGameScore(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionGameScore(gameId: _1!, score: _2!) + } + else { + return nil + } + } + public static func parse_messageActionGeoProximityReached(_ reader: BufferReader) -> MessageAction? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionGeoProximityReached(fromId: _1!, toId: _2!, distance: _3!) + } + else { + return nil + } + } + public static func parse_messageActionGiftCode(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _3: Int32? + _3 = reader.readInt32() var _4: String? _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() + var _5: String? + if Int(_1!) & Int(1 << 2) != 0 {_5 = parseString(reader) } + var _6: Int64? + if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt64() } + var _7: String? + if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) } + var _8: Int64? + if Int(_1!) & Int(1 << 3) != 0 {_8 = reader.readInt64() } let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageMedia.messageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, vcard: _4!, userId: _5!) + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.MessageAction.messageActionGiftCode(flags: _1!, boostPeer: _2, months: _3!, slug: _4!, currency: _5, amount: _6, cryptoCurrency: _7, cryptoAmount: _8) } else { return nil } } - public static func parse_messageMediaDice(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionGiftPremium(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() var _2: String? _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) } + var _6: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageMedia.messageMediaDice(value: _1!, emoticon: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.MessageAction.messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, months: _4!, cryptoCurrency: _5, cryptoAmount: _6) } else { return nil } } - public static func parse_messageMediaDocument(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionGiveawayLaunch(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionGiveawayLaunch + } + public static func parse_messageActionGiveawayResults(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Document? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Document - } } - var _3: Api.Document? - if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Document - } } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageMedia.messageMediaDocument(flags: _1!, document: _2, altDocument: _3, ttlSeconds: _4) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionGiveawayResults(winnersCount: _1!, unclaimedCount: _2!) } else { return nil } } - public static func parse_messageMediaEmpty(_ reader: BufferReader) -> MessageMedia? { - return Api.MessageMedia.messageMediaEmpty - } - public static func parse_messageMediaGame(_ reader: BufferReader) -> MessageMedia? { - var _1: Api.Game? + public static func parse_messageActionGroupCall(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputGroupCall? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Game + _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } let _c1 = _1 != nil - if _c1 { - return Api.MessageMedia.messageMediaGame(game: _1!) + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionGroupCall(flags: _1!, call: _2!, duration: _3) } else { return nil } } - public static func parse_messageMediaGeo(_ reader: BufferReader) -> MessageMedia? { - var _1: Api.GeoPoint? + public static func parse_messageActionGroupCallScheduled(_ reader: BufferReader) -> MessageAction? { + var _1: Api.InputGroupCall? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.MessageMedia.messageMediaGeo(geo: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionGroupCallScheduled(call: _1!, scheduleDate: _2!) } else { return nil } } - public static func parse_messageMediaGeoLive(_ reader: BufferReader) -> MessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.GeoPoint? + public static func parse_messageActionHistoryClear(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionHistoryClear + } + public static func parse_messageActionInviteToGroupCall(_ reader: BufferReader) -> MessageAction? { + var _1: Api.InputGroupCall? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: [Int64]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageMedia.messageMediaGeoLive(flags: _1!, geo: _2!, heading: _3, period: _4!, proximityNotificationRadius: _5) + if _c1 && _c2 { + return Api.MessageAction.messageActionInviteToGroupCall(call: _1!, users: _2!) } else { return nil } } - public static func parse_messageMediaGiveaway(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionPaymentSent(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: [Int64]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionPaymentSent(flags: _1!, currency: _2!, totalAmount: _3!, invoiceSlug: _4) + } + else { + return nil } - var _3: [String]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + public static func parse_messageActionPaymentSentMe(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Api.PaymentRequestedInfo? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo } } - var _4: String? - if Int(_1!) & Int(1 << 3) != 0 {_4 = parseString(reader) } - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() + var _6: String? + if Int(_1!) & Int(1 << 1) != 0 {_6 = parseString(reader) } + var _7: Api.PaymentCharge? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.PaymentCharge + } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c7 = _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageMedia.messageMediaGiveaway(flags: _1!, channels: _2!, countriesIso2: _3, prizeDescription: _4, quantity: _5!, months: _6!, untilDate: _7!) + return Api.MessageAction.messageActionPaymentSentMe(flags: _1!, currency: _2!, totalAmount: _3!, payload: _4!, info: _5, shippingOptionId: _6, charge: _7!) } else { return nil } } - public static func parse_messageMediaGiveawayResults(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionPhoneCall(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_3 = reader.readInt32() } + var _3: Api.PhoneCallDiscardReason? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.PhoneCallDiscardReason + } } var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: [Int64]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - var _8: Int32? - _8 = reader.readInt32() - var _9: String? - if Int(_1!) & Int(1 << 1) != 0 {_9 = parseString(reader) } - var _10: Int32? - _10 = reader.readInt32() + if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil - let _c10 = _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.MessageMedia.messageMediaGiveawayResults(flags: _1!, channelId: _2!, additionalPeersCount: _3, launchMsgId: _4!, winnersCount: _5!, unclaimedCount: _6!, winners: _7!, months: _8!, prizeDescription: _9, untilDate: _10!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionPhoneCall(flags: _1!, callId: _2!, reason: _3, duration: _4) } else { return nil } } - public static func parse_messageMediaInvoice(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionPinMessage(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionPinMessage + } + public static func parse_messageActionRequestedPeer(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: Api.WebDocument? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.WebDocument - } } - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } - var _6: String? - _6 = parseString(reader) - var _7: Int64? - _7 = reader.readInt64() - var _8: String? - _8 = parseString(reader) - var _9: Api.MessageExtendedMedia? - if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageExtendedMedia - } } + var _2: [Api.Peer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.MessageMedia.messageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, receiptMsgId: _5, currency: _6!, totalAmount: _7!, startParam: _8!, extendedMedia: _9) + if _c1 && _c2 { + return Api.MessageAction.messageActionRequestedPeer(buttonId: _1!, peers: _2!) } else { return nil } } - public static func parse_messageMediaPhoto(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionRequestedPeerSentMe(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Photo? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Photo - } } - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } + var _2: [Api.RequestedPeer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RequestedPeer.self) + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageMedia.messageMediaPhoto(flags: _1!, photo: _2, ttlSeconds: _3) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionRequestedPeerSentMe(buttonId: _1!, peers: _2!) } else { return nil } } - public static func parse_messageMediaPoll(_ reader: BufferReader) -> MessageMedia? { - var _1: Api.Poll? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Poll + public static func parse_messageActionScreenshotTaken(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionScreenshotTaken + } + public static func parse_messageActionSecureValuesSent(_ reader: BufferReader) -> MessageAction? { + var _1: [Api.SecureValueType]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValueType.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSecureValuesSent(types: _1!) + } + else { + return nil + } + } + public static func parse_messageActionSecureValuesSentMe(_ reader: BufferReader) -> MessageAction? { + var _1: [Api.SecureValue]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) } - var _2: Api.PollResults? + var _2: Api.SecureCredentialsEncrypted? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PollResults + _2 = Api.parse(reader, signature: signature) as? Api.SecureCredentialsEncrypted } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageMedia.messageMediaPoll(poll: _1!, results: _2!) + return Api.MessageAction.messageActionSecureValuesSentMe(values: _1!, credentials: _2!) + } + else { + return nil + } + } + public static func parse_messageActionSetChatTheme(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSetChatTheme(emoticon: _1!) } else { return nil } } - public static func parse_messageMediaStory(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionSetChatWallPaper(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Peer? + var _2: Api.WallPaper? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer + _2 = Api.parse(reader, signature: signature) as? Api.WallPaper } - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.StoryItem? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StoryItem - } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageMedia.messageMediaStory(flags: _1!, peer: _2!, id: _3!, story: _4) + if _c1 && _c2 { + return Api.MessageAction.messageActionSetChatWallPaper(flags: _1!, wallpaper: _2!) } else { return nil } } - public static func parse_messageMediaUnsupported(_ reader: BufferReader) -> MessageMedia? { - return Api.MessageMedia.messageMediaUnsupported + public static func parse_messageActionSetMessagesTTL(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt64() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionSetMessagesTTL(flags: _1!, period: _2!, autoSettingFrom: _3) + } + else { + return nil + } } - public static func parse_messageMediaVenue(_ reader: BufferReader) -> MessageMedia? { - var _1: Api.GeoPoint? + public static func parse_messageActionSuggestProfilePhoto(_ reader: BufferReader) -> MessageAction? { + var _1: Api.Photo? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint + _1 = Api.parse(reader, signature: signature) as? Api.Photo + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSuggestProfilePhoto(photo: _1!) + } + else { + return nil } + } + public static func parse_messageActionTopicCreate(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() var _2: String? _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageMedia.messageMediaVenue(geo: _1!, title: _2!, address: _3!, provider: _4!, venueId: _5!, venueType: _6!) + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionTopicCreate(flags: _1!, title: _2!, iconColor: _3!, iconEmojiId: _4) } else { return nil } } - public static func parse_messageMediaWebPage(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionTopicEdit(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.WebPage? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.WebPage + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + var _3: Int64? + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt64() } + var _4: Api.Bool? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _5: Api.Bool? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Bool + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageAction.messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4, hidden: _5) + } + else { + return nil + } + } + public static func parse_messageActionWebViewDataSent(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionWebViewDataSent(text: _1!) } + else { + return nil + } + } + public static func parse_messageActionWebViewDataSentMe(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageMedia.messageMediaWebPage(flags: _1!, webpage: _2!) + return Api.MessageAction.messageActionWebViewDataSentMe(text: _1!, data: _2!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 85ed7f414c4..3924397ce3a 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -1,187 +1,283 @@ public extension Api { - enum MessagePeerReaction: TypeConstructorDescription { - case messagePeerReaction(flags: Int32, peerId: Api.Peer, date: Int32, reaction: Api.Reaction) + indirect enum MessageEntity: TypeConstructorDescription { + case inputMessageEntityMentionName(offset: Int32, length: Int32, userId: Api.InputUser) + case messageEntityBankCard(offset: Int32, length: Int32) + case messageEntityBlockquote(flags: Int32, offset: Int32, length: Int32) + case messageEntityBold(offset: Int32, length: Int32) + case messageEntityBotCommand(offset: Int32, length: Int32) + case messageEntityCashtag(offset: Int32, length: Int32) + case messageEntityCode(offset: Int32, length: Int32) + case messageEntityCustomEmoji(offset: Int32, length: Int32, documentId: Int64) + case messageEntityEmail(offset: Int32, length: Int32) + case messageEntityHashtag(offset: Int32, length: Int32) + case messageEntityItalic(offset: Int32, length: Int32) + case messageEntityMention(offset: Int32, length: Int32) + case messageEntityMentionName(offset: Int32, length: Int32, userId: Int64) + case messageEntityPhone(offset: Int32, length: Int32) + case messageEntityPre(offset: Int32, length: Int32, language: String) + case messageEntitySpoiler(offset: Int32, length: Int32) + case messageEntityStrike(offset: Int32, length: Int32) + case messageEntityTextUrl(offset: Int32, length: Int32, url: String) + case messageEntityUnderline(offset: Int32, length: Int32) + case messageEntityUnknown(offset: Int32, length: Int32) + case messageEntityUrl(offset: Int32, length: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messagePeerReaction(let flags, let peerId, let date, let reaction): + case .inputMessageEntityMentionName(let offset, let length, let userId): if boxed { - buffer.appendInt32(-1938180548) + buffer.appendInt32(546203849) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + break + case .messageEntityBankCard(let offset, let length): + if boxed { + buffer.appendInt32(1981704948) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBlockquote(let flags, let offset, let length): + if boxed { + buffer.appendInt32(-238245204) } serializeInt32(flags, buffer: buffer, boxed: false) - peerId.serialize(buffer, true) - serializeInt32(date, buffer: buffer, boxed: false) - reaction.serialize(buffer, true) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messagePeerReaction(let flags, let peerId, let date, let reaction): - return ("messagePeerReaction", [("flags", flags as Any), ("peerId", peerId as Any), ("date", date as Any), ("reaction", reaction as Any)]) - } - } - - public static func parse_messagePeerReaction(_ reader: BufferReader) -> MessagePeerReaction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Reaction? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Reaction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessagePeerReaction.messagePeerReaction(flags: _1!, peerId: _2!, date: _3!, reaction: _4!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum MessagePeerVote: TypeConstructorDescription { - case messagePeerVote(peer: Api.Peer, option: Buffer, date: Int32) - case messagePeerVoteInputOption(peer: Api.Peer, date: Int32) - case messagePeerVoteMultiple(peer: Api.Peer, options: [Buffer], date: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messagePeerVote(let peer, let option, let date): + case .messageEntityBold(let offset, let length): if boxed { - buffer.appendInt32(-1228133028) + buffer.appendInt32(-1117713463) } - peer.serialize(buffer, true) - serializeBytes(option, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) break - case .messagePeerVoteInputOption(let peer, let date): + case .messageEntityBotCommand(let offset, let length): if boxed { - buffer.appendInt32(1959634180) + buffer.appendInt32(1827637959) } - peer.serialize(buffer, true) - serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) break - case .messagePeerVoteMultiple(let peer, let options, let date): + case .messageEntityCashtag(let offset, let length): if boxed { - buffer.appendInt32(1177089766) + buffer.appendInt32(1280209983) } - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(options.count)) - for item in options { - serializeBytes(item, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityCode(let offset, let length): + if boxed { + buffer.appendInt32(681706865) } - serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityCustomEmoji(let offset, let length, let documentId): + if boxed { + buffer.appendInt32(-925956616) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeInt64(documentId, buffer: buffer, boxed: false) + break + case .messageEntityEmail(let offset, let length): + if boxed { + buffer.appendInt32(1692693954) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityHashtag(let offset, let length): + if boxed { + buffer.appendInt32(1868782349) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityItalic(let offset, let length): + if boxed { + buffer.appendInt32(-2106619040) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityMention(let offset, let length): + if boxed { + buffer.appendInt32(-100378723) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityMentionName(let offset, let length, let userId): + if boxed { + buffer.appendInt32(-595914432) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + break + case .messageEntityPhone(let offset, let length): + if boxed { + buffer.appendInt32(-1687559349) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityPre(let offset, let length, let language): + if boxed { + buffer.appendInt32(1938967520) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(language, buffer: buffer, boxed: false) + break + case .messageEntitySpoiler(let offset, let length): + if boxed { + buffer.appendInt32(852137487) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityStrike(let offset, let length): + if boxed { + buffer.appendInt32(-1090087980) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityTextUrl(let offset, let length, let url): + if boxed { + buffer.appendInt32(1990644519) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + case .messageEntityUnderline(let offset, let length): + if boxed { + buffer.appendInt32(-1672577397) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUnknown(let offset, let length): + if boxed { + buffer.appendInt32(-1148011883) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUrl(let offset, let length): + if boxed { + buffer.appendInt32(1859134776) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messagePeerVote(let peer, let option, let date): - return ("messagePeerVote", [("peer", peer as Any), ("option", option as Any), ("date", date as Any)]) - case .messagePeerVoteInputOption(let peer, let date): - return ("messagePeerVoteInputOption", [("peer", peer as Any), ("date", date as Any)]) - case .messagePeerVoteMultiple(let peer, let options, let date): - return ("messagePeerVoteMultiple", [("peer", peer as Any), ("options", options as Any), ("date", date as Any)]) + case .inputMessageEntityMentionName(let offset, let length, let userId): + return ("inputMessageEntityMentionName", [("offset", offset as Any), ("length", length as Any), ("userId", userId as Any)]) + case .messageEntityBankCard(let offset, let length): + return ("messageEntityBankCard", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityBlockquote(let flags, let offset, let length): + return ("messageEntityBlockquote", [("flags", flags as Any), ("offset", offset as Any), ("length", length as Any)]) + case .messageEntityBold(let offset, let length): + return ("messageEntityBold", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityBotCommand(let offset, let length): + return ("messageEntityBotCommand", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityCashtag(let offset, let length): + return ("messageEntityCashtag", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityCode(let offset, let length): + return ("messageEntityCode", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityCustomEmoji(let offset, let length, let documentId): + return ("messageEntityCustomEmoji", [("offset", offset as Any), ("length", length as Any), ("documentId", documentId as Any)]) + case .messageEntityEmail(let offset, let length): + return ("messageEntityEmail", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityHashtag(let offset, let length): + return ("messageEntityHashtag", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityItalic(let offset, let length): + return ("messageEntityItalic", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityMention(let offset, let length): + return ("messageEntityMention", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityMentionName(let offset, let length, let userId): + return ("messageEntityMentionName", [("offset", offset as Any), ("length", length as Any), ("userId", userId as Any)]) + case .messageEntityPhone(let offset, let length): + return ("messageEntityPhone", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityPre(let offset, let length, let language): + return ("messageEntityPre", [("offset", offset as Any), ("length", length as Any), ("language", language as Any)]) + case .messageEntitySpoiler(let offset, let length): + return ("messageEntitySpoiler", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityStrike(let offset, let length): + return ("messageEntityStrike", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityTextUrl(let offset, let length, let url): + return ("messageEntityTextUrl", [("offset", offset as Any), ("length", length as Any), ("url", url as Any)]) + case .messageEntityUnderline(let offset, let length): + return ("messageEntityUnderline", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityUnknown(let offset, let length): + return ("messageEntityUnknown", [("offset", offset as Any), ("length", length as Any)]) + case .messageEntityUrl(let offset, let length): + return ("messageEntityUrl", [("offset", offset as Any), ("length", length as Any)]) } } - public static func parse_messagePeerVote(_ reader: BufferReader) -> MessagePeerVote? { - var _1: Api.Peer? + public static func parse_inputMessageEntityMentionName(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.InputUser? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + _3 = Api.parse(reader, signature: signature) as? Api.InputUser } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Int32? - _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.MessagePeerVote.messagePeerVote(peer: _1!, option: _2!, date: _3!) + return Api.MessageEntity.inputMessageEntityMentionName(offset: _1!, length: _2!, userId: _3!) } else { return nil } } - public static func parse_messagePeerVoteInputOption(_ reader: BufferReader) -> MessagePeerVote? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } + public static func parse_messageEntityBankCard(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessagePeerVote.messagePeerVoteInputOption(peer: _1!, date: _2!) + return Api.MessageEntity.messageEntityBankCard(offset: _1!, length: _2!) } else { return nil } } - public static func parse_messagePeerVoteMultiple(_ reader: BufferReader) -> MessagePeerVote? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: [Buffer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) - } + public static func parse_messageEntityBlockquote(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.MessagePeerVote.messagePeerVoteMultiple(peer: _1!, options: _2!, date: _3!) + return Api.MessageEntity.messageEntityBlockquote(flags: _1!, offset: _2!, length: _3!) } else { return nil } } - - } -} -public extension Api { - enum MessageRange: TypeConstructorDescription { - case messageRange(minId: Int32, maxId: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageRange(let minId, let maxId): - if boxed { - buffer.appendInt32(182649427) - } - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageRange(let minId, let maxId): - return ("messageRange", [("minId", minId as Any), ("maxId", maxId as Any)]) - } - } - - public static func parse_messageRange(_ reader: BufferReader) -> MessageRange? { + public static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? @@ -189,235 +285,257 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageRange.messageRange(minId: _1!, maxId: _2!) + return Api.MessageEntity.messageEntityBold(offset: _1!, length: _2!) } else { return nil } } - - } -} -public extension Api { - enum MessageReactions: TypeConstructorDescription { - case messageReactions(flags: Int32, results: [Api.ReactionCount], recentReactions: [Api.MessagePeerReaction]?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageReactions(let flags, let results, let recentReactions): - if boxed { - buffer.appendInt32(1328256121) - } - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(results.count)) - for item in results { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentReactions!.count)) - for item in recentReactions! { - item.serialize(buffer, true) - }} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageReactions(let flags, let results, let recentReactions): - return ("messageReactions", [("flags", flags as Any), ("results", results as Any), ("recentReactions", recentReactions as Any)]) - } - } - - public static func parse_messageReactions(_ reader: BufferReader) -> MessageReactions? { + public static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.ReactionCount]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) } - var _3: [Api.MessagePeerReaction]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self) - } } + else { + return nil + } + } + public static func parse_messageEntityCashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageReactions.messageReactions(flags: _1!, results: _2!, recentReactions: _3) + if _c1 && _c2 { + return Api.MessageEntity.messageEntityCashtag(offset: _1!, length: _2!) } else { return nil } } - - } -} -public extension Api { - enum MessageReplies: TypeConstructorDescription { - case messageReplies(flags: Int32, replies: Int32, repliesPts: Int32, recentRepliers: [Api.Peer]?, channelId: Int64?, maxId: Int32?, readMaxId: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let maxId, let readMaxId): - if boxed { - buffer.appendInt32(-2083123262) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(replies, buffer: buffer, boxed: false) - serializeInt32(repliesPts, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentRepliers!.count)) - for item in recentRepliers! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(channelId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(maxId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(readMaxId!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let maxId, let readMaxId): - return ("messageReplies", [("flags", flags as Any), ("replies", replies as Any), ("repliesPts", repliesPts as Any), ("recentRepliers", recentRepliers as Any), ("channelId", channelId as Any), ("maxId", maxId as Any), ("readMaxId", readMaxId as Any)]) - } - } - - public static func parse_messageReplies(_ reader: BufferReader) -> MessageReplies? { + public static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Api.Peer]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } } - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } - var _6: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt32() } - var _7: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_7 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageReplies.messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5, maxId: _6, readMaxId: _7) + if _c1 && _c2 { + return Api.MessageEntity.messageEntityCode(offset: _1!, length: _2!) } else { return nil } } - - } -} -public extension Api { - indirect enum MessageReplyHeader: TypeConstructorDescription { - case messageReplyHeader(flags: Int32, replyToMsgId: Int32?, replyToPeerId: Api.Peer?, replyFrom: Api.MessageFwdHeader?, replyMedia: Api.MessageMedia?, replyToTopId: Int32?, quoteText: String?, quoteEntities: [Api.MessageEntity]?, quoteOffset: Int32?) - case messageReplyStoryHeader(peer: Api.Peer, storyId: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyFrom, let replyMedia, let replyToTopId, let quoteText, let quoteEntities, let quoteOffset): - if boxed { - buffer.appendInt32(-1346631205) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {replyToPeerId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 5) != 0 {replyFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 8) != 0 {replyMedia!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(replyToTopId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 6) != 0 {serializeString(quoteText!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(quoteEntities!.count)) - for item in quoteEntities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(quoteOffset!, buffer: buffer, boxed: false)} - break - case .messageReplyStoryHeader(let peer, let storyId): - if boxed { - buffer.appendInt32(240843065) - } - peer.serialize(buffer, true) - serializeInt32(storyId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyFrom, let replyMedia, let replyToTopId, let quoteText, let quoteEntities, let quoteOffset): - return ("messageReplyHeader", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("replyFrom", replyFrom as Any), ("replyMedia", replyMedia as Any), ("replyToTopId", replyToTopId as Any), ("quoteText", quoteText as Any), ("quoteEntities", quoteEntities as Any), ("quoteOffset", quoteOffset as Any)]) - case .messageReplyStoryHeader(let peer, let storyId): - return ("messageReplyStoryHeader", [("peer", peer as Any), ("storyId", storyId as Any)]) - } - } - - public static func parse_messageReplyHeader(_ reader: BufferReader) -> MessageReplyHeader? { + public static func parse_messageEntityCustomEmoji(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_2 = reader.readInt32() } - var _3: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _4: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } } - var _5: Api.MessageMedia? - if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } } - var _6: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_6 = reader.readInt32() } - var _7: String? - if Int(_1!) & Int(1 << 6) != 0 {_7 = parseString(reader) } - var _8: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - var _9: Int32? - if Int(_1!) & Int(1 << 10) != 0 {_9 = reader.readInt32() } + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 5) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.MessageReplyHeader.messageReplyHeader(flags: _1!, replyToMsgId: _2, replyToPeerId: _3, replyFrom: _4, replyMedia: _5, replyToTopId: _6, quoteText: _7, quoteEntities: _8, quoteOffset: _9) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.messageEntityCustomEmoji(offset: _1!, length: _2!, documentId: _3!) } else { return nil } } - public static func parse_messageReplyStoryHeader(_ reader: BufferReader) -> MessageReplyHeader? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + public static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) + } + else { + return nil } + } + public static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageReplyHeader.messageReplyStoryHeader(peer: _1!, storyId: _2!) + return Api.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) + } + else { + return nil + } + } + public static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) + } + else { + return nil + } + } + public static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityMention(offset: _1!, length: _2!) + } + else { + return nil + } + } + public static func parse_messageEntityMentionName(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.messageEntityMentionName(offset: _1!, length: _2!, userId: _3!) + } + else { + return nil + } + } + public static func parse_messageEntityPhone(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityPhone(offset: _1!, length: _2!) + } + else { + return nil + } + } + public static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) + } + else { + return nil + } + } + public static func parse_messageEntitySpoiler(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntitySpoiler(offset: _1!, length: _2!) + } + else { + return nil + } + } + public static func parse_messageEntityStrike(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityStrike(offset: _1!, length: _2!) + } + else { + return nil + } + } + public static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) + } + else { + return nil + } + } + public static func parse_messageEntityUnderline(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityUnderline(offset: _1!, length: _2!) + } + else { + return nil + } + } + public static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) + } + else { + return nil + } + } + public static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) } else { return nil @@ -427,47 +545,73 @@ public extension Api { } } public extension Api { - enum MessageViews: TypeConstructorDescription { - case messageViews(flags: Int32, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?) + indirect enum MessageExtendedMedia: TypeConstructorDescription { + case messageExtendedMedia(media: Api.MessageMedia) + case messageExtendedMediaPreview(flags: Int32, w: Int32?, h: Int32?, thumb: Api.PhotoSize?, videoDuration: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageViews(let flags, let views, let forwards, let replies): + case .messageExtendedMedia(let media): if boxed { - buffer.appendInt32(1163625789) + buffer.appendInt32(-297296796) + } + media.serialize(buffer, true) + break + case .messageExtendedMediaPreview(let flags, let w, let h, let thumb, let videoDuration): + if boxed { + buffer.appendInt32(-1386050360) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {replies!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(w!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(h!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {thumb!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(videoDuration!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageViews(let flags, let views, let forwards, let replies): - return ("messageViews", [("flags", flags as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any)]) + case .messageExtendedMedia(let media): + return ("messageExtendedMedia", [("media", media as Any)]) + case .messageExtendedMediaPreview(let flags, let w, let h, let thumb, let videoDuration): + return ("messageExtendedMediaPreview", [("flags", flags as Any), ("w", w as Any), ("h", h as Any), ("thumb", thumb as Any), ("videoDuration", videoDuration as Any)]) } } - public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { + public static func parse_messageExtendedMedia(_ reader: BufferReader) -> MessageExtendedMedia? { + var _1: Api.MessageMedia? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageExtendedMedia.messageExtendedMedia(media: _1!) + } + else { + return nil + } + } + public static func parse_messageExtendedMediaPreview(_ reader: BufferReader) -> MessageExtendedMedia? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } - var _4: Api.MessageReplies? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.MessageReplies + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: Api.PhotoSize? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.PhotoSize } } + var _5: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageViews.messageViews(flags: _1!, views: _2, forwards: _3, replies: _4) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageExtendedMedia.messageExtendedMediaPreview(flags: _1!, w: _2, h: _3, thumb: _4, videoDuration: _5) } else { return nil @@ -477,451 +621,637 @@ public extension Api { } } public extension Api { - enum MessagesFilter: TypeConstructorDescription { - case inputMessagesFilterChatPhotos - case inputMessagesFilterContacts - case inputMessagesFilterDocument - case inputMessagesFilterEmpty - case inputMessagesFilterGeo - case inputMessagesFilterGif - case inputMessagesFilterMusic - case inputMessagesFilterMyMentions - case inputMessagesFilterPhoneCalls(flags: Int32) - case inputMessagesFilterPhotoVideo - case inputMessagesFilterPhotos - case inputMessagesFilterPinned - case inputMessagesFilterRoundVideo - case inputMessagesFilterRoundVoice - case inputMessagesFilterUrl - case inputMessagesFilterVideo - case inputMessagesFilterVoice + enum MessageFwdHeader: TypeConstructorDescription { + case messageFwdHeader(flags: Int32, fromId: Api.Peer?, fromName: String?, date: Int32, channelPost: Int32?, postAuthor: String?, savedFromPeer: Api.Peer?, savedFromMsgId: Int32?, savedFromId: Api.Peer?, savedFromName: String?, savedDate: Int32?, psaType: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .inputMessagesFilterChatPhotos: + case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let savedFromId, let savedFromName, let savedDate, let psaType): if boxed { - buffer.appendInt32(975236280) + buffer.appendInt32(1313731771) } - + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 5) != 0 {serializeString(fromName!, buffer: buffer, boxed: false)} + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(channelPost!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {savedFromPeer!.serialize(buffer, true)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(savedFromMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 8) != 0 {savedFromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 9) != 0 {serializeString(savedFromName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(savedDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {serializeString(psaType!, buffer: buffer, boxed: false)} break - case .inputMessagesFilterContacts: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let savedFromId, let savedFromName, let savedDate, let psaType): + return ("messageFwdHeader", [("flags", flags as Any), ("fromId", fromId as Any), ("fromName", fromName as Any), ("date", date as Any), ("channelPost", channelPost as Any), ("postAuthor", postAuthor as Any), ("savedFromPeer", savedFromPeer as Any), ("savedFromMsgId", savedFromMsgId as Any), ("savedFromId", savedFromId as Any), ("savedFromName", savedFromName as Any), ("savedDate", savedDate as Any), ("psaType", psaType as Any)]) + } + } + + public static func parse_messageFwdHeader(_ reader: BufferReader) -> MessageFwdHeader? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _3: String? + if Int(_1!) & Int(1 << 5) != 0 {_3 = parseString(reader) } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } + var _6: String? + if Int(_1!) & Int(1 << 3) != 0 {_6 = parseString(reader) } + var _7: Api.Peer? + if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _8: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() } + var _9: Api.Peer? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _10: String? + if Int(_1!) & Int(1 << 9) != 0 {_10 = parseString(reader) } + var _11: Int32? + if Int(_1!) & Int(1 << 10) != 0 {_11 = reader.readInt32() } + var _12: String? + if Int(_1!) & Int(1 << 6) != 0 {_12 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 9) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 10) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.MessageFwdHeader.messageFwdHeader(flags: _1!, fromId: _2, fromName: _3, date: _4!, channelPost: _5, postAuthor: _6, savedFromPeer: _7, savedFromMsgId: _8, savedFromId: _9, savedFromName: _10, savedDate: _11, psaType: _12) + } + else { + return nil + } + } + + } +} +public extension Api { + indirect enum MessageMedia: TypeConstructorDescription { + case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64) + case messageMediaDice(value: Int32, emoticon: String) + case messageMediaDocument(flags: Int32, document: Api.Document?, altDocument: Api.Document?, ttlSeconds: Int32?) + case messageMediaEmpty + case messageMediaGame(game: Api.Game) + case messageMediaGeo(geo: Api.GeoPoint) + case messageMediaGeoLive(flags: Int32, geo: Api.GeoPoint, heading: Int32?, period: Int32, proximityNotificationRadius: Int32?) + case messageMediaGiveaway(flags: Int32, channels: [Int64], countriesIso2: [String]?, prizeDescription: String?, quantity: Int32, months: Int32, untilDate: Int32) + case messageMediaGiveawayResults(flags: Int32, channelId: Int64, additionalPeersCount: Int32?, launchMsgId: Int32, winnersCount: Int32, unclaimedCount: Int32, winners: [Int64], months: Int32, prizeDescription: String?, untilDate: Int32) + case messageMediaInvoice(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?) + case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?) + case messageMediaPoll(poll: Api.Poll, results: Api.PollResults) + case messageMediaStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) + case messageMediaUnsupported + case messageMediaVenue(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) + case messageMediaWebPage(flags: Int32, webpage: Api.WebPage) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageMediaContact(let phoneNumber, let firstName, let lastName, let vcard, let userId): if boxed { - buffer.appendInt32(-530392189) + buffer.appendInt32(1882335561) } - + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeString(vcard, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) break - case .inputMessagesFilterDocument: + case .messageMediaDice(let value, let emoticon): if boxed { - buffer.appendInt32(-1629621880) + buffer.appendInt32(1065280907) } - + serializeInt32(value, buffer: buffer, boxed: false) + serializeString(emoticon, buffer: buffer, boxed: false) break - case .inputMessagesFilterEmpty: + case .messageMediaDocument(let flags, let document, let altDocument, let ttlSeconds): if boxed { - buffer.appendInt32(1474492012) + buffer.appendInt32(1291114285) } - + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {document!.serialize(buffer, true)} + if Int(flags) & Int(1 << 5) != 0 {altDocument!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} break - case .inputMessagesFilterGeo: + case .messageMediaEmpty: if boxed { - buffer.appendInt32(-419271411) + buffer.appendInt32(1038967584) } break - case .inputMessagesFilterGif: + case .messageMediaGame(let game): if boxed { - buffer.appendInt32(-3644025) + buffer.appendInt32(-38694904) } - + game.serialize(buffer, true) break - case .inputMessagesFilterMusic: + case .messageMediaGeo(let geo): if boxed { - buffer.appendInt32(928101534) + buffer.appendInt32(1457575028) } - + geo.serialize(buffer, true) break - case .inputMessagesFilterMyMentions: + case .messageMediaGeoLive(let flags, let geo, let heading, let period, let proximityNotificationRadius): if boxed { - buffer.appendInt32(-1040652646) + buffer.appendInt32(-1186937242) } - + serializeInt32(flags, buffer: buffer, boxed: false) + geo.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(heading!, buffer: buffer, boxed: false)} + serializeInt32(period, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(proximityNotificationRadius!, buffer: buffer, boxed: false)} break - case .inputMessagesFilterPhoneCalls(let flags): + case .messageMediaGiveaway(let flags, let channels, let countriesIso2, let prizeDescription, let quantity, let months, let untilDate): if boxed { - buffer.appendInt32(-2134272152) + buffer.appendInt32(-626162256) } serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(channels.count)) + for item in channels { + serializeInt64(item, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(countriesIso2!.count)) + for item in countriesIso2! { + serializeString(item, buffer: buffer, boxed: false) + }} + if Int(flags) & Int(1 << 3) != 0 {serializeString(prizeDescription!, buffer: buffer, boxed: false)} + serializeInt32(quantity, buffer: buffer, boxed: false) + serializeInt32(months, buffer: buffer, boxed: false) + serializeInt32(untilDate, buffer: buffer, boxed: false) break - case .inputMessagesFilterPhotoVideo: + case .messageMediaGiveawayResults(let flags, let channelId, let additionalPeersCount, let launchMsgId, let winnersCount, let unclaimedCount, let winners, let months, let prizeDescription, let untilDate): if boxed { - buffer.appendInt32(1458172132) + buffer.appendInt32(-963047320) } - + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(additionalPeersCount!, buffer: buffer, boxed: false)} + serializeInt32(launchMsgId, buffer: buffer, boxed: false) + serializeInt32(winnersCount, buffer: buffer, boxed: false) + serializeInt32(unclaimedCount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(winners.count)) + for item in winners { + serializeInt64(item, buffer: buffer, boxed: false) + } + serializeInt32(months, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(prizeDescription!, buffer: buffer, boxed: false)} + serializeInt32(untilDate, buffer: buffer, boxed: false) break - case .inputMessagesFilterPhotos: + case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia): if boxed { - buffer.appendInt32(-1777752804) + buffer.appendInt32(-156940077) } - + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {photo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(receiptMsgId!, buffer: buffer, boxed: false)} + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(totalAmount, buffer: buffer, boxed: false) + serializeString(startParam, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 {extendedMedia!.serialize(buffer, true)} break - case .inputMessagesFilterPinned: + case .messageMediaPhoto(let flags, let photo, let ttlSeconds): if boxed { - buffer.appendInt32(464520273) + buffer.appendInt32(1766936791) } - + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {photo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} break - case .inputMessagesFilterRoundVideo: + case .messageMediaPoll(let poll, let results): if boxed { - buffer.appendInt32(-1253451181) + buffer.appendInt32(1272375192) } - + poll.serialize(buffer, true) + results.serialize(buffer, true) break - case .inputMessagesFilterRoundVoice: + case .messageMediaStory(let flags, let peer, let id, let story): if boxed { - buffer.appendInt32(2054952868) + buffer.appendInt32(1758159491) } - + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {story!.serialize(buffer, true)} break - case .inputMessagesFilterUrl: + case .messageMediaUnsupported: if boxed { - buffer.appendInt32(2129714567) + buffer.appendInt32(-1618676578) } break - case .inputMessagesFilterVideo: + case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType): if boxed { - buffer.appendInt32(-1614803355) + buffer.appendInt32(784356159) } - + geo.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + serializeString(address, buffer: buffer, boxed: false) + serializeString(provider, buffer: buffer, boxed: false) + serializeString(venueId, buffer: buffer, boxed: false) + serializeString(venueType, buffer: buffer, boxed: false) break - case .inputMessagesFilterVoice: + case .messageMediaWebPage(let flags, let webpage): if boxed { - buffer.appendInt32(1358283666) + buffer.appendInt32(-571405253) } - + serializeInt32(flags, buffer: buffer, boxed: false) + webpage.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .inputMessagesFilterChatPhotos: - return ("inputMessagesFilterChatPhotos", []) - case .inputMessagesFilterContacts: - return ("inputMessagesFilterContacts", []) - case .inputMessagesFilterDocument: - return ("inputMessagesFilterDocument", []) - case .inputMessagesFilterEmpty: - return ("inputMessagesFilterEmpty", []) - case .inputMessagesFilterGeo: - return ("inputMessagesFilterGeo", []) - case .inputMessagesFilterGif: - return ("inputMessagesFilterGif", []) - case .inputMessagesFilterMusic: - return ("inputMessagesFilterMusic", []) - case .inputMessagesFilterMyMentions: - return ("inputMessagesFilterMyMentions", []) - case .inputMessagesFilterPhoneCalls(let flags): - return ("inputMessagesFilterPhoneCalls", [("flags", flags as Any)]) - case .inputMessagesFilterPhotoVideo: - return ("inputMessagesFilterPhotoVideo", []) - case .inputMessagesFilterPhotos: - return ("inputMessagesFilterPhotos", []) - case .inputMessagesFilterPinned: - return ("inputMessagesFilterPinned", []) - case .inputMessagesFilterRoundVideo: - return ("inputMessagesFilterRoundVideo", []) - case .inputMessagesFilterRoundVoice: - return ("inputMessagesFilterRoundVoice", []) - case .inputMessagesFilterUrl: - return ("inputMessagesFilterUrl", []) - case .inputMessagesFilterVideo: - return ("inputMessagesFilterVideo", []) - case .inputMessagesFilterVoice: - return ("inputMessagesFilterVoice", []) + case .messageMediaContact(let phoneNumber, let firstName, let lastName, let vcard, let userId): + return ("messageMediaContact", [("phoneNumber", phoneNumber as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("vcard", vcard as Any), ("userId", userId as Any)]) + case .messageMediaDice(let value, let emoticon): + return ("messageMediaDice", [("value", value as Any), ("emoticon", emoticon as Any)]) + case .messageMediaDocument(let flags, let document, let altDocument, let ttlSeconds): + return ("messageMediaDocument", [("flags", flags as Any), ("document", document as Any), ("altDocument", altDocument as Any), ("ttlSeconds", ttlSeconds as Any)]) + case .messageMediaEmpty: + return ("messageMediaEmpty", []) + case .messageMediaGame(let game): + return ("messageMediaGame", [("game", game as Any)]) + case .messageMediaGeo(let geo): + return ("messageMediaGeo", [("geo", geo as Any)]) + case .messageMediaGeoLive(let flags, let geo, let heading, let period, let proximityNotificationRadius): + return ("messageMediaGeoLive", [("flags", flags as Any), ("geo", geo as Any), ("heading", heading as Any), ("period", period as Any), ("proximityNotificationRadius", proximityNotificationRadius as Any)]) + case .messageMediaGiveaway(let flags, let channels, let countriesIso2, let prizeDescription, let quantity, let months, let untilDate): + return ("messageMediaGiveaway", [("flags", flags as Any), ("channels", channels as Any), ("countriesIso2", countriesIso2 as Any), ("prizeDescription", prizeDescription as Any), ("quantity", quantity as Any), ("months", months as Any), ("untilDate", untilDate as Any)]) + case .messageMediaGiveawayResults(let flags, let channelId, let additionalPeersCount, let launchMsgId, let winnersCount, let unclaimedCount, let winners, let months, let prizeDescription, let untilDate): + return ("messageMediaGiveawayResults", [("flags", flags as Any), ("channelId", channelId as Any), ("additionalPeersCount", additionalPeersCount as Any), ("launchMsgId", launchMsgId as Any), ("winnersCount", winnersCount as Any), ("unclaimedCount", unclaimedCount as Any), ("winners", winners as Any), ("months", months as Any), ("prizeDescription", prizeDescription as Any), ("untilDate", untilDate as Any)]) + case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia): + return ("messageMediaInvoice", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("receiptMsgId", receiptMsgId as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("startParam", startParam as Any), ("extendedMedia", extendedMedia as Any)]) + case .messageMediaPhoto(let flags, let photo, let ttlSeconds): + return ("messageMediaPhoto", [("flags", flags as Any), ("photo", photo as Any), ("ttlSeconds", ttlSeconds as Any)]) + case .messageMediaPoll(let poll, let results): + return ("messageMediaPoll", [("poll", poll as Any), ("results", results as Any)]) + case .messageMediaStory(let flags, let peer, let id, let story): + return ("messageMediaStory", [("flags", flags as Any), ("peer", peer as Any), ("id", id as Any), ("story", story as Any)]) + case .messageMediaUnsupported: + return ("messageMediaUnsupported", []) + case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType): + return ("messageMediaVenue", [("geo", geo as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)]) + case .messageMediaWebPage(let flags, let webpage): + return ("messageMediaWebPage", [("flags", flags as Any), ("webpage", webpage as Any)]) } } - public static func parse_inputMessagesFilterChatPhotos(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterChatPhotos - } - public static func parse_inputMessagesFilterContacts(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterContacts - } - public static func parse_inputMessagesFilterDocument(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterDocument - } - public static func parse_inputMessagesFilterEmpty(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterEmpty - } - public static func parse_inputMessagesFilterGeo(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterGeo - } - public static func parse_inputMessagesFilterGif(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterGif - } - public static func parse_inputMessagesFilterMusic(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterMusic - } - public static func parse_inputMessagesFilterMyMentions(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterMyMentions + public static func parse_messageMediaContact(_ reader: BufferReader) -> MessageMedia? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageMedia.messageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, vcard: _4!, userId: _5!) + } + else { + return nil + } } - public static func parse_inputMessagesFilterPhoneCalls(_ reader: BufferReader) -> MessagesFilter? { + public static func parse_messageMediaDice(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.MessagesFilter.inputMessagesFilterPhoneCalls(flags: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageMedia.messageMediaDice(value: _1!, emoticon: _2!) } else { return nil } } - public static func parse_inputMessagesFilterPhotoVideo(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterPhotoVideo - } - public static func parse_inputMessagesFilterPhotos(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterPhotos - } - public static func parse_inputMessagesFilterPinned(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterPinned - } - public static func parse_inputMessagesFilterRoundVideo(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterRoundVideo - } - public static func parse_inputMessagesFilterRoundVoice(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterRoundVoice + public static func parse_messageMediaDocument(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Document? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Document + } } + var _3: Api.Document? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Document + } } + var _4: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageMedia.messageMediaDocument(flags: _1!, document: _2, altDocument: _3, ttlSeconds: _4) + } + else { + return nil + } } - public static func parse_inputMessagesFilterUrl(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterUrl + public static func parse_messageMediaEmpty(_ reader: BufferReader) -> MessageMedia? { + return Api.MessageMedia.messageMediaEmpty } - public static func parse_inputMessagesFilterVideo(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterVideo + public static func parse_messageMediaGame(_ reader: BufferReader) -> MessageMedia? { + var _1: Api.Game? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Game + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageMedia.messageMediaGame(game: _1!) + } + else { + return nil + } } - public static func parse_inputMessagesFilterVoice(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterVoice + public static func parse_messageMediaGeo(_ reader: BufferReader) -> MessageMedia? { + var _1: Api.GeoPoint? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageMedia.messageMediaGeo(geo: _1!) + } + else { + return nil + } } - - } -} -public extension Api { - enum MissingInvitee: TypeConstructorDescription { - case missingInvitee(flags: Int32, userId: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .missingInvitee(let flags, let userId): - if boxed { - buffer.appendInt32(1653379620) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .missingInvitee(let flags, let userId): - return ("missingInvitee", [("flags", flags as Any), ("userId", userId as Any)]) - } - } - - public static func parse_missingInvitee(_ reader: BufferReader) -> MissingInvitee? { + public static func parse_messageMediaGeoLive(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Api.GeoPoint? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MissingInvitee.missingInvitee(flags: _1!, userId: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageMedia.messageMediaGeoLive(flags: _1!, geo: _2!, heading: _3, period: _4!, proximityNotificationRadius: _5) } else { return nil } } - - } -} -public extension Api { - enum MyBoost: TypeConstructorDescription { - case myBoost(flags: Int32, slot: Int32, peer: Api.Peer?, date: Int32, expires: Int32, cooldownUntilDate: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .myBoost(let flags, let slot, let peer, let date, let expires, let cooldownUntilDate): - if boxed { - buffer.appendInt32(-1001897636) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(slot, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(expires, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(cooldownUntilDate!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .myBoost(let flags, let slot, let peer, let date, let expires, let cooldownUntilDate): - return ("myBoost", [("flags", flags as Any), ("slot", slot as Any), ("peer", peer as Any), ("date", date as Any), ("expires", expires as Any), ("cooldownUntilDate", cooldownUntilDate as Any)]) - } - } - - public static func parse_myBoost(_ reader: BufferReader) -> MyBoost? { + public static func parse_messageMediaGiveaway(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer + var _2: [Int64]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + var _3: [String]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) } } + var _4: String? + if Int(_1!) & Int(1 << 3) != 0 {_4 = parseString(reader) } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.MessageMedia.messageMediaGiveaway(flags: _1!, channels: _2!, countriesIso2: _3, prizeDescription: _4, quantity: _5!, months: _6!, untilDate: _7!) + } + else { + return nil + } + } + public static func parse_messageMediaGiveawayResults(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_3 = reader.readInt32() } var _4: Int32? _4 = reader.readInt32() var _5: Int32? _5 = reader.readInt32() var _6: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_6 = reader.readInt32() } + _6 = reader.readInt32() + var _7: [Int64]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + var _8: Int32? + _8 = reader.readInt32() + var _9: String? + if Int(_1!) & Int(1 << 1) != 0 {_9 = parseString(reader) } + var _10: Int32? + _10 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MyBoost.myBoost(flags: _1!, slot: _2!, peer: _3, date: _4!, expires: _5!, cooldownUntilDate: _6) + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil + let _c10 = _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.MessageMedia.messageMediaGiveawayResults(flags: _1!, channelId: _2!, additionalPeersCount: _3, launchMsgId: _4!, winnersCount: _5!, unclaimedCount: _6!, winners: _7!, months: _8!, prizeDescription: _9, untilDate: _10!) } else { return nil } } - - } -} -public extension Api { - enum NearestDc: TypeConstructorDescription { - case nearestDc(country: String, thisDc: Int32, nearestDc: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .nearestDc(let country, let thisDc, let nearestDc): - if boxed { - buffer.appendInt32(-1910892683) - } - serializeString(country, buffer: buffer, boxed: false) - serializeInt32(thisDc, buffer: buffer, boxed: false) - serializeInt32(nearestDc, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .nearestDc(let country, let thisDc, let nearestDc): - return ("nearestDc", [("country", country as Any), ("thisDc", thisDc as Any), ("nearestDc", nearestDc as Any)]) - } - } - - public static func parse_nearestDc(_ reader: BufferReader) -> NearestDc? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() + public static func parse_messageMediaInvoice(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Api.WebDocument? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } + var _5: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } + var _6: String? + _6 = parseString(reader) + var _7: Int64? + _7 = reader.readInt64() + var _8: String? + _8 = parseString(reader) + var _9: Api.MessageExtendedMedia? + if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.MessageExtendedMedia + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.MessageMedia.messageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, receiptMsgId: _5, currency: _6!, totalAmount: _7!, startParam: _8!, extendedMedia: _9) + } + else { + return nil + } + } + public static func parse_messageMediaPhoto(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Photo? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Photo + } } + var _3: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageMedia.messageMediaPhoto(flags: _1!, photo: _2, ttlSeconds: _3) + } + else { + return nil + } + } + public static func parse_messageMediaPoll(_ reader: BufferReader) -> MessageMedia? { + var _1: Api.Poll? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Poll + } + var _2: Api.PollResults? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PollResults + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageMedia.messageMediaPoll(poll: _1!, results: _2!) + } + else { + return nil + } + } + public static func parse_messageMediaStory(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } var _3: Int32? _3 = reader.readInt32() + var _4: Api.StoryItem? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StoryItem + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.NearestDc.nearestDc(country: _1!, thisDc: _2!, nearestDc: _3!) + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageMedia.messageMediaStory(flags: _1!, peer: _2!, id: _3!, story: _4) } else { return nil } } - - } -} -public extension Api { - enum NotificationSound: TypeConstructorDescription { - case notificationSoundDefault - case notificationSoundLocal(title: String, data: String) - case notificationSoundNone - case notificationSoundRingtone(id: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .notificationSoundDefault: - if boxed { - buffer.appendInt32(-1746354498) - } - - break - case .notificationSoundLocal(let title, let data): - if boxed { - buffer.appendInt32(-2096391452) - } - serializeString(title, buffer: buffer, boxed: false) - serializeString(data, buffer: buffer, boxed: false) - break - case .notificationSoundNone: - if boxed { - buffer.appendInt32(1863070943) - } - - break - case .notificationSoundRingtone(let id): - if boxed { - buffer.appendInt32(-9666487) - } - serializeInt64(id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .notificationSoundDefault: - return ("notificationSoundDefault", []) - case .notificationSoundLocal(let title, let data): - return ("notificationSoundLocal", [("title", title as Any), ("data", data as Any)]) - case .notificationSoundNone: - return ("notificationSoundNone", []) - case .notificationSoundRingtone(let id): - return ("notificationSoundRingtone", [("id", id as Any)]) - } - } - - public static func parse_notificationSoundDefault(_ reader: BufferReader) -> NotificationSound? { - return Api.NotificationSound.notificationSoundDefault + public static func parse_messageMediaUnsupported(_ reader: BufferReader) -> MessageMedia? { + return Api.MessageMedia.messageMediaUnsupported } - public static func parse_notificationSoundLocal(_ reader: BufferReader) -> NotificationSound? { - var _1: String? - _1 = parseString(reader) + public static func parse_messageMediaVenue(_ reader: BufferReader) -> MessageMedia? { + var _1: Api.GeoPoint? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } var _2: String? _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.NotificationSound.notificationSoundLocal(title: _1!, data: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.MessageMedia.messageMediaVenue(geo: _1!, title: _2!, address: _3!, provider: _4!, venueId: _5!, venueType: _6!) } else { return nil } } - public static func parse_notificationSoundNone(_ reader: BufferReader) -> NotificationSound? { - return Api.NotificationSound.notificationSoundNone - } - public static func parse_notificationSoundRingtone(_ reader: BufferReader) -> NotificationSound? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_messageMediaWebPage(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.WebPage? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.WebPage + } let _c1 = _1 != nil - if _c1 { - return Api.NotificationSound.notificationSoundRingtone(id: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageMedia.messageMediaWebPage(flags: _1!, webpage: _2!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api16.swift b/submodules/TelegramApi/Sources/Api16.swift index 278c569f5c7..85ed7f414c4 100644 --- a/submodules/TelegramApi/Sources/Api16.swift +++ b/submodules/TelegramApi/Sources/Api16.swift @@ -1,69 +1,124 @@ public extension Api { - enum NotifyPeer: TypeConstructorDescription { - case notifyBroadcasts - case notifyChats - case notifyForumTopic(peer: Api.Peer, topMsgId: Int32) - case notifyPeer(peer: Api.Peer) - case notifyUsers + enum MessagePeerReaction: TypeConstructorDescription { + case messagePeerReaction(flags: Int32, peerId: Api.Peer, date: Int32, reaction: Api.Reaction) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .notifyBroadcasts: + case .messagePeerReaction(let flags, let peerId, let date, let reaction): if boxed { - buffer.appendInt32(-703403793) + buffer.appendInt32(-1938180548) } - + serializeInt32(flags, buffer: buffer, boxed: false) + peerId.serialize(buffer, true) + serializeInt32(date, buffer: buffer, boxed: false) + reaction.serialize(buffer, true) break - case .notifyChats: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messagePeerReaction(let flags, let peerId, let date, let reaction): + return ("messagePeerReaction", [("flags", flags as Any), ("peerId", peerId as Any), ("date", date as Any), ("reaction", reaction as Any)]) + } + } + + public static func parse_messagePeerReaction(_ reader: BufferReader) -> MessagePeerReaction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Reaction? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Reaction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessagePeerReaction.messagePeerReaction(flags: _1!, peerId: _2!, date: _3!, reaction: _4!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum MessagePeerVote: TypeConstructorDescription { + case messagePeerVote(peer: Api.Peer, option: Buffer, date: Int32) + case messagePeerVoteInputOption(peer: Api.Peer, date: Int32) + case messagePeerVoteMultiple(peer: Api.Peer, options: [Buffer], date: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messagePeerVote(let peer, let option, let date): if boxed { - buffer.appendInt32(-1073230141) + buffer.appendInt32(-1228133028) } - + peer.serialize(buffer, true) + serializeBytes(option, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) break - case .notifyForumTopic(let peer, let topMsgId): + case .messagePeerVoteInputOption(let peer, let date): if boxed { - buffer.appendInt32(577659656) + buffer.appendInt32(1959634180) } peer.serialize(buffer, true) - serializeInt32(topMsgId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) break - case .notifyPeer(let peer): + case .messagePeerVoteMultiple(let peer, let options, let date): if boxed { - buffer.appendInt32(-1613493288) + buffer.appendInt32(1177089766) } peer.serialize(buffer, true) - break - case .notifyUsers: - if boxed { - buffer.appendInt32(-1261946036) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(options.count)) + for item in options { + serializeBytes(item, buffer: buffer, boxed: false) } - + serializeInt32(date, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .notifyBroadcasts: - return ("notifyBroadcasts", []) - case .notifyChats: - return ("notifyChats", []) - case .notifyForumTopic(let peer, let topMsgId): - return ("notifyForumTopic", [("peer", peer as Any), ("topMsgId", topMsgId as Any)]) - case .notifyPeer(let peer): - return ("notifyPeer", [("peer", peer as Any)]) - case .notifyUsers: - return ("notifyUsers", []) + case .messagePeerVote(let peer, let option, let date): + return ("messagePeerVote", [("peer", peer as Any), ("option", option as Any), ("date", date as Any)]) + case .messagePeerVoteInputOption(let peer, let date): + return ("messagePeerVoteInputOption", [("peer", peer as Any), ("date", date as Any)]) + case .messagePeerVoteMultiple(let peer, let options, let date): + return ("messagePeerVoteMultiple", [("peer", peer as Any), ("options", options as Any), ("date", date as Any)]) } } - public static func parse_notifyBroadcasts(_ reader: BufferReader) -> NotifyPeer? { - return Api.NotifyPeer.notifyBroadcasts - } - public static func parse_notifyChats(_ reader: BufferReader) -> NotifyPeer? { - return Api.NotifyPeer.notifyChats + public static func parse_messagePeerVote(_ reader: BufferReader) -> MessagePeerVote? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessagePeerVote.messagePeerVote(peer: _1!, option: _2!, date: _3!) + } + else { + return nil + } } - public static func parse_notifyForumTopic(_ reader: BufferReader) -> NotifyPeer? { + public static func parse_messagePeerVoteInputOption(_ reader: BufferReader) -> MessagePeerVote? { var _1: Api.Peer? if let signature = reader.readInt32() { _1 = Api.parse(reader, signature: signature) as? Api.Peer @@ -73,59 +128,68 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.NotifyPeer.notifyForumTopic(peer: _1!, topMsgId: _2!) + return Api.MessagePeerVote.messagePeerVoteInputOption(peer: _1!, date: _2!) } else { return nil } } - public static func parse_notifyPeer(_ reader: BufferReader) -> NotifyPeer? { + public static func parse_messagePeerVoteMultiple(_ reader: BufferReader) -> MessagePeerVote? { var _1: Api.Peer? if let signature = reader.readInt32() { _1 = Api.parse(reader, signature: signature) as? Api.Peer } + var _2: [Buffer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + } + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.NotifyPeer.notifyPeer(peer: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessagePeerVote.messagePeerVoteMultiple(peer: _1!, options: _2!, date: _3!) } else { return nil } } - public static func parse_notifyUsers(_ reader: BufferReader) -> NotifyPeer? { - return Api.NotifyPeer.notifyUsers - } } } public extension Api { - enum OutboxReadDate: TypeConstructorDescription { - case outboxReadDate(date: Int32) + enum MessageRange: TypeConstructorDescription { + case messageRange(minId: Int32, maxId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .outboxReadDate(let date): + case .messageRange(let minId, let maxId): if boxed { - buffer.appendInt32(1001931436) + buffer.appendInt32(182649427) } - serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .outboxReadDate(let date): - return ("outboxReadDate", [("date", date as Any)]) + case .messageRange(let minId, let maxId): + return ("messageRange", [("minId", minId as Any), ("maxId", maxId as Any)]) } } - public static func parse_outboxReadDate(_ reader: BufferReader) -> OutboxReadDate? { + public static func parse_messageRange(_ reader: BufferReader) -> MessageRange? { var _1: Int32? _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.OutboxReadDate.outboxReadDate(date: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageRange.messageRange(minId: _1!, maxId: _2!) } else { return nil @@ -135,71 +199,53 @@ public extension Api { } } public extension Api { - enum Page: TypeConstructorDescription { - case page(flags: Int32, url: String, blocks: [Api.PageBlock], photos: [Api.Photo], documents: [Api.Document], views: Int32?) + enum MessageReactions: TypeConstructorDescription { + case messageReactions(flags: Int32, results: [Api.ReactionCount], recentReactions: [Api.MessagePeerReaction]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .page(let flags, let url, let blocks, let photos, let documents, let views): + case .messageReactions(let flags, let results, let recentReactions): if boxed { - buffer.appendInt32(-1738178803) + buffer.appendInt32(1328256121) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(blocks.count)) - for item in blocks { - item.serialize(buffer, true) - } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(photos.count)) - for item in photos { + buffer.appendInt32(Int32(results.count)) + for item in results { item.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documents.count)) - for item in documents { + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentReactions!.count)) + for item in recentReactions! { item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} + }} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .page(let flags, let url, let blocks, let photos, let documents, let views): - return ("page", [("flags", flags as Any), ("url", url as Any), ("blocks", blocks as Any), ("photos", photos as Any), ("documents", documents as Any), ("views", views as Any)]) + case .messageReactions(let flags, let results, let recentReactions): + return ("messageReactions", [("flags", flags as Any), ("results", results as Any), ("recentReactions", recentReactions as Any)]) } } - public static func parse_page(_ reader: BufferReader) -> Page? { + public static func parse_messageReactions(_ reader: BufferReader) -> MessageReactions? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: [Api.PageBlock]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - var _4: [Api.Photo]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) - } - var _5: [Api.Document]? + var _2: [Api.ReactionCount]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) } - var _6: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_6 = reader.readInt32() } + var _3: [Api.MessagePeerReaction]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self) + } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Page.page(flags: _1!, url: _2!, blocks: _3!, photos: _4!, documents: _5!, views: _6) + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageReactions.messageReactions(flags: _1!, results: _2!, recentReactions: _3) } else { return nil @@ -209,815 +255,673 @@ public extension Api { } } public extension Api { - indirect enum PageBlock: TypeConstructorDescription { - case pageBlockAnchor(name: String) - case pageBlockAudio(audioId: Int64, caption: Api.PageCaption) - case pageBlockAuthorDate(author: Api.RichText, publishedDate: Int32) - case pageBlockBlockquote(text: Api.RichText, caption: Api.RichText) - case pageBlockChannel(channel: Api.Chat) - case pageBlockCollage(items: [Api.PageBlock], caption: Api.PageCaption) - case pageBlockCover(cover: Api.PageBlock) - case pageBlockDetails(flags: Int32, blocks: [Api.PageBlock], title: Api.RichText) - case pageBlockDivider - case pageBlockEmbed(flags: Int32, url: String?, html: String?, posterPhotoId: Int64?, w: Int32?, h: Int32?, caption: Api.PageCaption) - case pageBlockEmbedPost(url: String, webpageId: Int64, authorPhotoId: Int64, author: String, date: Int32, blocks: [Api.PageBlock], caption: Api.PageCaption) - case pageBlockFooter(text: Api.RichText) - case pageBlockHeader(text: Api.RichText) - case pageBlockKicker(text: Api.RichText) - case pageBlockList(items: [Api.PageListItem]) - case pageBlockMap(geo: Api.GeoPoint, zoom: Int32, w: Int32, h: Int32, caption: Api.PageCaption) - case pageBlockOrderedList(items: [Api.PageListOrderedItem]) - case pageBlockParagraph(text: Api.RichText) - case pageBlockPhoto(flags: Int32, photoId: Int64, caption: Api.PageCaption, url: String?, webpageId: Int64?) - case pageBlockPreformatted(text: Api.RichText, language: String) - case pageBlockPullquote(text: Api.RichText, caption: Api.RichText) - case pageBlockRelatedArticles(title: Api.RichText, articles: [Api.PageRelatedArticle]) - case pageBlockSlideshow(items: [Api.PageBlock], caption: Api.PageCaption) - case pageBlockSubheader(text: Api.RichText) - case pageBlockSubtitle(text: Api.RichText) - case pageBlockTable(flags: Int32, title: Api.RichText, rows: [Api.PageTableRow]) - case pageBlockTitle(text: Api.RichText) - case pageBlockUnsupported - case pageBlockVideo(flags: Int32, videoId: Int64, caption: Api.PageCaption) + enum MessageReplies: TypeConstructorDescription { + case messageReplies(flags: Int32, replies: Int32, repliesPts: Int32, recentRepliers: [Api.Peer]?, channelId: Int64?, maxId: Int32?, readMaxId: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pageBlockAnchor(let name): - if boxed { - buffer.appendInt32(-837994576) - } - serializeString(name, buffer: buffer, boxed: false) - break - case .pageBlockAudio(let audioId, let caption): - if boxed { - buffer.appendInt32(-2143067670) - } - serializeInt64(audioId, buffer: buffer, boxed: false) - caption.serialize(buffer, true) - break - case .pageBlockAuthorDate(let author, let publishedDate): - if boxed { - buffer.appendInt32(-1162877472) - } - author.serialize(buffer, true) - serializeInt32(publishedDate, buffer: buffer, boxed: false) - break - case .pageBlockBlockquote(let text, let caption): - if boxed { - buffer.appendInt32(641563686) - } - text.serialize(buffer, true) - caption.serialize(buffer, true) - break - case .pageBlockChannel(let channel): - if boxed { - buffer.appendInt32(-283684427) - } - channel.serialize(buffer, true) - break - case .pageBlockCollage(let items, let caption): + case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let maxId, let readMaxId): if boxed { - buffer.appendInt32(1705048653) + buffer.appendInt32(-2083123262) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(items.count)) - for item in items { + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(replies, buffer: buffer, boxed: false) + serializeInt32(repliesPts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentRepliers!.count)) + for item in recentRepliers! { item.serialize(buffer, true) - } - caption.serialize(buffer, true) - break - case .pageBlockCover(let cover): - if boxed { - buffer.appendInt32(972174080) - } - cover.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(channelId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(maxId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(readMaxId!, buffer: buffer, boxed: false)} break - case .pageBlockDetails(let flags, let blocks, let title): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let maxId, let readMaxId): + return ("messageReplies", [("flags", flags as Any), ("replies", replies as Any), ("repliesPts", repliesPts as Any), ("recentRepliers", recentRepliers as Any), ("channelId", channelId as Any), ("maxId", maxId as Any), ("readMaxId", readMaxId as Any)]) + } + } + + public static func parse_messageReplies(_ reader: BufferReader) -> MessageReplies? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Api.Peer]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } } + var _5: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } + var _6: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt32() } + var _7: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_7 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.MessageReplies.messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5, maxId: _6, readMaxId: _7) + } + else { + return nil + } + } + + } +} +public extension Api { + indirect enum MessageReplyHeader: TypeConstructorDescription { + case messageReplyHeader(flags: Int32, replyToMsgId: Int32?, replyToPeerId: Api.Peer?, replyFrom: Api.MessageFwdHeader?, replyMedia: Api.MessageMedia?, replyToTopId: Int32?, quoteText: String?, quoteEntities: [Api.MessageEntity]?, quoteOffset: Int32?) + case messageReplyStoryHeader(peer: Api.Peer, storyId: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyFrom, let replyMedia, let replyToTopId, let quoteText, let quoteEntities, let quoteOffset): if boxed { - buffer.appendInt32(1987480557) + buffer.appendInt32(-1346631205) } serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(blocks.count)) - for item in blocks { + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {replyToPeerId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 5) != 0 {replyFrom!.serialize(buffer, true)} + if Int(flags) & Int(1 << 8) != 0 {replyMedia!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(replyToTopId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {serializeString(quoteText!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(quoteEntities!.count)) + for item in quoteEntities! { item.serialize(buffer, true) - } - title.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(quoteOffset!, buffer: buffer, boxed: false)} break - case .pageBlockDivider: + case .messageReplyStoryHeader(let peer, let storyId): if boxed { - buffer.appendInt32(-618614392) + buffer.appendInt32(240843065) } - + peer.serialize(buffer, true) + serializeInt32(storyId, buffer: buffer, boxed: false) break - case .pageBlockEmbed(let flags, let url, let html, let posterPhotoId, let w, let h, let caption): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyFrom, let replyMedia, let replyToTopId, let quoteText, let quoteEntities, let quoteOffset): + return ("messageReplyHeader", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("replyFrom", replyFrom as Any), ("replyMedia", replyMedia as Any), ("replyToTopId", replyToTopId as Any), ("quoteText", quoteText as Any), ("quoteEntities", quoteEntities as Any), ("quoteOffset", quoteOffset as Any)]) + case .messageReplyStoryHeader(let peer, let storyId): + return ("messageReplyStoryHeader", [("peer", peer as Any), ("storyId", storyId as Any)]) + } + } + + public static func parse_messageReplyHeader(_ reader: BufferReader) -> MessageReplyHeader? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_2 = reader.readInt32() } + var _3: Api.Peer? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _4: Api.MessageFwdHeader? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } } + var _5: Api.MessageMedia? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } } + var _6: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_6 = reader.readInt32() } + var _7: String? + if Int(_1!) & Int(1 << 6) != 0 {_7 = parseString(reader) } + var _8: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + var _9: Int32? + if Int(_1!) & Int(1 << 10) != 0 {_9 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 5) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.MessageReplyHeader.messageReplyHeader(flags: _1!, replyToMsgId: _2, replyToPeerId: _3, replyFrom: _4, replyMedia: _5, replyToTopId: _6, quoteText: _7, quoteEntities: _8, quoteOffset: _9) + } + else { + return nil + } + } + public static func parse_messageReplyStoryHeader(_ reader: BufferReader) -> MessageReplyHeader? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageReplyHeader.messageReplyStoryHeader(peer: _1!, storyId: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum MessageViews: TypeConstructorDescription { + case messageViews(flags: Int32, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageViews(let flags, let views, let forwards, let replies): if boxed { - buffer.appendInt32(-1468953147) + buffer.appendInt32(1163625789) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(html!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt64(posterPhotoId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 5) != 0 {serializeInt32(w!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 5) != 0 {serializeInt32(h!, buffer: buffer, boxed: false)} - caption.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {replies!.serialize(buffer, true)} break - case .pageBlockEmbedPost(let url, let webpageId, let authorPhotoId, let author, let date, let blocks, let caption): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageViews(let flags, let views, let forwards, let replies): + return ("messageViews", [("flags", flags as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any)]) + } + } + + public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _3: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } + var _4: Api.MessageReplies? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MessageReplies + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageViews.messageViews(flags: _1!, views: _2, forwards: _3, replies: _4) + } + else { + return nil + } + } + + } +} +public extension Api { + enum MessagesFilter: TypeConstructorDescription { + case inputMessagesFilterChatPhotos + case inputMessagesFilterContacts + case inputMessagesFilterDocument + case inputMessagesFilterEmpty + case inputMessagesFilterGeo + case inputMessagesFilterGif + case inputMessagesFilterMusic + case inputMessagesFilterMyMentions + case inputMessagesFilterPhoneCalls(flags: Int32) + case inputMessagesFilterPhotoVideo + case inputMessagesFilterPhotos + case inputMessagesFilterPinned + case inputMessagesFilterRoundVideo + case inputMessagesFilterRoundVoice + case inputMessagesFilterUrl + case inputMessagesFilterVideo + case inputMessagesFilterVoice + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputMessagesFilterChatPhotos: if boxed { - buffer.appendInt32(-229005301) - } - serializeString(url, buffer: buffer, boxed: false) - serializeInt64(webpageId, buffer: buffer, boxed: false) - serializeInt64(authorPhotoId, buffer: buffer, boxed: false) - serializeString(author, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(blocks.count)) - for item in blocks { - item.serialize(buffer, true) + buffer.appendInt32(975236280) } - caption.serialize(buffer, true) + break - case .pageBlockFooter(let text): + case .inputMessagesFilterContacts: if boxed { - buffer.appendInt32(1216809369) + buffer.appendInt32(-530392189) } - text.serialize(buffer, true) + break - case .pageBlockHeader(let text): + case .inputMessagesFilterDocument: if boxed { - buffer.appendInt32(-1076861716) + buffer.appendInt32(-1629621880) } - text.serialize(buffer, true) + break - case .pageBlockKicker(let text): + case .inputMessagesFilterEmpty: if boxed { - buffer.appendInt32(504660880) + buffer.appendInt32(1474492012) } - text.serialize(buffer, true) + break - case .pageBlockList(let items): + case .inputMessagesFilterGeo: if boxed { - buffer.appendInt32(-454524911) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(items.count)) - for item in items { - item.serialize(buffer, true) + buffer.appendInt32(-419271411) } + break - case .pageBlockMap(let geo, let zoom, let w, let h, let caption): + case .inputMessagesFilterGif: if boxed { - buffer.appendInt32(-1538310410) + buffer.appendInt32(-3644025) } - geo.serialize(buffer, true) - serializeInt32(zoom, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - caption.serialize(buffer, true) + break - case .pageBlockOrderedList(let items): + case .inputMessagesFilterMusic: if boxed { - buffer.appendInt32(-1702174239) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(items.count)) - for item in items { - item.serialize(buffer, true) + buffer.appendInt32(928101534) } + break - case .pageBlockParagraph(let text): + case .inputMessagesFilterMyMentions: if boxed { - buffer.appendInt32(1182402406) + buffer.appendInt32(-1040652646) } - text.serialize(buffer, true) + break - case .pageBlockPhoto(let flags, let photoId, let caption, let url, let webpageId): + case .inputMessagesFilterPhoneCalls(let flags): if boxed { - buffer.appendInt32(391759200) + buffer.appendInt32(-2134272152) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(photoId, buffer: buffer, boxed: false) - caption.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(webpageId!, buffer: buffer, boxed: false)} break - case .pageBlockPreformatted(let text, let language): + case .inputMessagesFilterPhotoVideo: if boxed { - buffer.appendInt32(-1066346178) - } - text.serialize(buffer, true) - serializeString(language, buffer: buffer, boxed: false) - break - case .pageBlockPullquote(let text, let caption): - if boxed { - buffer.appendInt32(1329878739) - } - text.serialize(buffer, true) - caption.serialize(buffer, true) - break - case .pageBlockRelatedArticles(let title, let articles): - if boxed { - buffer.appendInt32(370236054) - } - title.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(articles.count)) - for item in articles { - item.serialize(buffer, true) + buffer.appendInt32(1458172132) } + break - case .pageBlockSlideshow(let items, let caption): + case .inputMessagesFilterPhotos: if boxed { - buffer.appendInt32(52401552) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(items.count)) - for item in items { - item.serialize(buffer, true) + buffer.appendInt32(-1777752804) } - caption.serialize(buffer, true) + break - case .pageBlockSubheader(let text): + case .inputMessagesFilterPinned: if boxed { - buffer.appendInt32(-248793375) + buffer.appendInt32(464520273) } - text.serialize(buffer, true) + break - case .pageBlockSubtitle(let text): + case .inputMessagesFilterRoundVideo: if boxed { - buffer.appendInt32(-1879401953) + buffer.appendInt32(-1253451181) } - text.serialize(buffer, true) + break - case .pageBlockTable(let flags, let title, let rows): + case .inputMessagesFilterRoundVoice: if boxed { - buffer.appendInt32(-1085412734) - } - serializeInt32(flags, buffer: buffer, boxed: false) - title.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(rows.count)) - for item in rows { - item.serialize(buffer, true) + buffer.appendInt32(2054952868) } + break - case .pageBlockTitle(let text): + case .inputMessagesFilterUrl: if boxed { - buffer.appendInt32(1890305021) + buffer.appendInt32(2129714567) } - text.serialize(buffer, true) + break - case .pageBlockUnsupported: + case .inputMessagesFilterVideo: if boxed { - buffer.appendInt32(324435594) + buffer.appendInt32(-1614803355) } break - case .pageBlockVideo(let flags, let videoId, let caption): + case .inputMessagesFilterVoice: if boxed { - buffer.appendInt32(2089805750) + buffer.appendInt32(1358283666) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(videoId, buffer: buffer, boxed: false) - caption.serialize(buffer, true) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .pageBlockAnchor(let name): - return ("pageBlockAnchor", [("name", name as Any)]) - case .pageBlockAudio(let audioId, let caption): - return ("pageBlockAudio", [("audioId", audioId as Any), ("caption", caption as Any)]) - case .pageBlockAuthorDate(let author, let publishedDate): - return ("pageBlockAuthorDate", [("author", author as Any), ("publishedDate", publishedDate as Any)]) - case .pageBlockBlockquote(let text, let caption): - return ("pageBlockBlockquote", [("text", text as Any), ("caption", caption as Any)]) - case .pageBlockChannel(let channel): - return ("pageBlockChannel", [("channel", channel as Any)]) - case .pageBlockCollage(let items, let caption): - return ("pageBlockCollage", [("items", items as Any), ("caption", caption as Any)]) - case .pageBlockCover(let cover): - return ("pageBlockCover", [("cover", cover as Any)]) - case .pageBlockDetails(let flags, let blocks, let title): - return ("pageBlockDetails", [("flags", flags as Any), ("blocks", blocks as Any), ("title", title as Any)]) - case .pageBlockDivider: - return ("pageBlockDivider", []) - case .pageBlockEmbed(let flags, let url, let html, let posterPhotoId, let w, let h, let caption): - return ("pageBlockEmbed", [("flags", flags as Any), ("url", url as Any), ("html", html as Any), ("posterPhotoId", posterPhotoId as Any), ("w", w as Any), ("h", h as Any), ("caption", caption as Any)]) - case .pageBlockEmbedPost(let url, let webpageId, let authorPhotoId, let author, let date, let blocks, let caption): - return ("pageBlockEmbedPost", [("url", url as Any), ("webpageId", webpageId as Any), ("authorPhotoId", authorPhotoId as Any), ("author", author as Any), ("date", date as Any), ("blocks", blocks as Any), ("caption", caption as Any)]) - case .pageBlockFooter(let text): - return ("pageBlockFooter", [("text", text as Any)]) - case .pageBlockHeader(let text): - return ("pageBlockHeader", [("text", text as Any)]) - case .pageBlockKicker(let text): - return ("pageBlockKicker", [("text", text as Any)]) - case .pageBlockList(let items): - return ("pageBlockList", [("items", items as Any)]) - case .pageBlockMap(let geo, let zoom, let w, let h, let caption): - return ("pageBlockMap", [("geo", geo as Any), ("zoom", zoom as Any), ("w", w as Any), ("h", h as Any), ("caption", caption as Any)]) - case .pageBlockOrderedList(let items): - return ("pageBlockOrderedList", [("items", items as Any)]) - case .pageBlockParagraph(let text): - return ("pageBlockParagraph", [("text", text as Any)]) - case .pageBlockPhoto(let flags, let photoId, let caption, let url, let webpageId): - return ("pageBlockPhoto", [("flags", flags as Any), ("photoId", photoId as Any), ("caption", caption as Any), ("url", url as Any), ("webpageId", webpageId as Any)]) - case .pageBlockPreformatted(let text, let language): - return ("pageBlockPreformatted", [("text", text as Any), ("language", language as Any)]) - case .pageBlockPullquote(let text, let caption): - return ("pageBlockPullquote", [("text", text as Any), ("caption", caption as Any)]) - case .pageBlockRelatedArticles(let title, let articles): - return ("pageBlockRelatedArticles", [("title", title as Any), ("articles", articles as Any)]) - case .pageBlockSlideshow(let items, let caption): - return ("pageBlockSlideshow", [("items", items as Any), ("caption", caption as Any)]) - case .pageBlockSubheader(let text): - return ("pageBlockSubheader", [("text", text as Any)]) - case .pageBlockSubtitle(let text): - return ("pageBlockSubtitle", [("text", text as Any)]) - case .pageBlockTable(let flags, let title, let rows): - return ("pageBlockTable", [("flags", flags as Any), ("title", title as Any), ("rows", rows as Any)]) - case .pageBlockTitle(let text): - return ("pageBlockTitle", [("text", text as Any)]) - case .pageBlockUnsupported: - return ("pageBlockUnsupported", []) - case .pageBlockVideo(let flags, let videoId, let caption): - return ("pageBlockVideo", [("flags", flags as Any), ("videoId", videoId as Any), ("caption", caption as Any)]) + case .inputMessagesFilterChatPhotos: + return ("inputMessagesFilterChatPhotos", []) + case .inputMessagesFilterContacts: + return ("inputMessagesFilterContacts", []) + case .inputMessagesFilterDocument: + return ("inputMessagesFilterDocument", []) + case .inputMessagesFilterEmpty: + return ("inputMessagesFilterEmpty", []) + case .inputMessagesFilterGeo: + return ("inputMessagesFilterGeo", []) + case .inputMessagesFilterGif: + return ("inputMessagesFilterGif", []) + case .inputMessagesFilterMusic: + return ("inputMessagesFilterMusic", []) + case .inputMessagesFilterMyMentions: + return ("inputMessagesFilterMyMentions", []) + case .inputMessagesFilterPhoneCalls(let flags): + return ("inputMessagesFilterPhoneCalls", [("flags", flags as Any)]) + case .inputMessagesFilterPhotoVideo: + return ("inputMessagesFilterPhotoVideo", []) + case .inputMessagesFilterPhotos: + return ("inputMessagesFilterPhotos", []) + case .inputMessagesFilterPinned: + return ("inputMessagesFilterPinned", []) + case .inputMessagesFilterRoundVideo: + return ("inputMessagesFilterRoundVideo", []) + case .inputMessagesFilterRoundVoice: + return ("inputMessagesFilterRoundVoice", []) + case .inputMessagesFilterUrl: + return ("inputMessagesFilterUrl", []) + case .inputMessagesFilterVideo: + return ("inputMessagesFilterVideo", []) + case .inputMessagesFilterVoice: + return ("inputMessagesFilterVoice", []) } } - public static func parse_pageBlockAnchor(_ reader: BufferReader) -> PageBlock? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockAnchor(name: _1!) - } - else { - return nil - } + public static func parse_inputMessagesFilterChatPhotos(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterChatPhotos } - public static func parse_pageBlockAudio(_ reader: BufferReader) -> PageBlock? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.PageCaption? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockAudio(audioId: _1!, caption: _2!) - } - else { - return nil - } + public static func parse_inputMessagesFilterContacts(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterContacts } - public static func parse_pageBlockAuthorDate(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockAuthorDate(author: _1!, publishedDate: _2!) - } - else { - return nil - } + public static func parse_inputMessagesFilterDocument(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterDocument } - public static func parse_pageBlockBlockquote(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: Api.RichText? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockBlockquote(text: _1!, caption: _2!) - } - else { - return nil - } + public static func parse_inputMessagesFilterEmpty(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterEmpty } - public static func parse_pageBlockChannel(_ reader: BufferReader) -> PageBlock? { - var _1: Api.Chat? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Chat - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockChannel(channel: _1!) - } - else { - return nil - } + public static func parse_inputMessagesFilterGeo(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterGeo } - public static func parse_pageBlockCollage(_ reader: BufferReader) -> PageBlock? { - var _1: [Api.PageBlock]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - var _2: Api.PageCaption? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockCollage(items: _1!, caption: _2!) - } - else { - return nil - } + public static func parse_inputMessagesFilterGif(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterGif } - public static func parse_pageBlockCover(_ reader: BufferReader) -> PageBlock? { - var _1: Api.PageBlock? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PageBlock - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockCover(cover: _1!) - } - else { - return nil - } - } - public static func parse_pageBlockDetails(_ reader: BufferReader) -> PageBlock? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.PageBlock]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - var _3: Api.RichText? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PageBlock.pageBlockDetails(flags: _1!, blocks: _2!, title: _3!) - } - else { - return nil - } + public static func parse_inputMessagesFilterMusic(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterMusic } - public static func parse_pageBlockDivider(_ reader: BufferReader) -> PageBlock? { - return Api.PageBlock.pageBlockDivider + public static func parse_inputMessagesFilterMyMentions(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterMyMentions } - public static func parse_pageBlockEmbed(_ reader: BufferReader) -> PageBlock? { + public static func parse_inputMessagesFilterPhoneCalls(_ reader: BufferReader) -> MessagesFilter? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 1) != 0 {_2 = parseString(reader) } - var _3: String? - if Int(_1!) & Int(1 << 2) != 0 {_3 = parseString(reader) } - var _4: Int64? - if Int(_1!) & Int(1 << 4) != 0 {_4 = reader.readInt64() } - var _5: Int32? - if Int(_1!) & Int(1 << 5) != 0 {_5 = reader.readInt32() } - var _6: Int32? - if Int(_1!) & Int(1 << 5) != 0 {_6 = reader.readInt32() } - var _7: Api.PageCaption? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PageCaption - } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PageBlock.pageBlockEmbed(flags: _1!, url: _2, html: _3, posterPhotoId: _4, w: _5, h: _6, caption: _7!) + if _c1 { + return Api.MessagesFilter.inputMessagesFilterPhoneCalls(flags: _1!) } else { return nil } } - public static func parse_pageBlockEmbedPost(_ reader: BufferReader) -> PageBlock? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: [Api.PageBlock]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - var _7: Api.PageCaption? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PageBlock.pageBlockEmbedPost(url: _1!, webpageId: _2!, authorPhotoId: _3!, author: _4!, date: _5!, blocks: _6!, caption: _7!) - } - else { - return nil - } + public static func parse_inputMessagesFilterPhotoVideo(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterPhotoVideo } - public static func parse_pageBlockFooter(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockFooter(text: _1!) - } - else { - return nil - } + public static func parse_inputMessagesFilterPhotos(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterPhotos } - public static func parse_pageBlockHeader(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockHeader(text: _1!) - } - else { - return nil - } + public static func parse_inputMessagesFilterPinned(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterPinned } - public static func parse_pageBlockKicker(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockKicker(text: _1!) - } - else { - return nil - } + public static func parse_inputMessagesFilterRoundVideo(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterRoundVideo } - public static func parse_pageBlockList(_ reader: BufferReader) -> PageBlock? { - var _1: [Api.PageListItem]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageListItem.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockList(items: _1!) - } - else { - return nil - } + public static func parse_inputMessagesFilterRoundVoice(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterRoundVoice } - public static func parse_pageBlockMap(_ reader: BufferReader) -> PageBlock? { - var _1: Api.GeoPoint? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Api.PageCaption? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PageBlock.pageBlockMap(geo: _1!, zoom: _2!, w: _3!, h: _4!, caption: _5!) - } - else { - return nil - } + public static func parse_inputMessagesFilterUrl(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterUrl } - public static func parse_pageBlockOrderedList(_ reader: BufferReader) -> PageBlock? { - var _1: [Api.PageListOrderedItem]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageListOrderedItem.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockOrderedList(items: _1!) - } - else { - return nil - } + public static func parse_inputMessagesFilterVideo(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterVideo } - public static func parse_pageBlockParagraph(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockParagraph(text: _1!) - } - else { - return nil - } + public static func parse_inputMessagesFilterVoice(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterVoice } - public static func parse_pageBlockPhoto(_ reader: BufferReader) -> PageBlock? { + + } +} +public extension Api { + enum MissingInvitee: TypeConstructorDescription { + case missingInvitee(flags: Int32, userId: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .missingInvitee(let flags, let userId): + if boxed { + buffer.appendInt32(1653379620) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .missingInvitee(let flags, let userId): + return ("missingInvitee", [("flags", flags as Any), ("userId", userId as Any)]) + } + } + + public static func parse_missingInvitee(_ reader: BufferReader) -> MissingInvitee? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: Api.PageCaption? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PageBlock.pageBlockPhoto(flags: _1!, photoId: _2!, caption: _3!, url: _4, webpageId: _5) - } - else { - return nil - } - } - public static func parse_pageBlockPreformatted(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: String? - _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.PageBlock.pageBlockPreformatted(text: _1!, language: _2!) + return Api.MissingInvitee.missingInvitee(flags: _1!, userId: _2!) } else { return nil } } - public static func parse_pageBlockPullquote(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: Api.RichText? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } + + } +} +public extension Api { + enum MyBoost: TypeConstructorDescription { + case myBoost(flags: Int32, slot: Int32, peer: Api.Peer?, date: Int32, expires: Int32, cooldownUntilDate: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .myBoost(let flags, let slot, let peer, let date, let expires, let cooldownUntilDate): + if boxed { + buffer.appendInt32(-1001897636) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(slot, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(expires, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(cooldownUntilDate!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .myBoost(let flags, let slot, let peer, let date, let expires, let cooldownUntilDate): + return ("myBoost", [("flags", flags as Any), ("slot", slot as Any), ("peer", peer as Any), ("date", date as Any), ("expires", expires as Any), ("cooldownUntilDate", cooldownUntilDate as Any)]) + } + } + + public static func parse_myBoost(_ reader: BufferReader) -> MyBoost? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Peer? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_6 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockPullquote(text: _1!, caption: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.MyBoost.myBoost(flags: _1!, slot: _2!, peer: _3, date: _4!, expires: _5!, cooldownUntilDate: _6) } else { return nil } } - public static func parse_pageBlockRelatedArticles(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: [Api.PageRelatedArticle]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageRelatedArticle.self) - } + + } +} +public extension Api { + enum NearestDc: TypeConstructorDescription { + case nearestDc(country: String, thisDc: Int32, nearestDc: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .nearestDc(let country, let thisDc, let nearestDc): + if boxed { + buffer.appendInt32(-1910892683) + } + serializeString(country, buffer: buffer, boxed: false) + serializeInt32(thisDc, buffer: buffer, boxed: false) + serializeInt32(nearestDc, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .nearestDc(let country, let thisDc, let nearestDc): + return ("nearestDc", [("country", country as Any), ("thisDc", thisDc as Any), ("nearestDc", nearestDc as Any)]) + } + } + + public static func parse_nearestDc(_ reader: BufferReader) -> NearestDc? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockRelatedArticles(title: _1!, articles: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.NearestDc.nearestDc(country: _1!, thisDc: _2!, nearestDc: _3!) } else { return nil } } - public static func parse_pageBlockSlideshow(_ reader: BufferReader) -> PageBlock? { - var _1: [Api.PageBlock]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - var _2: Api.PageCaption? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PageCaption - } + + } +} +public extension Api { + enum NotificationSound: TypeConstructorDescription { + case notificationSoundDefault + case notificationSoundLocal(title: String, data: String) + case notificationSoundNone + case notificationSoundRingtone(id: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .notificationSoundDefault: + if boxed { + buffer.appendInt32(-1746354498) + } + + break + case .notificationSoundLocal(let title, let data): + if boxed { + buffer.appendInt32(-2096391452) + } + serializeString(title, buffer: buffer, boxed: false) + serializeString(data, buffer: buffer, boxed: false) + break + case .notificationSoundNone: + if boxed { + buffer.appendInt32(1863070943) + } + + break + case .notificationSoundRingtone(let id): + if boxed { + buffer.appendInt32(-9666487) + } + serializeInt64(id, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .notificationSoundDefault: + return ("notificationSoundDefault", []) + case .notificationSoundLocal(let title, let data): + return ("notificationSoundLocal", [("title", title as Any), ("data", data as Any)]) + case .notificationSoundNone: + return ("notificationSoundNone", []) + case .notificationSoundRingtone(let id): + return ("notificationSoundRingtone", [("id", id as Any)]) + } + } + + public static func parse_notificationSoundDefault(_ reader: BufferReader) -> NotificationSound? { + return Api.NotificationSound.notificationSoundDefault + } + public static func parse_notificationSoundLocal(_ reader: BufferReader) -> NotificationSound? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.PageBlock.pageBlockSlideshow(items: _1!, caption: _2!) - } - else { - return nil - } - } - public static func parse_pageBlockSubheader(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockSubheader(text: _1!) - } - else { - return nil - } - } - public static func parse_pageBlockSubtitle(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockSubtitle(text: _1!) + return Api.NotificationSound.notificationSoundLocal(title: _1!, data: _2!) } else { return nil } } - public static func parse_pageBlockTable(_ reader: BufferReader) -> PageBlock? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.RichText? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _3: [Api.PageTableRow]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageTableRow.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PageBlock.pageBlockTable(flags: _1!, title: _2!, rows: _3!) - } - else { - return nil - } + public static func parse_notificationSoundNone(_ reader: BufferReader) -> NotificationSound? { + return Api.NotificationSound.notificationSoundNone } - public static func parse_pageBlockTitle(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } + public static func parse_notificationSoundRingtone(_ reader: BufferReader) -> NotificationSound? { + var _1: Int64? + _1 = reader.readInt64() let _c1 = _1 != nil if _c1 { - return Api.PageBlock.pageBlockTitle(text: _1!) - } - else { - return nil - } - } - public static func parse_pageBlockUnsupported(_ reader: BufferReader) -> PageBlock? { - return Api.PageBlock.pageBlockUnsupported - } - public static func parse_pageBlockVideo(_ reader: BufferReader) -> PageBlock? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.PageCaption? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PageBlock.pageBlockVideo(flags: _1!, videoId: _2!, caption: _3!) + return Api.NotificationSound.notificationSoundRingtone(id: _1!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api17.swift b/submodules/TelegramApi/Sources/Api17.swift index eb008492305..278c569f5c7 100644 --- a/submodules/TelegramApi/Sources/Api17.swift +++ b/submodules/TelegramApi/Sources/Api17.swift @@ -1,239 +1,131 @@ public extension Api { - indirect enum PageCaption: TypeConstructorDescription { - case pageCaption(text: Api.RichText, credit: Api.RichText) + enum NotifyPeer: TypeConstructorDescription { + case notifyBroadcasts + case notifyChats + case notifyForumTopic(peer: Api.Peer, topMsgId: Int32) + case notifyPeer(peer: Api.Peer) + case notifyUsers public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pageCaption(let text, let credit): + case .notifyBroadcasts: if boxed { - buffer.appendInt32(1869903447) + buffer.appendInt32(-703403793) } - text.serialize(buffer, true) - credit.serialize(buffer, true) + break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .pageCaption(let text, let credit): - return ("pageCaption", [("text", text as Any), ("credit", credit as Any)]) - } - } - - public static func parse_pageCaption(_ reader: BufferReader) -> PageCaption? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: Api.RichText? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageCaption.pageCaption(text: _1!, credit: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - indirect enum PageListItem: TypeConstructorDescription { - case pageListItemBlocks(blocks: [Api.PageBlock]) - case pageListItemText(text: Api.RichText) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pageListItemBlocks(let blocks): + case .notifyChats: if boxed { - buffer.appendInt32(635466748) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(blocks.count)) - for item in blocks { - item.serialize(buffer, true) + buffer.appendInt32(-1073230141) } + break - case .pageListItemText(let text): + case .notifyForumTopic(let peer, let topMsgId): if boxed { - buffer.appendInt32(-1188055347) + buffer.appendInt32(577659656) } - text.serialize(buffer, true) + peer.serialize(buffer, true) + serializeInt32(topMsgId, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .pageListItemBlocks(let blocks): - return ("pageListItemBlocks", [("blocks", blocks as Any)]) - case .pageListItemText(let text): - return ("pageListItemText", [("text", text as Any)]) - } - } - - public static func parse_pageListItemBlocks(_ reader: BufferReader) -> PageListItem? { - var _1: [Api.PageBlock]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PageListItem.pageListItemBlocks(blocks: _1!) - } - else { - return nil - } - } - public static func parse_pageListItemText(_ reader: BufferReader) -> PageListItem? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageListItem.pageListItemText(text: _1!) - } - else { - return nil - } - } - - } -} -public extension Api { - indirect enum PageListOrderedItem: TypeConstructorDescription { - case pageListOrderedItemBlocks(num: String, blocks: [Api.PageBlock]) - case pageListOrderedItemText(num: String, text: Api.RichText) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pageListOrderedItemBlocks(let num, let blocks): + case .notifyPeer(let peer): if boxed { - buffer.appendInt32(-1730311882) - } - serializeString(num, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(blocks.count)) - for item in blocks { - item.serialize(buffer, true) + buffer.appendInt32(-1613493288) } + peer.serialize(buffer, true) break - case .pageListOrderedItemText(let num, let text): + case .notifyUsers: if boxed { - buffer.appendInt32(1577484359) + buffer.appendInt32(-1261946036) } - serializeString(num, buffer: buffer, boxed: false) - text.serialize(buffer, true) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .pageListOrderedItemBlocks(let num, let blocks): - return ("pageListOrderedItemBlocks", [("num", num as Any), ("blocks", blocks as Any)]) - case .pageListOrderedItemText(let num, let text): - return ("pageListOrderedItemText", [("num", num as Any), ("text", text as Any)]) + case .notifyBroadcasts: + return ("notifyBroadcasts", []) + case .notifyChats: + return ("notifyChats", []) + case .notifyForumTopic(let peer, let topMsgId): + return ("notifyForumTopic", [("peer", peer as Any), ("topMsgId", topMsgId as Any)]) + case .notifyPeer(let peer): + return ("notifyPeer", [("peer", peer as Any)]) + case .notifyUsers: + return ("notifyUsers", []) } } - public static func parse_pageListOrderedItemBlocks(_ reader: BufferReader) -> PageListOrderedItem? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.PageBlock]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + public static func parse_notifyBroadcasts(_ reader: BufferReader) -> NotifyPeer? { + return Api.NotifyPeer.notifyBroadcasts + } + public static func parse_notifyChats(_ reader: BufferReader) -> NotifyPeer? { + return Api.NotifyPeer.notifyChats + } + public static func parse_notifyForumTopic(_ reader: BufferReader) -> NotifyPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.PageListOrderedItem.pageListOrderedItemBlocks(num: _1!, blocks: _2!) + return Api.NotifyPeer.notifyForumTopic(peer: _1!, topMsgId: _2!) } else { return nil } } - public static func parse_pageListOrderedItemText(_ reader: BufferReader) -> PageListOrderedItem? { - var _1: String? - _1 = parseString(reader) - var _2: Api.RichText? + public static func parse_notifyPeer(_ reader: BufferReader) -> NotifyPeer? { + var _1: Api.Peer? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText + _1 = Api.parse(reader, signature: signature) as? Api.Peer } let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageListOrderedItem.pageListOrderedItemText(num: _1!, text: _2!) + if _c1 { + return Api.NotifyPeer.notifyPeer(peer: _1!) } else { return nil } } + public static func parse_notifyUsers(_ reader: BufferReader) -> NotifyPeer? { + return Api.NotifyPeer.notifyUsers + } } } public extension Api { - enum PageRelatedArticle: TypeConstructorDescription { - case pageRelatedArticle(flags: Int32, url: String, webpageId: Int64, title: String?, description: String?, photoId: Int64?, author: String?, publishedDate: Int32?) + enum OutboxReadDate: TypeConstructorDescription { + case outboxReadDate(date: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pageRelatedArticle(let flags, let url, let webpageId, let title, let description, let photoId, let author, let publishedDate): + case .outboxReadDate(let date): if boxed { - buffer.appendInt32(-1282352120) + buffer.appendInt32(1001931436) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - serializeInt64(webpageId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(description!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt64(photoId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeString(author!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(publishedDate!, buffer: buffer, boxed: false)} + serializeInt32(date, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .pageRelatedArticle(let flags, let url, let webpageId, let title, let description, let photoId, let author, let publishedDate): - return ("pageRelatedArticle", [("flags", flags as Any), ("url", url as Any), ("webpageId", webpageId as Any), ("title", title as Any), ("description", description as Any), ("photoId", photoId as Any), ("author", author as Any), ("publishedDate", publishedDate as Any)]) + case .outboxReadDate(let date): + return ("outboxReadDate", [("date", date as Any)]) } } - public static func parse_pageRelatedArticle(_ reader: BufferReader) -> PageRelatedArticle? { + public static func parse_outboxReadDate(_ reader: BufferReader) -> OutboxReadDate? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } - var _6: Int64? - if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt64() } - var _7: String? - if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) } - var _8: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PageRelatedArticle.pageRelatedArticle(flags: _1!, url: _2!, webpageId: _3!, title: _4, description: _5, photoId: _6, author: _7, publishedDate: _8) + if _c1 { + return Api.OutboxReadDate.outboxReadDate(date: _1!) } else { return nil @@ -243,47 +135,71 @@ public extension Api { } } public extension Api { - indirect enum PageTableCell: TypeConstructorDescription { - case pageTableCell(flags: Int32, text: Api.RichText?, colspan: Int32?, rowspan: Int32?) + enum Page: TypeConstructorDescription { + case page(flags: Int32, url: String, blocks: [Api.PageBlock], photos: [Api.Photo], documents: [Api.Document], views: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pageTableCell(let flags, let text, let colspan, let rowspan): + case .page(let flags, let url, let blocks, let photos, let documents, let views): if boxed { - buffer.appendInt32(878078826) + buffer.appendInt32(-1738178803) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 7) != 0 {text!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(colspan!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(rowspan!, buffer: buffer, boxed: false)} + serializeString(url, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(blocks.count)) + for item in blocks { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(photos.count)) + for item in photos { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(documents.count)) + for item in documents { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .pageTableCell(let flags, let text, let colspan, let rowspan): - return ("pageTableCell", [("flags", flags as Any), ("text", text as Any), ("colspan", colspan as Any), ("rowspan", rowspan as Any)]) + case .page(let flags, let url, let blocks, let photos, let documents, let views): + return ("page", [("flags", flags as Any), ("url", url as Any), ("blocks", blocks as Any), ("photos", photos as Any), ("documents", documents as Any), ("views", views as Any)]) } } - public static func parse_pageTableCell(_ reader: BufferReader) -> PageTableCell? { + public static func parse_page(_ reader: BufferReader) -> Page? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.RichText? - if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } } - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } + var _2: String? + _2 = parseString(reader) + var _3: [Api.PageBlock]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _4: [Api.Photo]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) + } + var _5: [Api.Document]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + var _6: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_6 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 7) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PageTableCell.pageTableCell(flags: _1!, text: _2, colspan: _3, rowspan: _4) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Page.page(flags: _1!, url: _2!, blocks: _3!, photos: _4!, documents: _5!, views: _6) } else { return nil @@ -293,1007 +209,815 @@ public extension Api { } } public extension Api { - enum PageTableRow: TypeConstructorDescription { - case pageTableRow(cells: [Api.PageTableCell]) + indirect enum PageBlock: TypeConstructorDescription { + case pageBlockAnchor(name: String) + case pageBlockAudio(audioId: Int64, caption: Api.PageCaption) + case pageBlockAuthorDate(author: Api.RichText, publishedDate: Int32) + case pageBlockBlockquote(text: Api.RichText, caption: Api.RichText) + case pageBlockChannel(channel: Api.Chat) + case pageBlockCollage(items: [Api.PageBlock], caption: Api.PageCaption) + case pageBlockCover(cover: Api.PageBlock) + case pageBlockDetails(flags: Int32, blocks: [Api.PageBlock], title: Api.RichText) + case pageBlockDivider + case pageBlockEmbed(flags: Int32, url: String?, html: String?, posterPhotoId: Int64?, w: Int32?, h: Int32?, caption: Api.PageCaption) + case pageBlockEmbedPost(url: String, webpageId: Int64, authorPhotoId: Int64, author: String, date: Int32, blocks: [Api.PageBlock], caption: Api.PageCaption) + case pageBlockFooter(text: Api.RichText) + case pageBlockHeader(text: Api.RichText) + case pageBlockKicker(text: Api.RichText) + case pageBlockList(items: [Api.PageListItem]) + case pageBlockMap(geo: Api.GeoPoint, zoom: Int32, w: Int32, h: Int32, caption: Api.PageCaption) + case pageBlockOrderedList(items: [Api.PageListOrderedItem]) + case pageBlockParagraph(text: Api.RichText) + case pageBlockPhoto(flags: Int32, photoId: Int64, caption: Api.PageCaption, url: String?, webpageId: Int64?) + case pageBlockPreformatted(text: Api.RichText, language: String) + case pageBlockPullquote(text: Api.RichText, caption: Api.RichText) + case pageBlockRelatedArticles(title: Api.RichText, articles: [Api.PageRelatedArticle]) + case pageBlockSlideshow(items: [Api.PageBlock], caption: Api.PageCaption) + case pageBlockSubheader(text: Api.RichText) + case pageBlockSubtitle(text: Api.RichText) + case pageBlockTable(flags: Int32, title: Api.RichText, rows: [Api.PageTableRow]) + case pageBlockTitle(text: Api.RichText) + case pageBlockUnsupported + case pageBlockVideo(flags: Int32, videoId: Int64, caption: Api.PageCaption) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pageTableRow(let cells): + case .pageBlockAnchor(let name): + if boxed { + buffer.appendInt32(-837994576) + } + serializeString(name, buffer: buffer, boxed: false) + break + case .pageBlockAudio(let audioId, let caption): + if boxed { + buffer.appendInt32(-2143067670) + } + serializeInt64(audioId, buffer: buffer, boxed: false) + caption.serialize(buffer, true) + break + case .pageBlockAuthorDate(let author, let publishedDate): + if boxed { + buffer.appendInt32(-1162877472) + } + author.serialize(buffer, true) + serializeInt32(publishedDate, buffer: buffer, boxed: false) + break + case .pageBlockBlockquote(let text, let caption): if boxed { - buffer.appendInt32(-524237339) + buffer.appendInt32(641563686) + } + text.serialize(buffer, true) + caption.serialize(buffer, true) + break + case .pageBlockChannel(let channel): + if boxed { + buffer.appendInt32(-283684427) + } + channel.serialize(buffer, true) + break + case .pageBlockCollage(let items, let caption): + if boxed { + buffer.appendInt32(1705048653) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(cells.count)) - for item in cells { + buffer.appendInt32(Int32(items.count)) + for item in items { item.serialize(buffer, true) } + caption.serialize(buffer, true) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .pageTableRow(let cells): - return ("pageTableRow", [("cells", cells as Any)]) - } - } - - public static func parse_pageTableRow(_ reader: BufferReader) -> PageTableRow? { - var _1: [Api.PageTableCell]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageTableCell.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PageTableRow.pageTableRow(cells: _1!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum PasswordKdfAlgo: TypeConstructorDescription { - case passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1: Buffer, salt2: Buffer, g: Int32, p: Buffer) - case passwordKdfAlgoUnknown - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(let salt1, let salt2, let g, let p): + case .pageBlockCover(let cover): + if boxed { + buffer.appendInt32(972174080) + } + cover.serialize(buffer, true) + break + case .pageBlockDetails(let flags, let blocks, let title): if boxed { - buffer.appendInt32(982592842) + buffer.appendInt32(1987480557) + } + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(blocks.count)) + for item in blocks { + item.serialize(buffer, true) } - serializeBytes(salt1, buffer: buffer, boxed: false) - serializeBytes(salt2, buffer: buffer, boxed: false) - serializeInt32(g, buffer: buffer, boxed: false) - serializeBytes(p, buffer: buffer, boxed: false) + title.serialize(buffer, true) break - case .passwordKdfAlgoUnknown: + case .pageBlockDivider: if boxed { - buffer.appendInt32(-732254058) + buffer.appendInt32(-618614392) } break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(let salt1, let salt2, let g, let p): - return ("passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow", [("salt1", salt1 as Any), ("salt2", salt2 as Any), ("g", g as Any), ("p", p as Any)]) - case .passwordKdfAlgoUnknown: - return ("passwordKdfAlgoUnknown", []) - } - } - - public static func parse_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(_ reader: BufferReader) -> PasswordKdfAlgo? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Buffer? - _4 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PasswordKdfAlgo.passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1: _1!, salt2: _2!, g: _3!, p: _4!) - } - else { - return nil - } - } - public static func parse_passwordKdfAlgoUnknown(_ reader: BufferReader) -> PasswordKdfAlgo? { - return Api.PasswordKdfAlgo.passwordKdfAlgoUnknown - } - - } -} -public extension Api { - enum PaymentCharge: TypeConstructorDescription { - case paymentCharge(id: String, providerChargeId: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentCharge(let id, let providerChargeId): + case .pageBlockEmbed(let flags, let url, let html, let posterPhotoId, let w, let h, let caption): if boxed { - buffer.appendInt32(-368917890) + buffer.appendInt32(-1468953147) } - serializeString(id, buffer: buffer, boxed: false) - serializeString(providerChargeId, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(html!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt64(posterPhotoId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {serializeInt32(w!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {serializeInt32(h!, buffer: buffer, boxed: false)} + caption.serialize(buffer, true) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .paymentCharge(let id, let providerChargeId): - return ("paymentCharge", [("id", id as Any), ("providerChargeId", providerChargeId as Any)]) - } - } - - public static func parse_paymentCharge(_ reader: BufferReader) -> PaymentCharge? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PaymentCharge.paymentCharge(id: _1!, providerChargeId: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum PaymentFormMethod: TypeConstructorDescription { - case paymentFormMethod(url: String, title: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentFormMethod(let url, let title): + case .pageBlockEmbedPost(let url, let webpageId, let authorPhotoId, let author, let date, let blocks, let caption): if boxed { - buffer.appendInt32(-1996951013) + buffer.appendInt32(-229005301) } serializeString(url, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - break + serializeInt64(webpageId, buffer: buffer, boxed: false) + serializeInt64(authorPhotoId, buffer: buffer, boxed: false) + serializeString(author, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(blocks.count)) + for item in blocks { + item.serialize(buffer, true) + } + caption.serialize(buffer, true) + break + case .pageBlockFooter(let text): + if boxed { + buffer.appendInt32(1216809369) + } + text.serialize(buffer, true) + break + case .pageBlockHeader(let text): + if boxed { + buffer.appendInt32(-1076861716) + } + text.serialize(buffer, true) + break + case .pageBlockKicker(let text): + if boxed { + buffer.appendInt32(504660880) + } + text.serialize(buffer, true) + break + case .pageBlockList(let items): + if boxed { + buffer.appendInt32(-454524911) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(items.count)) + for item in items { + item.serialize(buffer, true) + } + break + case .pageBlockMap(let geo, let zoom, let w, let h, let caption): + if boxed { + buffer.appendInt32(-1538310410) + } + geo.serialize(buffer, true) + serializeInt32(zoom, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + caption.serialize(buffer, true) + break + case .pageBlockOrderedList(let items): + if boxed { + buffer.appendInt32(-1702174239) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(items.count)) + for item in items { + item.serialize(buffer, true) + } + break + case .pageBlockParagraph(let text): + if boxed { + buffer.appendInt32(1182402406) + } + text.serialize(buffer, true) + break + case .pageBlockPhoto(let flags, let photoId, let caption, let url, let webpageId): + if boxed { + buffer.appendInt32(391759200) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(photoId, buffer: buffer, boxed: false) + caption.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(webpageId!, buffer: buffer, boxed: false)} + break + case .pageBlockPreformatted(let text, let language): + if boxed { + buffer.appendInt32(-1066346178) + } + text.serialize(buffer, true) + serializeString(language, buffer: buffer, boxed: false) + break + case .pageBlockPullquote(let text, let caption): + if boxed { + buffer.appendInt32(1329878739) + } + text.serialize(buffer, true) + caption.serialize(buffer, true) + break + case .pageBlockRelatedArticles(let title, let articles): + if boxed { + buffer.appendInt32(370236054) + } + title.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(articles.count)) + for item in articles { + item.serialize(buffer, true) + } + break + case .pageBlockSlideshow(let items, let caption): + if boxed { + buffer.appendInt32(52401552) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(items.count)) + for item in items { + item.serialize(buffer, true) + } + caption.serialize(buffer, true) + break + case .pageBlockSubheader(let text): + if boxed { + buffer.appendInt32(-248793375) + } + text.serialize(buffer, true) + break + case .pageBlockSubtitle(let text): + if boxed { + buffer.appendInt32(-1879401953) + } + text.serialize(buffer, true) + break + case .pageBlockTable(let flags, let title, let rows): + if boxed { + buffer.appendInt32(-1085412734) + } + serializeInt32(flags, buffer: buffer, boxed: false) + title.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(rows.count)) + for item in rows { + item.serialize(buffer, true) + } + break + case .pageBlockTitle(let text): + if boxed { + buffer.appendInt32(1890305021) + } + text.serialize(buffer, true) + break + case .pageBlockUnsupported: + if boxed { + buffer.appendInt32(324435594) + } + + break + case .pageBlockVideo(let flags, let videoId, let caption): + if boxed { + buffer.appendInt32(2089805750) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(videoId, buffer: buffer, boxed: false) + caption.serialize(buffer, true) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .paymentFormMethod(let url, let title): - return ("paymentFormMethod", [("url", url as Any), ("title", title as Any)]) - } - } - - public static func parse_paymentFormMethod(_ reader: BufferReader) -> PaymentFormMethod? { + case .pageBlockAnchor(let name): + return ("pageBlockAnchor", [("name", name as Any)]) + case .pageBlockAudio(let audioId, let caption): + return ("pageBlockAudio", [("audioId", audioId as Any), ("caption", caption as Any)]) + case .pageBlockAuthorDate(let author, let publishedDate): + return ("pageBlockAuthorDate", [("author", author as Any), ("publishedDate", publishedDate as Any)]) + case .pageBlockBlockquote(let text, let caption): + return ("pageBlockBlockquote", [("text", text as Any), ("caption", caption as Any)]) + case .pageBlockChannel(let channel): + return ("pageBlockChannel", [("channel", channel as Any)]) + case .pageBlockCollage(let items, let caption): + return ("pageBlockCollage", [("items", items as Any), ("caption", caption as Any)]) + case .pageBlockCover(let cover): + return ("pageBlockCover", [("cover", cover as Any)]) + case .pageBlockDetails(let flags, let blocks, let title): + return ("pageBlockDetails", [("flags", flags as Any), ("blocks", blocks as Any), ("title", title as Any)]) + case .pageBlockDivider: + return ("pageBlockDivider", []) + case .pageBlockEmbed(let flags, let url, let html, let posterPhotoId, let w, let h, let caption): + return ("pageBlockEmbed", [("flags", flags as Any), ("url", url as Any), ("html", html as Any), ("posterPhotoId", posterPhotoId as Any), ("w", w as Any), ("h", h as Any), ("caption", caption as Any)]) + case .pageBlockEmbedPost(let url, let webpageId, let authorPhotoId, let author, let date, let blocks, let caption): + return ("pageBlockEmbedPost", [("url", url as Any), ("webpageId", webpageId as Any), ("authorPhotoId", authorPhotoId as Any), ("author", author as Any), ("date", date as Any), ("blocks", blocks as Any), ("caption", caption as Any)]) + case .pageBlockFooter(let text): + return ("pageBlockFooter", [("text", text as Any)]) + case .pageBlockHeader(let text): + return ("pageBlockHeader", [("text", text as Any)]) + case .pageBlockKicker(let text): + return ("pageBlockKicker", [("text", text as Any)]) + case .pageBlockList(let items): + return ("pageBlockList", [("items", items as Any)]) + case .pageBlockMap(let geo, let zoom, let w, let h, let caption): + return ("pageBlockMap", [("geo", geo as Any), ("zoom", zoom as Any), ("w", w as Any), ("h", h as Any), ("caption", caption as Any)]) + case .pageBlockOrderedList(let items): + return ("pageBlockOrderedList", [("items", items as Any)]) + case .pageBlockParagraph(let text): + return ("pageBlockParagraph", [("text", text as Any)]) + case .pageBlockPhoto(let flags, let photoId, let caption, let url, let webpageId): + return ("pageBlockPhoto", [("flags", flags as Any), ("photoId", photoId as Any), ("caption", caption as Any), ("url", url as Any), ("webpageId", webpageId as Any)]) + case .pageBlockPreformatted(let text, let language): + return ("pageBlockPreformatted", [("text", text as Any), ("language", language as Any)]) + case .pageBlockPullquote(let text, let caption): + return ("pageBlockPullquote", [("text", text as Any), ("caption", caption as Any)]) + case .pageBlockRelatedArticles(let title, let articles): + return ("pageBlockRelatedArticles", [("title", title as Any), ("articles", articles as Any)]) + case .pageBlockSlideshow(let items, let caption): + return ("pageBlockSlideshow", [("items", items as Any), ("caption", caption as Any)]) + case .pageBlockSubheader(let text): + return ("pageBlockSubheader", [("text", text as Any)]) + case .pageBlockSubtitle(let text): + return ("pageBlockSubtitle", [("text", text as Any)]) + case .pageBlockTable(let flags, let title, let rows): + return ("pageBlockTable", [("flags", flags as Any), ("title", title as Any), ("rows", rows as Any)]) + case .pageBlockTitle(let text): + return ("pageBlockTitle", [("text", text as Any)]) + case .pageBlockUnsupported: + return ("pageBlockUnsupported", []) + case .pageBlockVideo(let flags, let videoId, let caption): + return ("pageBlockVideo", [("flags", flags as Any), ("videoId", videoId as Any), ("caption", caption as Any)]) + } + } + + public static func parse_pageBlockAnchor(_ reader: BufferReader) -> PageBlock? { var _1: String? _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockAnchor(name: _1!) + } + else { + return nil + } + } + public static func parse_pageBlockAudio(_ reader: BufferReader) -> PageBlock? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.PageCaption? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PageCaption + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.PaymentFormMethod.paymentFormMethod(url: _1!, title: _2!) + return Api.PageBlock.pageBlockAudio(audioId: _1!, caption: _2!) } else { return nil } } - - } -} -public extension Api { - enum PaymentRequestedInfo: TypeConstructorDescription { - case paymentRequestedInfo(flags: Int32, name: String?, phone: String?, email: String?, shippingAddress: Api.PostAddress?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentRequestedInfo(let flags, let name, let phone, let email, let shippingAddress): - if boxed { - buffer.appendInt32(-1868808300) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(name!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(phone!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(email!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {shippingAddress!.serialize(buffer, true)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .paymentRequestedInfo(let flags, let name, let phone, let email, let shippingAddress): - return ("paymentRequestedInfo", [("flags", flags as Any), ("name", name as Any), ("phone", phone as Any), ("email", email as Any), ("shippingAddress", shippingAddress as Any)]) - } - } - - public static func parse_paymentRequestedInfo(_ reader: BufferReader) -> PaymentRequestedInfo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } - var _4: String? - if Int(_1!) & Int(1 << 2) != 0 {_4 = parseString(reader) } - var _5: Api.PostAddress? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.PostAddress - } } + public static func parse_pageBlockAuthorDate(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PaymentRequestedInfo.paymentRequestedInfo(flags: _1!, name: _2, phone: _3, email: _4, shippingAddress: _5) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockAuthorDate(author: _1!, publishedDate: _2!) } else { return nil } } - - } -} -public extension Api { - enum PaymentSavedCredentials: TypeConstructorDescription { - case paymentSavedCredentialsCard(id: String, title: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentSavedCredentialsCard(let id, let title): - if boxed { - buffer.appendInt32(-842892769) - } - serializeString(id, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .paymentSavedCredentialsCard(let id, let title): - return ("paymentSavedCredentialsCard", [("id", id as Any), ("title", title as Any)]) - } - } - - public static func parse_paymentSavedCredentialsCard(_ reader: BufferReader) -> PaymentSavedCredentials? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) + public static func parse_pageBlockBlockquote(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.PaymentSavedCredentials.paymentSavedCredentialsCard(id: _1!, title: _2!) + return Api.PageBlock.pageBlockBlockquote(text: _1!, caption: _2!) } else { return nil } } - - } -} -public extension Api { - enum Peer: TypeConstructorDescription { - case peerChannel(channelId: Int64) - case peerChat(chatId: Int64) - case peerUser(userId: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerChannel(let channelId): - if boxed { - buffer.appendInt32(-1566230754) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - break - case .peerChat(let chatId): - if boxed { - buffer.appendInt32(918946202) - } - serializeInt64(chatId, buffer: buffer, boxed: false) - break - case .peerUser(let userId): - if boxed { - buffer.appendInt32(1498486562) - } - serializeInt64(userId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .peerChannel(let channelId): - return ("peerChannel", [("channelId", channelId as Any)]) - case .peerChat(let chatId): - return ("peerChat", [("chatId", chatId as Any)]) - case .peerUser(let userId): - return ("peerUser", [("userId", userId as Any)]) - } - } - - public static func parse_peerChannel(_ reader: BufferReader) -> Peer? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_pageBlockChannel(_ reader: BufferReader) -> PageBlock? { + var _1: Api.Chat? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Chat + } let _c1 = _1 != nil if _c1 { - return Api.Peer.peerChannel(channelId: _1!) + return Api.PageBlock.pageBlockChannel(channel: _1!) } else { return nil } } - public static func parse_peerChat(_ reader: BufferReader) -> Peer? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_pageBlockCollage(_ reader: BufferReader) -> PageBlock? { + var _1: [Api.PageBlock]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _2: Api.PageCaption? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PageCaption + } let _c1 = _1 != nil - if _c1 { - return Api.Peer.peerChat(chatId: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockCollage(items: _1!, caption: _2!) } else { return nil } } - public static func parse_peerUser(_ reader: BufferReader) -> Peer? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_pageBlockCover(_ reader: BufferReader) -> PageBlock? { + var _1: Api.PageBlock? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PageBlock + } let _c1 = _1 != nil if _c1 { - return Api.Peer.peerUser(userId: _1!) + return Api.PageBlock.pageBlockCover(cover: _1!) } else { return nil } } - - } -} -public extension Api { - enum PeerBlocked: TypeConstructorDescription { - case peerBlocked(peerId: Api.Peer, date: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerBlocked(let peerId, let date): - if boxed { - buffer.appendInt32(-386039788) - } - peerId.serialize(buffer, true) - serializeInt32(date, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .peerBlocked(let peerId, let date): - return ("peerBlocked", [("peerId", peerId as Any), ("date", date as Any)]) - } - } - - public static func parse_peerBlocked(_ reader: BufferReader) -> PeerBlocked? { - var _1: Api.Peer? + public static func parse_pageBlockDetails(_ reader: BufferReader) -> PageBlock? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.PageBlock]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _3: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + _3 = Api.parse(reader, signature: signature) as? Api.RichText } - var _2: Int32? - _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PeerBlocked.peerBlocked(peerId: _1!, date: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.PageBlock.pageBlockDetails(flags: _1!, blocks: _2!, title: _3!) } else { return nil } } - - } -} -public extension Api { - enum PeerColor: TypeConstructorDescription { - case peerColor(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerColor(let flags, let color, let backgroundEmojiId): - if boxed { - buffer.appendInt32(-1253352753) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .peerColor(let flags, let color, let backgroundEmojiId): - return ("peerColor", [("flags", flags as Any), ("color", color as Any), ("backgroundEmojiId", backgroundEmojiId as Any)]) - } - } - - public static func parse_peerColor(_ reader: BufferReader) -> PeerColor? { + public static func parse_pageBlockDivider(_ reader: BufferReader) -> PageBlock? { + return Api.PageBlock.pageBlockDivider + } + public static func parse_pageBlockEmbed(_ reader: BufferReader) -> PageBlock? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _2: String? + if Int(_1!) & Int(1 << 1) != 0 {_2 = parseString(reader) } + var _3: String? + if Int(_1!) & Int(1 << 2) != 0 {_3 = parseString(reader) } + var _4: Int64? + if Int(_1!) & Int(1 << 4) != 0 {_4 = reader.readInt64() } + var _5: Int32? + if Int(_1!) & Int(1 << 5) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 5) != 0 {_6 = reader.readInt32() } + var _7: Api.PageCaption? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.PageCaption + } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PageBlock.pageBlockEmbed(flags: _1!, url: _2, html: _3, posterPhotoId: _4, w: _5, h: _6, caption: _7!) + } + else { + return nil + } + } + public static func parse_pageBlockEmbedPost(_ reader: BufferReader) -> PageBlock? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() var _3: Int64? - if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt64() } + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: [Api.PageBlock]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _7: Api.PageCaption? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.PageCaption + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.PeerColor.peerColor(flags: _1!, color: _2, backgroundEmojiId: _3) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PageBlock.pageBlockEmbedPost(url: _1!, webpageId: _2!, authorPhotoId: _3!, author: _4!, date: _5!, blocks: _6!, caption: _7!) } else { return nil } } - - } -} -public extension Api { - enum PeerLocated: TypeConstructorDescription { - case peerLocated(peer: Api.Peer, expires: Int32, distance: Int32) - case peerSelfLocated(expires: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerLocated(let peer, let expires, let distance): - if boxed { - buffer.appendInt32(-901375139) - } - peer.serialize(buffer, true) - serializeInt32(expires, buffer: buffer, boxed: false) - serializeInt32(distance, buffer: buffer, boxed: false) - break - case .peerSelfLocated(let expires): - if boxed { - buffer.appendInt32(-118740917) - } - serializeInt32(expires, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .peerLocated(let peer, let expires, let distance): - return ("peerLocated", [("peer", peer as Any), ("expires", expires as Any), ("distance", distance as Any)]) - case .peerSelfLocated(let expires): - return ("peerSelfLocated", [("expires", expires as Any)]) - } - } - - public static func parse_peerLocated(_ reader: BufferReader) -> PeerLocated? { - var _1: Api.Peer? + public static func parse_pageBlockFooter(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockFooter(text: _1!) + } + else { + return nil + } + } + public static func parse_pageBlockHeader(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockHeader(text: _1!) + } + else { + return nil + } + } + public static func parse_pageBlockKicker(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockKicker(text: _1!) + } + else { + return nil + } + } + public static func parse_pageBlockList(_ reader: BufferReader) -> PageBlock? { + var _1: [Api.PageListItem]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageListItem.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockList(items: _1!) + } + else { + return nil + } + } + public static func parse_pageBlockMap(_ reader: BufferReader) -> PageBlock? { + var _1: Api.GeoPoint? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint } var _2: Int32? _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Api.PageCaption? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.PageCaption + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PeerLocated.peerLocated(peer: _1!, expires: _2!, distance: _3!) + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PageBlock.pageBlockMap(geo: _1!, zoom: _2!, w: _3!, h: _4!, caption: _5!) } else { return nil } } - public static func parse_peerSelfLocated(_ reader: BufferReader) -> PeerLocated? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_pageBlockOrderedList(_ reader: BufferReader) -> PageBlock? { + var _1: [Api.PageListOrderedItem]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageListOrderedItem.self) + } let _c1 = _1 != nil if _c1 { - return Api.PeerLocated.peerSelfLocated(expires: _1!) + return Api.PageBlock.pageBlockOrderedList(items: _1!) } else { return nil } } - - } -} -public extension Api { - enum PeerNotifySettings: TypeConstructorDescription { - case peerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, iosSound: Api.NotificationSound?, androidSound: Api.NotificationSound?, otherSound: Api.NotificationSound?, storiesMuted: Api.Bool?, storiesHideSender: Api.Bool?, storiesIosSound: Api.NotificationSound?, storiesAndroidSound: Api.NotificationSound?, storiesOtherSound: Api.NotificationSound?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let iosSound, let androidSound, let otherSound, let storiesMuted, let storiesHideSender, let storiesIosSound, let storiesAndroidSound, let storiesOtherSound): - if boxed { - buffer.appendInt32(-1721619444) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {showPreviews!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {silent!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(muteUntil!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {iosSound!.serialize(buffer, true)} - if Int(flags) & Int(1 << 4) != 0 {androidSound!.serialize(buffer, true)} - if Int(flags) & Int(1 << 5) != 0 {otherSound!.serialize(buffer, true)} - if Int(flags) & Int(1 << 6) != 0 {storiesMuted!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {storiesHideSender!.serialize(buffer, true)} - if Int(flags) & Int(1 << 8) != 0 {storiesIosSound!.serialize(buffer, true)} - if Int(flags) & Int(1 << 9) != 0 {storiesAndroidSound!.serialize(buffer, true)} - if Int(flags) & Int(1 << 10) != 0 {storiesOtherSound!.serialize(buffer, true)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .peerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let iosSound, let androidSound, let otherSound, let storiesMuted, let storiesHideSender, let storiesIosSound, let storiesAndroidSound, let storiesOtherSound): - return ("peerNotifySettings", [("flags", flags as Any), ("showPreviews", showPreviews as Any), ("silent", silent as Any), ("muteUntil", muteUntil as Any), ("iosSound", iosSound as Any), ("androidSound", androidSound as Any), ("otherSound", otherSound as Any), ("storiesMuted", storiesMuted as Any), ("storiesHideSender", storiesHideSender as Any), ("storiesIosSound", storiesIosSound as Any), ("storiesAndroidSound", storiesAndroidSound as Any), ("storiesOtherSound", storiesOtherSound as Any)]) - } - } - - public static func parse_peerNotifySettings(_ reader: BufferReader) -> PeerNotifySettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _3: Api.Bool? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } - var _5: Api.NotificationSound? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } } - var _6: Api.NotificationSound? - if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } } - var _7: Api.NotificationSound? - if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } } - var _8: Api.Bool? - if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _9: Api.Bool? - if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _10: Api.NotificationSound? - if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } } - var _11: Api.NotificationSound? - if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } } - var _12: Api.NotificationSound? - if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } } + public static func parse_pageBlockParagraph(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 7) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 8) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 9) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 10) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.PeerNotifySettings.peerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, iosSound: _5, androidSound: _6, otherSound: _7, storiesMuted: _8, storiesHideSender: _9, storiesIosSound: _10, storiesAndroidSound: _11, storiesOtherSound: _12) + if _c1 { + return Api.PageBlock.pageBlockParagraph(text: _1!) } else { return nil } } - - } -} -public extension Api { - enum PeerSettings: TypeConstructorDescription { - case peerSettings(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?, businessBotId: Int64?, businessBotManageUrl: String?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl): - if boxed { - buffer.appendInt32(-1395233698) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 6) != 0 {serializeInt32(geoDistance!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {serializeString(requestChatTitle!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {serializeInt32(requestChatDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 13) != 0 {serializeInt64(businessBotId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 13) != 0 {serializeString(businessBotManageUrl!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl): - return ("peerSettings", [("flags", flags as Any), ("geoDistance", geoDistance as Any), ("requestChatTitle", requestChatTitle as Any), ("requestChatDate", requestChatDate as Any), ("businessBotId", businessBotId as Any), ("businessBotManageUrl", businessBotManageUrl as Any)]) - } - } - - public static func parse_peerSettings(_ reader: BufferReader) -> PeerSettings? { + public static func parse_pageBlockPhoto(_ reader: BufferReader) -> PageBlock? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 6) != 0 {_2 = reader.readInt32() } - var _3: String? - if Int(_1!) & Int(1 << 9) != 0 {_3 = parseString(reader) } - var _4: Int32? - if Int(_1!) & Int(1 << 9) != 0 {_4 = reader.readInt32() } + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.PageCaption? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.PageCaption + } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } var _5: Int64? - if Int(_1!) & Int(1 << 13) != 0 {_5 = reader.readInt64() } - var _6: String? - if Int(_1!) & Int(1 << 13) != 0 {_6 = parseString(reader) } + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 9) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 13) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 13) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PageBlock.pageBlockPhoto(flags: _1!, photoId: _2!, caption: _3!, url: _4, webpageId: _5) } else { return nil } } - - } -} -public extension Api { - enum PeerStories: TypeConstructorDescription { - case peerStories(flags: Int32, peer: Api.Peer, maxReadId: Int32?, stories: [Api.StoryItem]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerStories(let flags, let peer, let maxReadId, let stories): - if boxed { - buffer.appendInt32(-1707742823) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(maxReadId!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stories.count)) - for item in stories { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .peerStories(let flags, let peer, let maxReadId, let stories): - return ("peerStories", [("flags", flags as Any), ("peer", peer as Any), ("maxReadId", maxReadId as Any), ("stories", stories as Any)]) - } - } - - public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? + public static func parse_pageBlockPreformatted(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: [Api.StoryItem]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockPreformatted(text: _1!, language: _2!) + } + else { + return nil + } + } + public static func parse_pageBlockPullquote(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PeerStories.peerStories(flags: _1!, peer: _2!, maxReadId: _3, stories: _4!) + if _c1 && _c2 { + return Api.PageBlock.pageBlockPullquote(text: _1!, caption: _2!) } else { return nil } } - - } -} -public extension Api { - enum PhoneCall: TypeConstructorDescription { - case phoneCall(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAOrB: Buffer, keyFingerprint: Int64, protocol: Api.PhoneCallProtocol, connections: [Api.PhoneConnection], startDate: Int32, customParameters: Api.DataJSON?) - case phoneCallAccepted(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gB: Buffer, protocol: Api.PhoneCallProtocol) - case phoneCallDiscarded(flags: Int32, id: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?) - case phoneCallEmpty(id: Int64) - case phoneCallRequested(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAHash: Buffer, protocol: Api.PhoneCallProtocol) - case phoneCallWaiting(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, protocol: Api.PhoneCallProtocol, receiveDate: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .phoneCall(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAOrB, let keyFingerprint, let `protocol`, let connections, let startDate, let customParameters): - if boxed { - buffer.appendInt32(810769141) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) - serializeBytes(gAOrB, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - `protocol`.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(connections.count)) - for item in connections { - item.serialize(buffer, true) - } - serializeInt32(startDate, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 7) != 0 {customParameters!.serialize(buffer, true)} - break - case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`): - if boxed { - buffer.appendInt32(912311057) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) - serializeBytes(gB, buffer: buffer, boxed: false) - `protocol`.serialize(buffer, true) - break - case .phoneCallDiscarded(let flags, let id, let reason, let duration): - if boxed { - buffer.appendInt32(1355435489) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {reason!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} - break - case .phoneCallEmpty(let id): - if boxed { - buffer.appendInt32(1399245077) - } - serializeInt64(id, buffer: buffer, boxed: false) - break - case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`): - if boxed { - buffer.appendInt32(347139340) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) - serializeBytes(gAHash, buffer: buffer, boxed: false) - `protocol`.serialize(buffer, true) - break - case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate): - if boxed { - buffer.appendInt32(-987599081) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) - `protocol`.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(receiveDate!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .phoneCall(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAOrB, let keyFingerprint, let `protocol`, let connections, let startDate, let customParameters): - return ("phoneCall", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gAOrB", gAOrB as Any), ("keyFingerprint", keyFingerprint as Any), ("`protocol`", `protocol` as Any), ("connections", connections as Any), ("startDate", startDate as Any), ("customParameters", customParameters as Any)]) - case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`): - return ("phoneCallAccepted", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gB", gB as Any), ("`protocol`", `protocol` as Any)]) - case .phoneCallDiscarded(let flags, let id, let reason, let duration): - return ("phoneCallDiscarded", [("flags", flags as Any), ("id", id as Any), ("reason", reason as Any), ("duration", duration as Any)]) - case .phoneCallEmpty(let id): - return ("phoneCallEmpty", [("id", id as Any)]) - case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`): - return ("phoneCallRequested", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gAHash", gAHash as Any), ("`protocol`", `protocol` as Any)]) - case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate): - return ("phoneCallWaiting", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("`protocol`", `protocol` as Any), ("receiveDate", receiveDate as Any)]) - } - } - - public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() - var _7: Buffer? - _7 = parseBytes(reader) - var _8: Int64? - _8 = reader.readInt64() - var _9: Api.PhoneCallProtocol? + public static func parse_pageBlockRelatedArticles(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _10: [Api.PhoneConnection]? + var _2: [Api.PageRelatedArticle]? if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhoneConnection.self) - } - var _11: Int32? - _11 = reader.readInt32() - var _12: Api.DataJSON? - if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.DataJSON - } } + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageRelatedArticle.self) + } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.PhoneCall.phoneCall(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAOrB: _7!, keyFingerprint: _8!, protocol: _9!, connections: _10!, startDate: _11!, customParameters: _12) + if _c1 && _c2 { + return Api.PageBlock.pageBlockRelatedArticles(title: _1!, articles: _2!) } else { return nil } } - public static func parse_phoneCallAccepted(_ reader: BufferReader) -> PhoneCall? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() - var _7: Buffer? - _7 = parseBytes(reader) - var _8: Api.PhoneCallProtocol? + public static func parse_pageBlockSlideshow(_ reader: BufferReader) -> PageBlock? { + var _1: [Api.PageBlock]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _2: Api.PageCaption? if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + _2 = Api.parse(reader, signature: signature) as? Api.PageCaption } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!) + if _c1 && _c2 { + return Api.PageBlock.pageBlockSlideshow(items: _1!, caption: _2!) } else { return nil } } - public static func parse_phoneCallDiscarded(_ reader: BufferReader) -> PhoneCall? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.PhoneCallDiscardReason? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.PhoneCallDiscardReason - } } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + public static func parse_pageBlockSubheader(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhoneCall.phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4) + if _c1 { + return Api.PageBlock.pageBlockSubheader(text: _1!) } else { return nil } } - public static func parse_phoneCallEmpty(_ reader: BufferReader) -> PhoneCall? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_pageBlockSubtitle(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } let _c1 = _1 != nil if _c1 { - return Api.PhoneCall.phoneCallEmpty(id: _1!) + return Api.PageBlock.pageBlockSubtitle(text: _1!) } else { return nil } } - public static func parse_phoneCallRequested(_ reader: BufferReader) -> PhoneCall? { + public static func parse_pageBlockTable(_ reader: BufferReader) -> PageBlock? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() - var _7: Buffer? - _7 = parseBytes(reader) - var _8: Api.PhoneCallProtocol? + var _2: Api.RichText? if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _3: [Api.PageTableRow]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageTableRow.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!) + if _c1 && _c2 && _c3 { + return Api.PageBlock.pageBlockTable(flags: _1!, title: _2!, rows: _3!) + } + else { + return nil + } + } + public static func parse_pageBlockTitle(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockTitle(text: _1!) } else { return nil } } - public static func parse_phoneCallWaiting(_ reader: BufferReader) -> PhoneCall? { + public static func parse_pageBlockUnsupported(_ reader: BufferReader) -> PageBlock? { + return Api.PageBlock.pageBlockUnsupported + } + public static func parse_pageBlockVideo(_ reader: BufferReader) -> PageBlock? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() - var _7: Api.PhoneCallProtocol? + var _3: Api.PageCaption? if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + _3 = Api.parse(reader, signature: signature) as? Api.PageCaption } - var _8: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_8 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8) + if _c1 && _c2 && _c3 { + return Api.PageBlock.pageBlockVideo(flags: _1!, videoId: _2!, caption: _3!) } else { return nil @@ -1302,67 +1026,3 @@ public extension Api { } } -public extension Api { - enum PhoneCallDiscardReason: TypeConstructorDescription { - case phoneCallDiscardReasonBusy - case phoneCallDiscardReasonDisconnect - case phoneCallDiscardReasonHangup - case phoneCallDiscardReasonMissed - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .phoneCallDiscardReasonBusy: - if boxed { - buffer.appendInt32(-84416311) - } - - break - case .phoneCallDiscardReasonDisconnect: - if boxed { - buffer.appendInt32(-527056480) - } - - break - case .phoneCallDiscardReasonHangup: - if boxed { - buffer.appendInt32(1471006352) - } - - break - case .phoneCallDiscardReasonMissed: - if boxed { - buffer.appendInt32(-2048646399) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .phoneCallDiscardReasonBusy: - return ("phoneCallDiscardReasonBusy", []) - case .phoneCallDiscardReasonDisconnect: - return ("phoneCallDiscardReasonDisconnect", []) - case .phoneCallDiscardReasonHangup: - return ("phoneCallDiscardReasonHangup", []) - case .phoneCallDiscardReasonMissed: - return ("phoneCallDiscardReasonMissed", []) - } - } - - public static func parse_phoneCallDiscardReasonBusy(_ reader: BufferReader) -> PhoneCallDiscardReason? { - return Api.PhoneCallDiscardReason.phoneCallDiscardReasonBusy - } - public static func parse_phoneCallDiscardReasonDisconnect(_ reader: BufferReader) -> PhoneCallDiscardReason? { - return Api.PhoneCallDiscardReason.phoneCallDiscardReasonDisconnect - } - public static func parse_phoneCallDiscardReasonHangup(_ reader: BufferReader) -> PhoneCallDiscardReason? { - return Api.PhoneCallDiscardReason.phoneCallDiscardReasonHangup - } - public static func parse_phoneCallDiscardReasonMissed(_ reader: BufferReader) -> PhoneCallDiscardReason? { - return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMissed - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api18.swift b/submodules/TelegramApi/Sources/Api18.swift index 86d042f173f..eb008492305 100644 --- a/submodules/TelegramApi/Sources/Api18.swift +++ b/submodules/TelegramApi/Sources/Api18.swift @@ -1,49 +1,39 @@ public extension Api { - enum PhoneCallProtocol: TypeConstructorDescription { - case phoneCallProtocol(flags: Int32, minLayer: Int32, maxLayer: Int32, libraryVersions: [String]) + indirect enum PageCaption: TypeConstructorDescription { + case pageCaption(text: Api.RichText, credit: Api.RichText) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .phoneCallProtocol(let flags, let minLayer, let maxLayer, let libraryVersions): + case .pageCaption(let text, let credit): if boxed { - buffer.appendInt32(-58224696) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(minLayer, buffer: buffer, boxed: false) - serializeInt32(maxLayer, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(libraryVersions.count)) - for item in libraryVersions { - serializeString(item, buffer: buffer, boxed: false) + buffer.appendInt32(1869903447) } + text.serialize(buffer, true) + credit.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .phoneCallProtocol(let flags, let minLayer, let maxLayer, let libraryVersions): - return ("phoneCallProtocol", [("flags", flags as Any), ("minLayer", minLayer as Any), ("maxLayer", maxLayer as Any), ("libraryVersions", libraryVersions as Any)]) + case .pageCaption(let text, let credit): + return ("pageCaption", [("text", text as Any), ("credit", credit as Any)]) } } - public static func parse_phoneCallProtocol(_ reader: BufferReader) -> PhoneCallProtocol? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [String]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + public static func parse_pageCaption(_ reader: BufferReader) -> PageCaption? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhoneCallProtocol.phoneCallProtocol(flags: _1!, minLayer: _2!, maxLayer: _3!, libraryVersions: _4!) + if _c1 && _c2 { + return Api.PageCaption.pageCaption(text: _1!, credit: _2!) } else { return nil @@ -53,97 +43,61 @@ public extension Api { } } public extension Api { - enum PhoneConnection: TypeConstructorDescription { - case phoneConnection(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, peerTag: Buffer) - case phoneConnectionWebrtc(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, username: String, password: String) + indirect enum PageListItem: TypeConstructorDescription { + case pageListItemBlocks(blocks: [Api.PageBlock]) + case pageListItemText(text: Api.RichText) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .phoneConnection(let flags, let id, let ip, let ipv6, let port, let peerTag): + case .pageListItemBlocks(let blocks): if boxed { - buffer.appendInt32(-1665063993) + buffer.appendInt32(635466748) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(blocks.count)) + for item in blocks { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeString(ip, buffer: buffer, boxed: false) - serializeString(ipv6, buffer: buffer, boxed: false) - serializeInt32(port, buffer: buffer, boxed: false) - serializeBytes(peerTag, buffer: buffer, boxed: false) break - case .phoneConnectionWebrtc(let flags, let id, let ip, let ipv6, let port, let username, let password): + case .pageListItemText(let text): if boxed { - buffer.appendInt32(1667228533) + buffer.appendInt32(-1188055347) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeString(ip, buffer: buffer, boxed: false) - serializeString(ipv6, buffer: buffer, boxed: false) - serializeInt32(port, buffer: buffer, boxed: false) - serializeString(username, buffer: buffer, boxed: false) - serializeString(password, buffer: buffer, boxed: false) + text.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .phoneConnection(let flags, let id, let ip, let ipv6, let port, let peerTag): - return ("phoneConnection", [("flags", flags as Any), ("id", id as Any), ("ip", ip as Any), ("ipv6", ipv6 as Any), ("port", port as Any), ("peerTag", peerTag as Any)]) - case .phoneConnectionWebrtc(let flags, let id, let ip, let ipv6, let port, let username, let password): - return ("phoneConnectionWebrtc", [("flags", flags as Any), ("id", id as Any), ("ip", ip as Any), ("ipv6", ipv6 as Any), ("port", port as Any), ("username", username as Any), ("password", password as Any)]) + case .pageListItemBlocks(let blocks): + return ("pageListItemBlocks", [("blocks", blocks as Any)]) + case .pageListItemText(let text): + return ("pageListItemText", [("text", text as Any)]) } } - public static func parse_phoneConnection(_ reader: BufferReader) -> PhoneConnection? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Buffer? - _6 = parseBytes(reader) + public static func parse_pageListItemBlocks(_ reader: BufferReader) -> PageListItem? { + var _1: [Api.PageBlock]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PhoneConnection.phoneConnection(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, peerTag: _6!) + if _c1 { + return Api.PageListItem.pageListItemBlocks(blocks: _1!) } else { return nil } } - public static func parse_phoneConnectionWebrtc(_ reader: BufferReader) -> PhoneConnection? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: String? - _6 = parseString(reader) - var _7: String? - _7 = parseString(reader) + public static func parse_pageListItemText(_ reader: BufferReader) -> PageListItem? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PhoneConnection.phoneConnectionWebrtc(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, username: _6!, password: _7!) + if _c1 { + return Api.PageListItem.pageListItemText(text: _1!) } else { return nil @@ -153,93 +107,69 @@ public extension Api { } } public extension Api { - enum Photo: TypeConstructorDescription { - case photo(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, sizes: [Api.PhotoSize], videoSizes: [Api.VideoSize]?, dcId: Int32) - case photoEmpty(id: Int64) + indirect enum PageListOrderedItem: TypeConstructorDescription { + case pageListOrderedItemBlocks(num: String, blocks: [Api.PageBlock]) + case pageListOrderedItemText(num: String, text: Api.RichText) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes, let videoSizes, let dcId): + case .pageListOrderedItemBlocks(let num, let blocks): if boxed { - buffer.appendInt32(-82216347) + buffer.appendInt32(-1730311882) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeBytes(fileReference, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) + serializeString(num, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sizes.count)) - for item in sizes { + buffer.appendInt32(Int32(blocks.count)) + for item in blocks { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(videoSizes!.count)) - for item in videoSizes! { - item.serialize(buffer, true) - }} - serializeInt32(dcId, buffer: buffer, boxed: false) break - case .photoEmpty(let id): + case .pageListOrderedItemText(let num, let text): if boxed { - buffer.appendInt32(590459437) + buffer.appendInt32(1577484359) } - serializeInt64(id, buffer: buffer, boxed: false) + serializeString(num, buffer: buffer, boxed: false) + text.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes, let videoSizes, let dcId): - return ("photo", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("fileReference", fileReference as Any), ("date", date as Any), ("sizes", sizes as Any), ("videoSizes", videoSizes as Any), ("dcId", dcId as Any)]) - case .photoEmpty(let id): - return ("photoEmpty", [("id", id as Any)]) + case .pageListOrderedItemBlocks(let num, let blocks): + return ("pageListOrderedItemBlocks", [("num", num as Any), ("blocks", blocks as Any)]) + case .pageListOrderedItemText(let num, let text): + return ("pageListOrderedItemText", [("num", num as Any), ("text", text as Any)]) } } - public static func parse_photo(_ reader: BufferReader) -> Photo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Buffer? - _4 = parseBytes(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: [Api.PhotoSize]? + public static func parse_pageListOrderedItemBlocks(_ reader: BufferReader) -> PageListOrderedItem? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.PageBlock]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) } - var _7: [Api.VideoSize]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.VideoSize.self) - } } - var _8: Int32? - _8 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Photo.photo(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, sizes: _6!, videoSizes: _7, dcId: _8!) + if _c1 && _c2 { + return Api.PageListOrderedItem.pageListOrderedItemBlocks(num: _1!, blocks: _2!) } else { return nil } } - public static func parse_photoEmpty(_ reader: BufferReader) -> Photo? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_pageListOrderedItemText(_ reader: BufferReader) -> PageListOrderedItem? { + var _1: String? + _1 = parseString(reader) + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } let _c1 = _1 != nil - if _c1 { - return Api.Photo.photoEmpty(id: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageListOrderedItem.pageListOrderedItemText(num: _1!, text: _2!) } else { return nil @@ -249,183 +179,61 @@ public extension Api { } } public extension Api { - enum PhotoSize: TypeConstructorDescription { - case photoCachedSize(type: String, w: Int32, h: Int32, bytes: Buffer) - case photoPathSize(type: String, bytes: Buffer) - case photoSize(type: String, w: Int32, h: Int32, size: Int32) - case photoSizeEmpty(type: String) - case photoSizeProgressive(type: String, w: Int32, h: Int32, sizes: [Int32]) - case photoStrippedSize(type: String, bytes: Buffer) + enum PageRelatedArticle: TypeConstructorDescription { + case pageRelatedArticle(flags: Int32, url: String, webpageId: Int64, title: String?, description: String?, photoId: Int64?, author: String?, publishedDate: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .photoCachedSize(let type, let w, let h, let bytes): - if boxed { - buffer.appendInt32(35527382) - } - serializeString(type, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - break - case .photoPathSize(let type, let bytes): + case .pageRelatedArticle(let flags, let url, let webpageId, let title, let description, let photoId, let author, let publishedDate): if boxed { - buffer.appendInt32(-668906175) + buffer.appendInt32(-1282352120) } - serializeString(type, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - break - case .photoSize(let type, let w, let h, let size): - if boxed { - buffer.appendInt32(1976012384) - } - serializeString(type, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - break - case .photoSizeEmpty(let type): - if boxed { - buffer.appendInt32(236446268) - } - serializeString(type, buffer: buffer, boxed: false) - break - case .photoSizeProgressive(let type, let w, let h, let sizes): - if boxed { - buffer.appendInt32(-96535659) - } - serializeString(type, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sizes.count)) - for item in sizes { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .photoStrippedSize(let type, let bytes): - if boxed { - buffer.appendInt32(-525288402) - } - serializeString(type, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + serializeInt64(webpageId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(description!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt64(photoId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeString(author!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(publishedDate!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .photoCachedSize(let type, let w, let h, let bytes): - return ("photoCachedSize", [("type", type as Any), ("w", w as Any), ("h", h as Any), ("bytes", bytes as Any)]) - case .photoPathSize(let type, let bytes): - return ("photoPathSize", [("type", type as Any), ("bytes", bytes as Any)]) - case .photoSize(let type, let w, let h, let size): - return ("photoSize", [("type", type as Any), ("w", w as Any), ("h", h as Any), ("size", size as Any)]) - case .photoSizeEmpty(let type): - return ("photoSizeEmpty", [("type", type as Any)]) - case .photoSizeProgressive(let type, let w, let h, let sizes): - return ("photoSizeProgressive", [("type", type as Any), ("w", w as Any), ("h", h as Any), ("sizes", sizes as Any)]) - case .photoStrippedSize(let type, let bytes): - return ("photoStrippedSize", [("type", type as Any), ("bytes", bytes as Any)]) - } - } - - public static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Buffer? - _4 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhotoSize.photoCachedSize(type: _1!, w: _2!, h: _3!, bytes: _4!) - } - else { - return nil - } - } - public static func parse_photoPathSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PhotoSize.photoPathSize(type: _1!, bytes: _2!) - } - else { - return nil - } - } - public static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhotoSize.photoSize(type: _1!, w: _2!, h: _3!, size: _4!) - } - else { - return nil - } - } - public static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.PhotoSize.photoSizeEmpty(type: _1!) - } - else { - return nil - } - } - public static func parse_photoSizeProgressive(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Int32]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } + case .pageRelatedArticle(let flags, let url, let webpageId, let title, let description, let photoId, let author, let publishedDate): + return ("pageRelatedArticle", [("flags", flags as Any), ("url", url as Any), ("webpageId", webpageId as Any), ("title", title as Any), ("description", description as Any), ("photoId", photoId as Any), ("author", author as Any), ("publishedDate", publishedDate as Any)]) + } + } + + public static func parse_pageRelatedArticle(_ reader: BufferReader) -> PageRelatedArticle? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + var _5: String? + if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } + var _6: Int64? + if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt64() } + var _7: String? + if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) } + var _8: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhotoSize.photoSizeProgressive(type: _1!, w: _2!, h: _3!, sizes: _4!) - } - else { - return nil - } - } - public static func parse_photoStrippedSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PhotoSize.photoStrippedSize(type: _1!, bytes: _2!) + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.PageRelatedArticle.pageRelatedArticle(flags: _1!, url: _2!, webpageId: _3!, title: _4, description: _5, photoId: _6, author: _7, publishedDate: _8) } else { return nil @@ -435,61 +243,47 @@ public extension Api { } } public extension Api { - enum Poll: TypeConstructorDescription { - case poll(id: Int64, flags: Int32, question: Api.TextWithEntities, answers: [Api.PollAnswer], closePeriod: Int32?, closeDate: Int32?) + indirect enum PageTableCell: TypeConstructorDescription { + case pageTableCell(flags: Int32, text: Api.RichText?, colspan: Int32?, rowspan: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .poll(let id, let flags, let question, let answers, let closePeriod, let closeDate): + case .pageTableCell(let flags, let text, let colspan, let rowspan): if boxed { - buffer.appendInt32(1484026161) + buffer.appendInt32(878078826) } - serializeInt64(id, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false) - question.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(answers.count)) - for item in answers { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(closePeriod!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 5) != 0 {serializeInt32(closeDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 7) != 0 {text!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(colspan!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(rowspan!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .poll(let id, let flags, let question, let answers, let closePeriod, let closeDate): - return ("poll", [("id", id as Any), ("flags", flags as Any), ("question", question as Any), ("answers", answers as Any), ("closePeriod", closePeriod as Any), ("closeDate", closeDate as Any)]) + case .pageTableCell(let flags, let text, let colspan, let rowspan): + return ("pageTableCell", [("flags", flags as Any), ("text", text as Any), ("colspan", colspan as Any), ("rowspan", rowspan as Any)]) } } - public static func parse_poll(_ reader: BufferReader) -> Poll? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.TextWithEntities? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - var _4: [Api.PollAnswer]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PollAnswer.self) - } - var _5: Int32? - if Int(_2!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } - var _6: Int32? - if Int(_2!) & Int(1 << 5) != 0 {_6 = reader.readInt32() } + public static func parse_pageTableCell(_ reader: BufferReader) -> PageTableCell? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.RichText? + if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } } + var _3: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } + var _4: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_2!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_2!) & Int(1 << 5) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Poll.poll(id: _1!, flags: _2!, question: _3!, answers: _4!, closePeriod: _5, closeDate: _6) + let _c2 = (Int(_1!) & Int(1 << 7) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PageTableCell.pageTableCell(flags: _1!, text: _2, colspan: _3, rowspan: _4) } else { return nil @@ -499,39 +293,39 @@ public extension Api { } } public extension Api { - enum PollAnswer: TypeConstructorDescription { - case pollAnswer(text: Api.TextWithEntities, option: Buffer) + enum PageTableRow: TypeConstructorDescription { + case pageTableRow(cells: [Api.PageTableCell]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pollAnswer(let text, let option): + case .pageTableRow(let cells): if boxed { - buffer.appendInt32(-15277366) + buffer.appendInt32(-524237339) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(cells.count)) + for item in cells { + item.serialize(buffer, true) } - text.serialize(buffer, true) - serializeBytes(option, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .pollAnswer(let text, let option): - return ("pollAnswer", [("text", text as Any), ("option", option as Any)]) + case .pageTableRow(let cells): + return ("pageTableRow", [("cells", cells as Any)]) } } - public static func parse_pollAnswer(_ reader: BufferReader) -> PollAnswer? { - var _1: Api.TextWithEntities? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + public static func parse_pageTableRow(_ reader: BufferReader) -> PageTableRow? { + var _1: [Api.PageTableCell]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageTableCell.self) } - var _2: Buffer? - _2 = parseBytes(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PollAnswer.pollAnswer(text: _1!, option: _2!) + if _c1 { + return Api.PageTableRow.pageTableRow(cells: _1!) } else { return nil @@ -541,115 +335,97 @@ public extension Api { } } public extension Api { - enum PollAnswerVoters: TypeConstructorDescription { - case pollAnswerVoters(flags: Int32, option: Buffer, voters: Int32) + enum PasswordKdfAlgo: TypeConstructorDescription { + case passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1: Buffer, salt2: Buffer, g: Int32, p: Buffer) + case passwordKdfAlgoUnknown public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pollAnswerVoters(let flags, let option, let voters): + case .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(let salt1, let salt2, let g, let p): if boxed { - buffer.appendInt32(997055186) + buffer.appendInt32(982592842) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeBytes(option, buffer: buffer, boxed: false) - serializeInt32(voters, buffer: buffer, boxed: false) + serializeBytes(salt1, buffer: buffer, boxed: false) + serializeBytes(salt2, buffer: buffer, boxed: false) + serializeInt32(g, buffer: buffer, boxed: false) + serializeBytes(p, buffer: buffer, boxed: false) + break + case .passwordKdfAlgoUnknown: + if boxed { + buffer.appendInt32(-732254058) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .pollAnswerVoters(let flags, let option, let voters): - return ("pollAnswerVoters", [("flags", flags as Any), ("option", option as Any), ("voters", voters as Any)]) + case .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(let salt1, let salt2, let g, let p): + return ("passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow", [("salt1", salt1 as Any), ("salt2", salt2 as Any), ("g", g as Any), ("p", p as Any)]) + case .passwordKdfAlgoUnknown: + return ("passwordKdfAlgoUnknown", []) } } - public static func parse_pollAnswerVoters(_ reader: BufferReader) -> PollAnswerVoters? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(_ reader: BufferReader) -> PasswordKdfAlgo? { + var _1: Buffer? + _1 = parseBytes(reader) var _2: Buffer? _2 = parseBytes(reader) var _3: Int32? _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PollAnswerVoters.pollAnswerVoters(flags: _1!, option: _2!, voters: _3!) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PasswordKdfAlgo.passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1: _1!, salt2: _2!, g: _3!, p: _4!) } else { return nil } } + public static func parse_passwordKdfAlgoUnknown(_ reader: BufferReader) -> PasswordKdfAlgo? { + return Api.PasswordKdfAlgo.passwordKdfAlgoUnknown + } } } public extension Api { - enum PollResults: TypeConstructorDescription { - case pollResults(flags: Int32, results: [Api.PollAnswerVoters]?, totalVoters: Int32?, recentVoters: [Api.Peer]?, solution: String?, solutionEntities: [Api.MessageEntity]?) + enum PaymentCharge: TypeConstructorDescription { + case paymentCharge(id: String, providerChargeId: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pollResults(let flags, let results, let totalVoters, let recentVoters, let solution, let solutionEntities): + case .paymentCharge(let id, let providerChargeId): if boxed { - buffer.appendInt32(2061444128) + buffer.appendInt32(-368917890) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(results!.count)) - for item in results! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(totalVoters!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentVoters!.count)) - for item in recentVoters! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 4) != 0 {serializeString(solution!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(solutionEntities!.count)) - for item in solutionEntities! { - item.serialize(buffer, true) - }} + serializeString(id, buffer: buffer, boxed: false) + serializeString(providerChargeId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .pollResults(let flags, let results, let totalVoters, let recentVoters, let solution, let solutionEntities): - return ("pollResults", [("flags", flags as Any), ("results", results as Any), ("totalVoters", totalVoters as Any), ("recentVoters", recentVoters as Any), ("solution", solution as Any), ("solutionEntities", solutionEntities as Any)]) + case .paymentCharge(let id, let providerChargeId): + return ("paymentCharge", [("id", id as Any), ("providerChargeId", providerChargeId as Any)]) } } - public static func parse_pollResults(_ reader: BufferReader) -> PollResults? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.PollAnswerVoters]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PollAnswerVoters.self) - } } - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } - var _4: [Api.Peer]? - if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } } - var _5: String? - if Int(_1!) & Int(1 << 4) != 0 {_5 = parseString(reader) } - var _6: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } + public static func parse_paymentCharge(_ reader: BufferReader) -> PaymentCharge? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PollResults.pollResults(flags: _1!, results: _2, totalVoters: _3, recentVoters: _4, solution: _5, solutionEntities: _6) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PaymentCharge.paymentCharge(id: _1!, providerChargeId: _2!) } else { return nil @@ -659,37 +435,37 @@ public extension Api { } } public extension Api { - enum PopularContact: TypeConstructorDescription { - case popularContact(clientId: Int64, importers: Int32) + enum PaymentFormMethod: TypeConstructorDescription { + case paymentFormMethod(url: String, title: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .popularContact(let clientId, let importers): + case .paymentFormMethod(let url, let title): if boxed { - buffer.appendInt32(1558266229) + buffer.appendInt32(-1996951013) } - serializeInt64(clientId, buffer: buffer, boxed: false) - serializeInt32(importers, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .popularContact(let clientId, let importers): - return ("popularContact", [("clientId", clientId as Any), ("importers", importers as Any)]) + case .paymentFormMethod(let url, let title): + return ("paymentFormMethod", [("url", url as Any), ("title", title as Any)]) } } - public static func parse_popularContact(_ reader: BufferReader) -> PopularContact? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() + public static func parse_paymentFormMethod(_ reader: BufferReader) -> PaymentFormMethod? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.PopularContact.popularContact(clientId: _1!, importers: _2!) + return Api.PaymentFormMethod.paymentFormMethod(url: _1!, title: _2!) } else { return nil @@ -699,53 +475,91 @@ public extension Api { } } public extension Api { - enum PostAddress: TypeConstructorDescription { - case postAddress(streetLine1: String, streetLine2: String, city: String, state: String, countryIso2: String, postCode: String) + enum PaymentRequestedInfo: TypeConstructorDescription { + case paymentRequestedInfo(flags: Int32, name: String?, phone: String?, email: String?, shippingAddress: Api.PostAddress?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .postAddress(let streetLine1, let streetLine2, let city, let state, let countryIso2, let postCode): + case .paymentRequestedInfo(let flags, let name, let phone, let email, let shippingAddress): if boxed { - buffer.appendInt32(512535275) + buffer.appendInt32(-1868808300) } - serializeString(streetLine1, buffer: buffer, boxed: false) - serializeString(streetLine2, buffer: buffer, boxed: false) - serializeString(city, buffer: buffer, boxed: false) - serializeString(state, buffer: buffer, boxed: false) - serializeString(countryIso2, buffer: buffer, boxed: false) - serializeString(postCode, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(name!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(phone!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(email!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {shippingAddress!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .postAddress(let streetLine1, let streetLine2, let city, let state, let countryIso2, let postCode): - return ("postAddress", [("streetLine1", streetLine1 as Any), ("streetLine2", streetLine2 as Any), ("city", city as Any), ("state", state as Any), ("countryIso2", countryIso2 as Any), ("postCode", postCode as Any)]) + case .paymentRequestedInfo(let flags, let name, let phone, let email, let shippingAddress): + return ("paymentRequestedInfo", [("flags", flags as Any), ("name", name as Any), ("phone", phone as Any), ("email", email as Any), ("shippingAddress", shippingAddress as Any)]) } } - public static func parse_postAddress(_ reader: BufferReader) -> PostAddress? { + public static func parse_paymentRequestedInfo(_ reader: BufferReader) -> PaymentRequestedInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + var _3: String? + if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } + var _4: String? + if Int(_1!) & Int(1 << 2) != 0 {_4 = parseString(reader) } + var _5: Api.PostAddress? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.PostAddress + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PaymentRequestedInfo.paymentRequestedInfo(flags: _1!, name: _2, phone: _3, email: _4, shippingAddress: _5) + } + else { + return nil + } + } + + } +} +public extension Api { + enum PaymentSavedCredentials: TypeConstructorDescription { + case paymentSavedCredentialsCard(id: String, title: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentSavedCredentialsCard(let id, let title): + if boxed { + buffer.appendInt32(-842892769) + } + serializeString(id, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .paymentSavedCredentialsCard(let id, let title): + return ("paymentSavedCredentialsCard", [("id", id as Any), ("title", title as Any)]) + } + } + + public static func parse_paymentSavedCredentialsCard(_ reader: BufferReader) -> PaymentSavedCredentials? { var _1: String? _1 = parseString(reader) var _2: String? _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PostAddress.postAddress(streetLine1: _1!, streetLine2: _2!, city: _3!, state: _4!, countryIso2: _5!, postCode: _6!) + if _c1 && _c2 { + return Api.PaymentSavedCredentials.paymentSavedCredentialsCard(id: _1!, title: _2!) } else { return nil @@ -755,77 +569,115 @@ public extension Api { } } public extension Api { - enum PostInteractionCounters: TypeConstructorDescription { - case postInteractionCountersMessage(msgId: Int32, views: Int32, forwards: Int32, reactions: Int32) - case postInteractionCountersStory(storyId: Int32, views: Int32, forwards: Int32, reactions: Int32) + enum Peer: TypeConstructorDescription { + case peerChannel(channelId: Int64) + case peerChat(chatId: Int64) + case peerUser(userId: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .postInteractionCountersMessage(let msgId, let views, let forwards, let reactions): + case .peerChannel(let channelId): + if boxed { + buffer.appendInt32(-1566230754) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + break + case .peerChat(let chatId): if boxed { - buffer.appendInt32(-419066241) + buffer.appendInt32(918946202) } - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(views, buffer: buffer, boxed: false) - serializeInt32(forwards, buffer: buffer, boxed: false) - serializeInt32(reactions, buffer: buffer, boxed: false) + serializeInt64(chatId, buffer: buffer, boxed: false) break - case .postInteractionCountersStory(let storyId, let views, let forwards, let reactions): + case .peerUser(let userId): if boxed { - buffer.appendInt32(-1974989273) + buffer.appendInt32(1498486562) } - serializeInt32(storyId, buffer: buffer, boxed: false) - serializeInt32(views, buffer: buffer, boxed: false) - serializeInt32(forwards, buffer: buffer, boxed: false) - serializeInt32(reactions, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .postInteractionCountersMessage(let msgId, let views, let forwards, let reactions): - return ("postInteractionCountersMessage", [("msgId", msgId as Any), ("views", views as Any), ("forwards", forwards as Any), ("reactions", reactions as Any)]) - case .postInteractionCountersStory(let storyId, let views, let forwards, let reactions): - return ("postInteractionCountersStory", [("storyId", storyId as Any), ("views", views as Any), ("forwards", forwards as Any), ("reactions", reactions as Any)]) + case .peerChannel(let channelId): + return ("peerChannel", [("channelId", channelId as Any)]) + case .peerChat(let chatId): + return ("peerChat", [("chatId", chatId as Any)]) + case .peerUser(let userId): + return ("peerUser", [("userId", userId as Any)]) } } - public static func parse_postInteractionCountersMessage(_ reader: BufferReader) -> PostInteractionCounters? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() + public static func parse_peerChannel(_ reader: BufferReader) -> Peer? { + var _1: Int64? + _1 = reader.readInt64() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PostInteractionCounters.postInteractionCountersMessage(msgId: _1!, views: _2!, forwards: _3!, reactions: _4!) + if _c1 { + return Api.Peer.peerChannel(channelId: _1!) } else { return nil } } - public static func parse_postInteractionCountersStory(_ reader: BufferReader) -> PostInteractionCounters? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_peerChat(_ reader: BufferReader) -> Peer? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Peer.peerChat(chatId: _1!) + } + else { + return nil + } + } + public static func parse_peerUser(_ reader: BufferReader) -> Peer? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Peer.peerUser(userId: _1!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum PeerBlocked: TypeConstructorDescription { + case peerBlocked(peerId: Api.Peer, date: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerBlocked(let peerId, let date): + if boxed { + buffer.appendInt32(-386039788) + } + peerId.serialize(buffer, true) + serializeInt32(date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .peerBlocked(let peerId, let date): + return ("peerBlocked", [("peerId", peerId as Any), ("date", date as Any)]) + } + } + + public static func parse_peerBlocked(_ reader: BufferReader) -> PeerBlocked? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } var _2: Int32? _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PostInteractionCounters.postInteractionCountersStory(storyId: _1!, views: _2!, forwards: _3!, reactions: _4!) + if _c1 && _c2 { + return Api.PeerBlocked.peerBlocked(peerId: _1!, date: _2!) } else { return nil @@ -835,57 +687,107 @@ public extension Api { } } public extension Api { - enum PremiumGiftCodeOption: TypeConstructorDescription { - case premiumGiftCodeOption(flags: Int32, users: Int32, months: Int32, storeProduct: String?, storeQuantity: Int32?, currency: String, amount: Int64) + enum PeerColor: TypeConstructorDescription { + case peerColor(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .premiumGiftCodeOption(let flags, let users, let months, let storeProduct, let storeQuantity, let currency, let amount): + case .peerColor(let flags, let color, let backgroundEmojiId): if boxed { - buffer.appendInt32(629052971) + buffer.appendInt32(-1253352753) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(users, buffer: buffer, boxed: false) - serializeInt32(months, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(storeQuantity!, buffer: buffer, boxed: false)} - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(amount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .premiumGiftCodeOption(let flags, let users, let months, let storeProduct, let storeQuantity, let currency, let amount): - return ("premiumGiftCodeOption", [("flags", flags as Any), ("users", users as Any), ("months", months as Any), ("storeProduct", storeProduct as Any), ("storeQuantity", storeQuantity as Any), ("currency", currency as Any), ("amount", amount as Any)]) + case .peerColor(let flags, let color, let backgroundEmojiId): + return ("peerColor", [("flags", flags as Any), ("color", color as Any), ("backgroundEmojiId", backgroundEmojiId as Any)]) } } - public static func parse_premiumGiftCodeOption(_ reader: BufferReader) -> PremiumGiftCodeOption? { + public static func parse_peerColor(_ reader: BufferReader) -> PeerColor? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _3: Int64? + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt64() } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.PeerColor.peerColor(flags: _1!, color: _2, backgroundEmojiId: _3) + } + else { + return nil + } + } + + } +} +public extension Api { + enum PeerLocated: TypeConstructorDescription { + case peerLocated(peer: Api.Peer, expires: Int32, distance: Int32) + case peerSelfLocated(expires: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerLocated(let peer, let expires, let distance): + if boxed { + buffer.appendInt32(-901375139) + } + peer.serialize(buffer, true) + serializeInt32(expires, buffer: buffer, boxed: false) + serializeInt32(distance, buffer: buffer, boxed: false) + break + case .peerSelfLocated(let expires): + if boxed { + buffer.appendInt32(-118740917) + } + serializeInt32(expires, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .peerLocated(let peer, let expires, let distance): + return ("peerLocated", [("peer", peer as Any), ("expires", expires as Any), ("distance", distance as Any)]) + case .peerSelfLocated(let expires): + return ("peerSelfLocated", [("expires", expires as Any)]) + } + } + + public static func parse_peerLocated(_ reader: BufferReader) -> PeerLocated? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } - var _6: String? - _6 = parseString(reader) - var _7: Int64? - _7 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PremiumGiftCodeOption.premiumGiftCodeOption(flags: _1!, users: _2!, months: _3!, storeProduct: _4, storeQuantity: _5, currency: _6!, amount: _7!) + if _c1 && _c2 && _c3 { + return Api.PeerLocated.peerLocated(peer: _1!, expires: _2!, distance: _3!) + } + else { + return nil + } + } + public static func parse_peerSelfLocated(_ reader: BufferReader) -> PeerLocated? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.PeerLocated.peerSelfLocated(expires: _1!) } else { return nil @@ -895,53 +797,97 @@ public extension Api { } } public extension Api { - enum PremiumGiftOption: TypeConstructorDescription { - case premiumGiftOption(flags: Int32, months: Int32, currency: String, amount: Int64, botUrl: String, storeProduct: String?) + enum PeerNotifySettings: TypeConstructorDescription { + case peerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, iosSound: Api.NotificationSound?, androidSound: Api.NotificationSound?, otherSound: Api.NotificationSound?, storiesMuted: Api.Bool?, storiesHideSender: Api.Bool?, storiesIosSound: Api.NotificationSound?, storiesAndroidSound: Api.NotificationSound?, storiesOtherSound: Api.NotificationSound?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .premiumGiftOption(let flags, let months, let currency, let amount, let botUrl, let storeProduct): + case .peerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let iosSound, let androidSound, let otherSound, let storiesMuted, let storiesHideSender, let storiesIosSound, let storiesAndroidSound, let storiesOtherSound): if boxed { - buffer.appendInt32(1958953753) + buffer.appendInt32(-1721619444) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(months, buffer: buffer, boxed: false) - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(amount, buffer: buffer, boxed: false) - serializeString(botUrl, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {showPreviews!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {silent!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(muteUntil!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {iosSound!.serialize(buffer, true)} + if Int(flags) & Int(1 << 4) != 0 {androidSound!.serialize(buffer, true)} + if Int(flags) & Int(1 << 5) != 0 {otherSound!.serialize(buffer, true)} + if Int(flags) & Int(1 << 6) != 0 {storiesMuted!.serialize(buffer, true)} + if Int(flags) & Int(1 << 7) != 0 {storiesHideSender!.serialize(buffer, true)} + if Int(flags) & Int(1 << 8) != 0 {storiesIosSound!.serialize(buffer, true)} + if Int(flags) & Int(1 << 9) != 0 {storiesAndroidSound!.serialize(buffer, true)} + if Int(flags) & Int(1 << 10) != 0 {storiesOtherSound!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .premiumGiftOption(let flags, let months, let currency, let amount, let botUrl, let storeProduct): - return ("premiumGiftOption", [("flags", flags as Any), ("months", months as Any), ("currency", currency as Any), ("amount", amount as Any), ("botUrl", botUrl as Any), ("storeProduct", storeProduct as Any)]) + case .peerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let iosSound, let androidSound, let otherSound, let storiesMuted, let storiesHideSender, let storiesIosSound, let storiesAndroidSound, let storiesOtherSound): + return ("peerNotifySettings", [("flags", flags as Any), ("showPreviews", showPreviews as Any), ("silent", silent as Any), ("muteUntil", muteUntil as Any), ("iosSound", iosSound as Any), ("androidSound", androidSound as Any), ("otherSound", otherSound as Any), ("storiesMuted", storiesMuted as Any), ("storiesHideSender", storiesHideSender as Any), ("storiesIosSound", storiesIosSound as Any), ("storiesAndroidSound", storiesAndroidSound as Any), ("storiesOtherSound", storiesOtherSound as Any)]) } } - public static func parse_premiumGiftOption(_ reader: BufferReader) -> PremiumGiftOption? { + public static func parse_peerNotifySettings(_ reader: BufferReader) -> PeerNotifySettings? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - _5 = parseString(reader) - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } + var _2: Api.Bool? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _3: Api.Bool? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _4: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } + var _5: Api.NotificationSound? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } } + var _6: Api.NotificationSound? + if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } } + var _7: Api.NotificationSound? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } } + var _8: Api.Bool? + if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _9: Api.Bool? + if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _10: Api.NotificationSound? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } } + var _11: Api.NotificationSound? + if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } } + var _12: Api.NotificationSound? + if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PremiumGiftOption.premiumGiftOption(flags: _1!, months: _2!, currency: _3!, amount: _4!, botUrl: _5!, storeProduct: _6) + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 7) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 8) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 9) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 10) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.PeerNotifySettings.peerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, iosSound: _5, androidSound: _6, otherSound: _7, storiesMuted: _8, storiesHideSender: _9, storiesIosSound: _10, storiesAndroidSound: _11, storiesOtherSound: _12) } else { return nil @@ -951,57 +897,53 @@ public extension Api { } } public extension Api { - enum PremiumSubscriptionOption: TypeConstructorDescription { - case premiumSubscriptionOption(flags: Int32, transaction: String?, months: Int32, currency: String, amount: Int64, botUrl: String, storeProduct: String?) + enum PeerSettings: TypeConstructorDescription { + case peerSettings(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?, businessBotId: Int64?, businessBotManageUrl: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .premiumSubscriptionOption(let flags, let transaction, let months, let currency, let amount, let botUrl, let storeProduct): + case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl): if boxed { - buffer.appendInt32(1596792306) + buffer.appendInt32(-1395233698) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {serializeString(transaction!, buffer: buffer, boxed: false)} - serializeInt32(months, buffer: buffer, boxed: false) - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(amount, buffer: buffer, boxed: false) - serializeString(botUrl, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {serializeInt32(geoDistance!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 9) != 0 {serializeString(requestChatTitle!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 9) != 0 {serializeInt32(requestChatDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 13) != 0 {serializeInt64(businessBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 13) != 0 {serializeString(businessBotManageUrl!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .premiumSubscriptionOption(let flags, let transaction, let months, let currency, let amount, let botUrl, let storeProduct): - return ("premiumSubscriptionOption", [("flags", flags as Any), ("transaction", transaction as Any), ("months", months as Any), ("currency", currency as Any), ("amount", amount as Any), ("botUrl", botUrl as Any), ("storeProduct", storeProduct as Any)]) + case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl): + return ("peerSettings", [("flags", flags as Any), ("geoDistance", geoDistance as Any), ("requestChatTitle", requestChatTitle as Any), ("requestChatDate", requestChatDate as Any), ("businessBotId", businessBotId as Any), ("businessBotManageUrl", businessBotManageUrl as Any)]) } } - public static func parse_premiumSubscriptionOption(_ reader: BufferReader) -> PremiumSubscriptionOption? { + public static func parse_peerSettings(_ reader: BufferReader) -> PeerSettings? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 3) != 0 {_2 = parseString(reader) } - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) + var _2: Int32? + if Int(_1!) & Int(1 << 6) != 0 {_2 = reader.readInt32() } + var _3: String? + if Int(_1!) & Int(1 << 9) != 0 {_3 = parseString(reader) } + var _4: Int32? + if Int(_1!) & Int(1 << 9) != 0 {_4 = reader.readInt32() } var _5: Int64? - _5 = reader.readInt64() + if Int(_1!) & Int(1 << 13) != 0 {_5 = reader.readInt64() } var _6: String? - _6 = parseString(reader) - var _7: String? - if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) } + if Int(_1!) & Int(1 << 13) != 0 {_6 = parseString(reader) } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PremiumSubscriptionOption.premiumSubscriptionOption(flags: _1!, transaction: _2, months: _3!, currency: _4!, amount: _5!, botUrl: _6!, storeProduct: _7) + let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 9) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 13) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 13) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6) } else { return nil @@ -1011,45 +953,53 @@ public extension Api { } } public extension Api { - enum PrepaidGiveaway: TypeConstructorDescription { - case prepaidGiveaway(id: Int64, months: Int32, quantity: Int32, date: Int32) + enum PeerStories: TypeConstructorDescription { + case peerStories(flags: Int32, peer: Api.Peer, maxReadId: Int32?, stories: [Api.StoryItem]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .prepaidGiveaway(let id, let months, let quantity, let date): + case .peerStories(let flags, let peer, let maxReadId, let stories): if boxed { - buffer.appendInt32(-1303143084) + buffer.appendInt32(-1707742823) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(maxReadId!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stories.count)) + for item in stories { + item.serialize(buffer, true) } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt32(months, buffer: buffer, boxed: false) - serializeInt32(quantity, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .prepaidGiveaway(let id, let months, let quantity, let date): - return ("prepaidGiveaway", [("id", id as Any), ("months", months as Any), ("quantity", quantity as Any), ("date", date as Any)]) + case .peerStories(let flags, let peer, let maxReadId, let stories): + return ("peerStories", [("flags", flags as Any), ("peer", peer as Any), ("maxReadId", maxReadId as Any), ("stories", stories as Any)]) } } - public static func parse_prepaidGiveaway(_ reader: BufferReader) -> PrepaidGiveaway? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() + public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: [Api.StoryItem]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self) + } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.PrepaidGiveaway.prepaidGiveaway(id: _1!, months: _2!, quantity: _3!, date: _4!) + return Api.PeerStories.peerStories(flags: _1!, peer: _2!, maxReadId: _3, stories: _4!) } else { return nil @@ -1059,84 +1009,329 @@ public extension Api { } } public extension Api { - enum PrivacyKey: TypeConstructorDescription { - case privacyKeyAbout - case privacyKeyAddedByPhone - case privacyKeyBirthday - case privacyKeyChatInvite - case privacyKeyForwards - case privacyKeyPhoneCall - case privacyKeyPhoneNumber - case privacyKeyPhoneP2P - case privacyKeyProfilePhoto - case privacyKeyStatusTimestamp - case privacyKeyVoiceMessages + enum PhoneCall: TypeConstructorDescription { + case phoneCall(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAOrB: Buffer, keyFingerprint: Int64, protocol: Api.PhoneCallProtocol, connections: [Api.PhoneConnection], startDate: Int32, customParameters: Api.DataJSON?) + case phoneCallAccepted(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gB: Buffer, protocol: Api.PhoneCallProtocol) + case phoneCallDiscarded(flags: Int32, id: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?) + case phoneCallEmpty(id: Int64) + case phoneCallRequested(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAHash: Buffer, protocol: Api.PhoneCallProtocol) + case phoneCallWaiting(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, protocol: Api.PhoneCallProtocol, receiveDate: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .privacyKeyAbout: + case .phoneCall(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAOrB, let keyFingerprint, let `protocol`, let connections, let startDate, let customParameters): if boxed { - buffer.appendInt32(-1534675103) + buffer.appendInt32(810769141) } - - break - case .privacyKeyAddedByPhone: - if boxed { - buffer.appendInt32(1124062251) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(adminId, buffer: buffer, boxed: false) + serializeInt64(participantId, buffer: buffer, boxed: false) + serializeBytes(gAOrB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + `protocol`.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(connections.count)) + for item in connections { + item.serialize(buffer, true) } - + serializeInt32(startDate, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 7) != 0 {customParameters!.serialize(buffer, true)} break - case .privacyKeyBirthday: + case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`): if boxed { - buffer.appendInt32(536913176) + buffer.appendInt32(912311057) } - + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(adminId, buffer: buffer, boxed: false) + serializeInt64(participantId, buffer: buffer, boxed: false) + serializeBytes(gB, buffer: buffer, boxed: false) + `protocol`.serialize(buffer, true) break - case .privacyKeyChatInvite: + case .phoneCallDiscarded(let flags, let id, let reason, let duration): if boxed { - buffer.appendInt32(1343122938) + buffer.appendInt32(1355435489) } - + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {reason!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} break - case .privacyKeyForwards: + case .phoneCallEmpty(let id): if boxed { - buffer.appendInt32(1777096355) + buffer.appendInt32(1399245077) } - + serializeInt64(id, buffer: buffer, boxed: false) break - case .privacyKeyPhoneCall: + case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`): if boxed { - buffer.appendInt32(1030105979) + buffer.appendInt32(347139340) } - + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(adminId, buffer: buffer, boxed: false) + serializeInt64(participantId, buffer: buffer, boxed: false) + serializeBytes(gAHash, buffer: buffer, boxed: false) + `protocol`.serialize(buffer, true) break - case .privacyKeyPhoneNumber: + case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate): if boxed { - buffer.appendInt32(-778378131) + buffer.appendInt32(-987599081) } - + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(adminId, buffer: buffer, boxed: false) + serializeInt64(participantId, buffer: buffer, boxed: false) + `protocol`.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(receiveDate!, buffer: buffer, boxed: false)} break - case .privacyKeyPhoneP2P: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .phoneCall(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAOrB, let keyFingerprint, let `protocol`, let connections, let startDate, let customParameters): + return ("phoneCall", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gAOrB", gAOrB as Any), ("keyFingerprint", keyFingerprint as Any), ("`protocol`", `protocol` as Any), ("connections", connections as Any), ("startDate", startDate as Any), ("customParameters", customParameters as Any)]) + case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`): + return ("phoneCallAccepted", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gB", gB as Any), ("`protocol`", `protocol` as Any)]) + case .phoneCallDiscarded(let flags, let id, let reason, let duration): + return ("phoneCallDiscarded", [("flags", flags as Any), ("id", id as Any), ("reason", reason as Any), ("duration", duration as Any)]) + case .phoneCallEmpty(let id): + return ("phoneCallEmpty", [("id", id as Any)]) + case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`): + return ("phoneCallRequested", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gAHash", gAHash as Any), ("`protocol`", `protocol` as Any)]) + case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate): + return ("phoneCallWaiting", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("`protocol`", `protocol` as Any), ("receiveDate", receiveDate as Any)]) + } + } + + public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + _5 = reader.readInt64() + var _6: Int64? + _6 = reader.readInt64() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Int64? + _8 = reader.readInt64() + var _9: Api.PhoneCallProtocol? + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + } + var _10: [Api.PhoneConnection]? + if let _ = reader.readInt32() { + _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhoneConnection.self) + } + var _11: Int32? + _11 = reader.readInt32() + var _12: Api.DataJSON? + if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.DataJSON + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.PhoneCall.phoneCall(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAOrB: _7!, keyFingerprint: _8!, protocol: _9!, connections: _10!, startDate: _11!, customParameters: _12) + } + else { + return nil + } + } + public static func parse_phoneCallAccepted(_ reader: BufferReader) -> PhoneCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + _5 = reader.readInt64() + var _6: Int64? + _6 = reader.readInt64() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Api.PhoneCallProtocol? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.PhoneCall.phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!) + } + else { + return nil + } + } + public static func parse_phoneCallDiscarded(_ reader: BufferReader) -> PhoneCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.PhoneCallDiscardReason? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.PhoneCallDiscardReason + } } + var _4: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PhoneCall.phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4) + } + else { + return nil + } + } + public static func parse_phoneCallEmpty(_ reader: BufferReader) -> PhoneCall? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.PhoneCall.phoneCallEmpty(id: _1!) + } + else { + return nil + } + } + public static func parse_phoneCallRequested(_ reader: BufferReader) -> PhoneCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + _5 = reader.readInt64() + var _6: Int64? + _6 = reader.readInt64() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Api.PhoneCallProtocol? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.PhoneCall.phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!) + } + else { + return nil + } + } + public static func parse_phoneCallWaiting(_ reader: BufferReader) -> PhoneCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + _5 = reader.readInt64() + var _6: Int64? + _6 = reader.readInt64() + var _7: Api.PhoneCallProtocol? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + } + var _8: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_8 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.PhoneCall.phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8) + } + else { + return nil + } + } + + } +} +public extension Api { + enum PhoneCallDiscardReason: TypeConstructorDescription { + case phoneCallDiscardReasonBusy + case phoneCallDiscardReasonDisconnect + case phoneCallDiscardReasonHangup + case phoneCallDiscardReasonMissed + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .phoneCallDiscardReasonBusy: if boxed { - buffer.appendInt32(961092808) + buffer.appendInt32(-84416311) } break - case .privacyKeyProfilePhoto: + case .phoneCallDiscardReasonDisconnect: if boxed { - buffer.appendInt32(-1777000467) + buffer.appendInt32(-527056480) } break - case .privacyKeyStatusTimestamp: + case .phoneCallDiscardReasonHangup: if boxed { - buffer.appendInt32(-1137792208) + buffer.appendInt32(1471006352) } break - case .privacyKeyVoiceMessages: + case .phoneCallDiscardReasonMissed: if boxed { - buffer.appendInt32(110621716) + buffer.appendInt32(-2048646399) } break @@ -1145,63 +1340,28 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .privacyKeyAbout: - return ("privacyKeyAbout", []) - case .privacyKeyAddedByPhone: - return ("privacyKeyAddedByPhone", []) - case .privacyKeyBirthday: - return ("privacyKeyBirthday", []) - case .privacyKeyChatInvite: - return ("privacyKeyChatInvite", []) - case .privacyKeyForwards: - return ("privacyKeyForwards", []) - case .privacyKeyPhoneCall: - return ("privacyKeyPhoneCall", []) - case .privacyKeyPhoneNumber: - return ("privacyKeyPhoneNumber", []) - case .privacyKeyPhoneP2P: - return ("privacyKeyPhoneP2P", []) - case .privacyKeyProfilePhoto: - return ("privacyKeyProfilePhoto", []) - case .privacyKeyStatusTimestamp: - return ("privacyKeyStatusTimestamp", []) - case .privacyKeyVoiceMessages: - return ("privacyKeyVoiceMessages", []) - } - } - - public static func parse_privacyKeyAbout(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyAbout - } - public static func parse_privacyKeyAddedByPhone(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyAddedByPhone - } - public static func parse_privacyKeyBirthday(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyBirthday - } - public static func parse_privacyKeyChatInvite(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyChatInvite - } - public static func parse_privacyKeyForwards(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyForwards - } - public static func parse_privacyKeyPhoneCall(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyPhoneCall - } - public static func parse_privacyKeyPhoneNumber(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyPhoneNumber - } - public static func parse_privacyKeyPhoneP2P(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyPhoneP2P + case .phoneCallDiscardReasonBusy: + return ("phoneCallDiscardReasonBusy", []) + case .phoneCallDiscardReasonDisconnect: + return ("phoneCallDiscardReasonDisconnect", []) + case .phoneCallDiscardReasonHangup: + return ("phoneCallDiscardReasonHangup", []) + case .phoneCallDiscardReasonMissed: + return ("phoneCallDiscardReasonMissed", []) + } + } + + public static func parse_phoneCallDiscardReasonBusy(_ reader: BufferReader) -> PhoneCallDiscardReason? { + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonBusy } - public static func parse_privacyKeyProfilePhoto(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyProfilePhoto + public static func parse_phoneCallDiscardReasonDisconnect(_ reader: BufferReader) -> PhoneCallDiscardReason? { + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonDisconnect } - public static func parse_privacyKeyStatusTimestamp(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyStatusTimestamp + public static func parse_phoneCallDiscardReasonHangup(_ reader: BufferReader) -> PhoneCallDiscardReason? { + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonHangup } - public static func parse_privacyKeyVoiceMessages(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyVoiceMessages + public static func parse_phoneCallDiscardReasonMissed(_ reader: BufferReader) -> PhoneCallDiscardReason? { + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMissed } } diff --git a/submodules/TelegramApi/Sources/Api19.swift b/submodules/TelegramApi/Sources/Api19.swift index 2b1d7e1f79b..86d042f173f 100644 --- a/submodules/TelegramApi/Sources/Api19.swift +++ b/submodules/TelegramApi/Sources/Api19.swift @@ -1,92 +1,20 @@ public extension Api { - enum PrivacyRule: TypeConstructorDescription { - case privacyValueAllowAll - case privacyValueAllowChatParticipants(chats: [Int64]) - case privacyValueAllowCloseFriends - case privacyValueAllowContacts - case privacyValueAllowPremium - case privacyValueAllowUsers(users: [Int64]) - case privacyValueDisallowAll - case privacyValueDisallowChatParticipants(chats: [Int64]) - case privacyValueDisallowContacts - case privacyValueDisallowUsers(users: [Int64]) + enum PhoneCallProtocol: TypeConstructorDescription { + case phoneCallProtocol(flags: Int32, minLayer: Int32, maxLayer: Int32, libraryVersions: [String]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .privacyValueAllowAll: + case .phoneCallProtocol(let flags, let minLayer, let maxLayer, let libraryVersions): if boxed { - buffer.appendInt32(1698855810) - } - - break - case .privacyValueAllowChatParticipants(let chats): - if boxed { - buffer.appendInt32(1796427406) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .privacyValueAllowCloseFriends: - if boxed { - buffer.appendInt32(-135735141) - } - - break - case .privacyValueAllowContacts: - if boxed { - buffer.appendInt32(-123988) - } - - break - case .privacyValueAllowPremium: - if boxed { - buffer.appendInt32(-320241333) - } - - break - case .privacyValueAllowUsers(let users): - if boxed { - buffer.appendInt32(-1198497870) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .privacyValueDisallowAll: - if boxed { - buffer.appendInt32(-1955338397) - } - - break - case .privacyValueDisallowChatParticipants(let chats): - if boxed { - buffer.appendInt32(1103656293) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .privacyValueDisallowContacts: - if boxed { - buffer.appendInt32(-125240806) - } - - break - case .privacyValueDisallowUsers(let users): - if boxed { - buffer.appendInt32(-463335103) + buffer.appendInt32(-58224696) } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(minLayer, buffer: buffer, boxed: false) + serializeInt32(maxLayer, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - serializeInt64(item, buffer: buffer, boxed: false) + buffer.appendInt32(Int32(libraryVersions.count)) + for item in libraryVersions { + serializeString(item, buffer: buffer, boxed: false) } break } @@ -94,94 +22,28 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .privacyValueAllowAll: - return ("privacyValueAllowAll", []) - case .privacyValueAllowChatParticipants(let chats): - return ("privacyValueAllowChatParticipants", [("chats", chats as Any)]) - case .privacyValueAllowCloseFriends: - return ("privacyValueAllowCloseFriends", []) - case .privacyValueAllowContacts: - return ("privacyValueAllowContacts", []) - case .privacyValueAllowPremium: - return ("privacyValueAllowPremium", []) - case .privacyValueAllowUsers(let users): - return ("privacyValueAllowUsers", [("users", users as Any)]) - case .privacyValueDisallowAll: - return ("privacyValueDisallowAll", []) - case .privacyValueDisallowChatParticipants(let chats): - return ("privacyValueDisallowChatParticipants", [("chats", chats as Any)]) - case .privacyValueDisallowContacts: - return ("privacyValueDisallowContacts", []) - case .privacyValueDisallowUsers(let users): - return ("privacyValueDisallowUsers", [("users", users as Any)]) - } - } - - public static func parse_privacyValueAllowAll(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueAllowAll - } - public static func parse_privacyValueAllowChatParticipants(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueAllowChatParticipants(chats: _1!) - } - else { - return nil - } - } - public static func parse_privacyValueAllowCloseFriends(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueAllowCloseFriends - } - public static func parse_privacyValueAllowContacts(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueAllowContacts - } - public static func parse_privacyValueAllowPremium(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueAllowPremium - } - public static func parse_privacyValueAllowUsers(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueAllowUsers(users: _1!) - } - else { - return nil - } - } - public static func parse_privacyValueDisallowAll(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueDisallowAll - } - public static func parse_privacyValueDisallowChatParticipants(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueDisallowChatParticipants(chats: _1!) - } - else { - return nil - } - } - public static func parse_privacyValueDisallowContacts(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueDisallowContacts - } - public static func parse_privacyValueDisallowUsers(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? + case .phoneCallProtocol(let flags, let minLayer, let maxLayer, let libraryVersions): + return ("phoneCallProtocol", [("flags", flags as Any), ("minLayer", minLayer as Any), ("maxLayer", maxLayer as Any), ("libraryVersions", libraryVersions as Any)]) + } + } + + public static func parse_phoneCallProtocol(_ reader: BufferReader) -> PhoneCallProtocol? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [String]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) } let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueDisallowUsers(users: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PhoneCallProtocol.phoneCallProtocol(flags: _1!, minLayer: _2!, maxLayer: _3!, libraryVersions: _4!) } else { return nil @@ -191,63 +53,97 @@ public extension Api { } } public extension Api { - indirect enum PublicForward: TypeConstructorDescription { - case publicForwardMessage(message: Api.Message) - case publicForwardStory(peer: Api.Peer, story: Api.StoryItem) + enum PhoneConnection: TypeConstructorDescription { + case phoneConnection(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, peerTag: Buffer) + case phoneConnectionWebrtc(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, username: String, password: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .publicForwardMessage(let message): + case .phoneConnection(let flags, let id, let ip, let ipv6, let port, let peerTag): if boxed { - buffer.appendInt32(32685898) + buffer.appendInt32(-1665063993) } - message.serialize(buffer, true) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeString(ip, buffer: buffer, boxed: false) + serializeString(ipv6, buffer: buffer, boxed: false) + serializeInt32(port, buffer: buffer, boxed: false) + serializeBytes(peerTag, buffer: buffer, boxed: false) break - case .publicForwardStory(let peer, let story): + case .phoneConnectionWebrtc(let flags, let id, let ip, let ipv6, let port, let username, let password): if boxed { - buffer.appendInt32(-302797360) + buffer.appendInt32(1667228533) } - peer.serialize(buffer, true) - story.serialize(buffer, true) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeString(ip, buffer: buffer, boxed: false) + serializeString(ipv6, buffer: buffer, boxed: false) + serializeInt32(port, buffer: buffer, boxed: false) + serializeString(username, buffer: buffer, boxed: false) + serializeString(password, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .publicForwardMessage(let message): - return ("publicForwardMessage", [("message", message as Any)]) - case .publicForwardStory(let peer, let story): - return ("publicForwardStory", [("peer", peer as Any), ("story", story as Any)]) + case .phoneConnection(let flags, let id, let ip, let ipv6, let port, let peerTag): + return ("phoneConnection", [("flags", flags as Any), ("id", id as Any), ("ip", ip as Any), ("ipv6", ipv6 as Any), ("port", port as Any), ("peerTag", peerTag as Any)]) + case .phoneConnectionWebrtc(let flags, let id, let ip, let ipv6, let port, let username, let password): + return ("phoneConnectionWebrtc", [("flags", flags as Any), ("id", id as Any), ("ip", ip as Any), ("ipv6", ipv6 as Any), ("port", port as Any), ("username", username as Any), ("password", password as Any)]) } } - public static func parse_publicForwardMessage(_ reader: BufferReader) -> PublicForward? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } + public static func parse_phoneConnection(_ reader: BufferReader) -> PhoneConnection? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Buffer? + _6 = parseBytes(reader) let _c1 = _1 != nil - if _c1 { - return Api.PublicForward.publicForwardMessage(message: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PhoneConnection.phoneConnection(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, peerTag: _6!) } else { return nil } } - public static func parse_publicForwardStory(_ reader: BufferReader) -> PublicForward? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.StoryItem? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StoryItem - } + public static func parse_phoneConnectionWebrtc(_ reader: BufferReader) -> PhoneConnection? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: String? + _6 = parseString(reader) + var _7: String? + _7 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PublicForward.publicForwardStory(peer: _1!, story: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PhoneConnection.phoneConnectionWebrtc(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, username: _6!, password: _7!) } else { return nil @@ -257,45 +153,93 @@ public extension Api { } } public extension Api { - enum QuickReply: TypeConstructorDescription { - case quickReply(shortcutId: Int32, shortcut: String, topMessage: Int32, count: Int32) + enum Photo: TypeConstructorDescription { + case photo(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, sizes: [Api.PhotoSize], videoSizes: [Api.VideoSize]?, dcId: Int32) + case photoEmpty(id: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .quickReply(let shortcutId, let shortcut, let topMessage, let count): + case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes, let videoSizes, let dcId): + if boxed { + buffer.appendInt32(-82216347) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sizes.count)) + for item in sizes { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(videoSizes!.count)) + for item in videoSizes! { + item.serialize(buffer, true) + }} + serializeInt32(dcId, buffer: buffer, boxed: false) + break + case .photoEmpty(let id): if boxed { - buffer.appendInt32(110563371) + buffer.appendInt32(590459437) } - serializeInt32(shortcutId, buffer: buffer, boxed: false) - serializeString(shortcut, buffer: buffer, boxed: false) - serializeInt32(topMessage, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .quickReply(let shortcutId, let shortcut, let topMessage, let count): - return ("quickReply", [("shortcutId", shortcutId as Any), ("shortcut", shortcut as Any), ("topMessage", topMessage as Any), ("count", count as Any)]) + case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes, let videoSizes, let dcId): + return ("photo", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("fileReference", fileReference as Any), ("date", date as Any), ("sizes", sizes as Any), ("videoSizes", videoSizes as Any), ("dcId", dcId as Any)]) + case .photoEmpty(let id): + return ("photoEmpty", [("id", id as Any)]) } } - public static func parse_quickReply(_ reader: BufferReader) -> QuickReply? { + public static func parse_photo(_ reader: BufferReader) -> Photo? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: [Api.PhotoSize]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) + } + var _7: [Api.VideoSize]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.VideoSize.self) + } } + var _8: Int32? + _8 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.QuickReply.quickReply(shortcutId: _1!, shortcut: _2!, topMessage: _3!, count: _4!) + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Photo.photo(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, sizes: _6!, videoSizes: _7, dcId: _8!) + } + else { + return nil + } + } + public static func parse_photoEmpty(_ reader: BufferReader) -> Photo? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Photo.photoEmpty(id: _1!) } else { return nil @@ -305,115 +249,247 @@ public extension Api { } } public extension Api { - enum Reaction: TypeConstructorDescription { - case reactionCustomEmoji(documentId: Int64) - case reactionEmoji(emoticon: String) - case reactionEmpty + enum PhotoSize: TypeConstructorDescription { + case photoCachedSize(type: String, w: Int32, h: Int32, bytes: Buffer) + case photoPathSize(type: String, bytes: Buffer) + case photoSize(type: String, w: Int32, h: Int32, size: Int32) + case photoSizeEmpty(type: String) + case photoSizeProgressive(type: String, w: Int32, h: Int32, sizes: [Int32]) + case photoStrippedSize(type: String, bytes: Buffer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .reactionCustomEmoji(let documentId): + case .photoCachedSize(let type, let w, let h, let bytes): if boxed { - buffer.appendInt32(-1992950669) + buffer.appendInt32(35527382) } - serializeInt64(documentId, buffer: buffer, boxed: false) + serializeString(type, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) break - case .reactionEmoji(let emoticon): + case .photoPathSize(let type, let bytes): if boxed { - buffer.appendInt32(455247544) + buffer.appendInt32(-668906175) } - serializeString(emoticon, buffer: buffer, boxed: false) + serializeString(type, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) break - case .reactionEmpty: + case .photoSize(let type, let w, let h, let size): if boxed { - buffer.appendInt32(2046153753) + buffer.appendInt32(1976012384) } - + serializeString(type, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + break + case .photoSizeEmpty(let type): + if boxed { + buffer.appendInt32(236446268) + } + serializeString(type, buffer: buffer, boxed: false) + break + case .photoSizeProgressive(let type, let w, let h, let sizes): + if boxed { + buffer.appendInt32(-96535659) + } + serializeString(type, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sizes.count)) + for item in sizes { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .photoStrippedSize(let type, let bytes): + if boxed { + buffer.appendInt32(-525288402) + } + serializeString(type, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .reactionCustomEmoji(let documentId): - return ("reactionCustomEmoji", [("documentId", documentId as Any)]) - case .reactionEmoji(let emoticon): - return ("reactionEmoji", [("emoticon", emoticon as Any)]) - case .reactionEmpty: - return ("reactionEmpty", []) + case .photoCachedSize(let type, let w, let h, let bytes): + return ("photoCachedSize", [("type", type as Any), ("w", w as Any), ("h", h as Any), ("bytes", bytes as Any)]) + case .photoPathSize(let type, let bytes): + return ("photoPathSize", [("type", type as Any), ("bytes", bytes as Any)]) + case .photoSize(let type, let w, let h, let size): + return ("photoSize", [("type", type as Any), ("w", w as Any), ("h", h as Any), ("size", size as Any)]) + case .photoSizeEmpty(let type): + return ("photoSizeEmpty", [("type", type as Any)]) + case .photoSizeProgressive(let type, let w, let h, let sizes): + return ("photoSizeProgressive", [("type", type as Any), ("w", w as Any), ("h", h as Any), ("sizes", sizes as Any)]) + case .photoStrippedSize(let type, let bytes): + return ("photoStrippedSize", [("type", type as Any), ("bytes", bytes as Any)]) } } - public static func parse_reactionCustomEmoji(_ reader: BufferReader) -> Reaction? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) let _c1 = _1 != nil - if _c1 { - return Api.Reaction.reactionCustomEmoji(documentId: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PhotoSize.photoCachedSize(type: _1!, w: _2!, h: _3!, bytes: _4!) + } + else { + return nil + } + } + public static func parse_photoPathSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PhotoSize.photoPathSize(type: _1!, bytes: _2!) + } + else { + return nil + } + } + public static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PhotoSize.photoSize(type: _1!, w: _2!, h: _3!, size: _4!) } else { return nil } } - public static func parse_reactionEmoji(_ reader: BufferReader) -> Reaction? { + public static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.Reaction.reactionEmoji(emoticon: _1!) + return Api.PhotoSize.photoSizeEmpty(type: _1!) + } + else { + return nil + } + } + public static func parse_photoSizeProgressive(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Int32]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PhotoSize.photoSizeProgressive(type: _1!, w: _2!, h: _3!, sizes: _4!) } else { return nil } } - public static func parse_reactionEmpty(_ reader: BufferReader) -> Reaction? { - return Api.Reaction.reactionEmpty + public static func parse_photoStrippedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PhotoSize.photoStrippedSize(type: _1!, bytes: _2!) + } + else { + return nil + } } } } public extension Api { - enum ReactionCount: TypeConstructorDescription { - case reactionCount(flags: Int32, chosenOrder: Int32?, reaction: Api.Reaction, count: Int32) + enum Poll: TypeConstructorDescription { + case poll(id: Int64, flags: Int32, question: Api.TextWithEntities, answers: [Api.PollAnswer], closePeriod: Int32?, closeDate: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .reactionCount(let flags, let chosenOrder, let reaction, let count): + case .poll(let id, let flags, let question, let answers, let closePeriod, let closeDate): if boxed { - buffer.appendInt32(-1546531968) + buffer.appendInt32(1484026161) } + serializeInt64(id, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(chosenOrder!, buffer: buffer, boxed: false)} - reaction.serialize(buffer, true) - serializeInt32(count, buffer: buffer, boxed: false) + question.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(answers.count)) + for item in answers { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(closePeriod!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {serializeInt32(closeDate!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .reactionCount(let flags, let chosenOrder, let reaction, let count): - return ("reactionCount", [("flags", flags as Any), ("chosenOrder", chosenOrder as Any), ("reaction", reaction as Any), ("count", count as Any)]) + case .poll(let id, let flags, let question, let answers, let closePeriod, let closeDate): + return ("poll", [("id", id as Any), ("flags", flags as Any), ("question", question as Any), ("answers", answers as Any), ("closePeriod", closePeriod as Any), ("closeDate", closeDate as Any)]) } } - public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_poll(_ reader: BufferReader) -> Poll? { + var _1: Int64? + _1 = reader.readInt64() var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: Api.Reaction? + _2 = reader.readInt32() + var _3: Api.TextWithEntities? if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Reaction + _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } - var _4: Int32? - _4 = reader.readInt32() + var _4: [Api.PollAnswer]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PollAnswer.self) + } + var _5: Int32? + if Int(_2!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_2!) & Int(1 << 5) != 0 {_6 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.ReactionCount.reactionCount(flags: _1!, chosenOrder: _2, reaction: _3!, count: _4!) + let _c5 = (Int(_2!) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_2!) & Int(1 << 5) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Poll.poll(id: _1!, flags: _2!, question: _3!, answers: _4!, closePeriod: _5, closeDate: _6) } else { return nil @@ -423,97 +499,157 @@ public extension Api { } } public extension Api { - enum ReactionNotificationsFrom: TypeConstructorDescription { - case reactionNotificationsFromAll - case reactionNotificationsFromContacts + enum PollAnswer: TypeConstructorDescription { + case pollAnswer(text: Api.TextWithEntities, option: Buffer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .reactionNotificationsFromAll: + case .pollAnswer(let text, let option): if boxed { - buffer.appendInt32(1268654752) + buffer.appendInt32(-15277366) } - + text.serialize(buffer, true) + serializeBytes(option, buffer: buffer, boxed: false) break - case .reactionNotificationsFromContacts: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .pollAnswer(let text, let option): + return ("pollAnswer", [("text", text as Any), ("option", option as Any)]) + } + } + + public static func parse_pollAnswer(_ reader: BufferReader) -> PollAnswer? { + var _1: Api.TextWithEntities? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PollAnswer.pollAnswer(text: _1!, option: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum PollAnswerVoters: TypeConstructorDescription { + case pollAnswerVoters(flags: Int32, option: Buffer, voters: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pollAnswerVoters(let flags, let option, let voters): if boxed { - buffer.appendInt32(-1161583078) + buffer.appendInt32(997055186) } - + serializeInt32(flags, buffer: buffer, boxed: false) + serializeBytes(option, buffer: buffer, boxed: false) + serializeInt32(voters, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .reactionNotificationsFromAll: - return ("reactionNotificationsFromAll", []) - case .reactionNotificationsFromContacts: - return ("reactionNotificationsFromContacts", []) + case .pollAnswerVoters(let flags, let option, let voters): + return ("pollAnswerVoters", [("flags", flags as Any), ("option", option as Any), ("voters", voters as Any)]) } } - public static func parse_reactionNotificationsFromAll(_ reader: BufferReader) -> ReactionNotificationsFrom? { - return Api.ReactionNotificationsFrom.reactionNotificationsFromAll - } - public static func parse_reactionNotificationsFromContacts(_ reader: BufferReader) -> ReactionNotificationsFrom? { - return Api.ReactionNotificationsFrom.reactionNotificationsFromContacts + public static func parse_pollAnswerVoters(_ reader: BufferReader) -> PollAnswerVoters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.PollAnswerVoters.pollAnswerVoters(flags: _1!, option: _2!, voters: _3!) + } + else { + return nil + } } } } public extension Api { - enum ReactionsNotifySettings: TypeConstructorDescription { - case reactionsNotifySettings(flags: Int32, messagesNotifyFrom: Api.ReactionNotificationsFrom?, storiesNotifyFrom: Api.ReactionNotificationsFrom?, sound: Api.NotificationSound, showPreviews: Api.Bool) + enum PollResults: TypeConstructorDescription { + case pollResults(flags: Int32, results: [Api.PollAnswerVoters]?, totalVoters: Int32?, recentVoters: [Api.Peer]?, solution: String?, solutionEntities: [Api.MessageEntity]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .reactionsNotifySettings(let flags, let messagesNotifyFrom, let storiesNotifyFrom, let sound, let showPreviews): + case .pollResults(let flags, let results, let totalVoters, let recentVoters, let solution, let solutionEntities): if boxed { - buffer.appendInt32(1457736048) + buffer.appendInt32(2061444128) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {messagesNotifyFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {storiesNotifyFrom!.serialize(buffer, true)} - sound.serialize(buffer, true) - showPreviews.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(results!.count)) + for item in results! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(totalVoters!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentVoters!.count)) + for item in recentVoters! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 4) != 0 {serializeString(solution!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(solutionEntities!.count)) + for item in solutionEntities! { + item.serialize(buffer, true) + }} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .reactionsNotifySettings(let flags, let messagesNotifyFrom, let storiesNotifyFrom, let sound, let showPreviews): - return ("reactionsNotifySettings", [("flags", flags as Any), ("messagesNotifyFrom", messagesNotifyFrom as Any), ("storiesNotifyFrom", storiesNotifyFrom as Any), ("sound", sound as Any), ("showPreviews", showPreviews as Any)]) + case .pollResults(let flags, let results, let totalVoters, let recentVoters, let solution, let solutionEntities): + return ("pollResults", [("flags", flags as Any), ("results", results as Any), ("totalVoters", totalVoters as Any), ("recentVoters", recentVoters as Any), ("solution", solution as Any), ("solutionEntities", solutionEntities as Any)]) } } - public static func parse_reactionsNotifySettings(_ reader: BufferReader) -> ReactionsNotifySettings? { + public static func parse_pollResults(_ reader: BufferReader) -> PollResults? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.ReactionNotificationsFrom? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom + var _2: [Api.PollAnswerVoters]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PollAnswerVoters.self) } } - var _3: Api.ReactionNotificationsFrom? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom + var _3: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } + var _4: [Api.Peer]? + if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } } + var _5: String? + if Int(_1!) & Int(1 << 4) != 0 {_5 = parseString(reader) } + var _6: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } - var _4: Api.NotificationSound? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } - var _5: Api.Bool? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Bool - } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.ReactionsNotifySettings.reactionsNotifySettings(flags: _1!, messagesNotifyFrom: _2, storiesNotifyFrom: _3, sound: _4!, showPreviews: _5!) + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PollResults.pollResults(flags: _1!, results: _2, totalVoters: _3, recentVoters: _4, solution: _5, solutionEntities: _6) } else { return nil @@ -523,29 +659,29 @@ public extension Api { } } public extension Api { - enum ReadParticipantDate: TypeConstructorDescription { - case readParticipantDate(userId: Int64, date: Int32) + enum PopularContact: TypeConstructorDescription { + case popularContact(clientId: Int64, importers: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .readParticipantDate(let userId, let date): + case .popularContact(let clientId, let importers): if boxed { - buffer.appendInt32(1246753138) + buffer.appendInt32(1558266229) } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(clientId, buffer: buffer, boxed: false) + serializeInt32(importers, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .readParticipantDate(let userId, let date): - return ("readParticipantDate", [("userId", userId as Any), ("date", date as Any)]) + case .popularContact(let clientId, let importers): + return ("popularContact", [("clientId", clientId as Any), ("importers", importers as Any)]) } } - public static func parse_readParticipantDate(_ reader: BufferReader) -> ReadParticipantDate? { + public static func parse_popularContact(_ reader: BufferReader) -> PopularContact? { var _1: Int64? _1 = reader.readInt64() var _2: Int32? @@ -553,7 +689,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.ReadParticipantDate.readParticipantDate(userId: _1!, date: _2!) + return Api.PopularContact.popularContact(clientId: _1!, importers: _2!) } else { return nil @@ -563,37 +699,53 @@ public extension Api { } } public extension Api { - enum ReceivedNotifyMessage: TypeConstructorDescription { - case receivedNotifyMessage(id: Int32, flags: Int32) + enum PostAddress: TypeConstructorDescription { + case postAddress(streetLine1: String, streetLine2: String, city: String, state: String, countryIso2: String, postCode: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .receivedNotifyMessage(let id, let flags): + case .postAddress(let streetLine1, let streetLine2, let city, let state, let countryIso2, let postCode): if boxed { - buffer.appendInt32(-1551583367) + buffer.appendInt32(512535275) } - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(streetLine1, buffer: buffer, boxed: false) + serializeString(streetLine2, buffer: buffer, boxed: false) + serializeString(city, buffer: buffer, boxed: false) + serializeString(state, buffer: buffer, boxed: false) + serializeString(countryIso2, buffer: buffer, boxed: false) + serializeString(postCode, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .receivedNotifyMessage(let id, let flags): - return ("receivedNotifyMessage", [("id", id as Any), ("flags", flags as Any)]) + case .postAddress(let streetLine1, let streetLine2, let city, let state, let countryIso2, let postCode): + return ("postAddress", [("streetLine1", streetLine1 as Any), ("streetLine2", streetLine2 as Any), ("city", city as Any), ("state", state as Any), ("countryIso2", countryIso2 as Any), ("postCode", postCode as Any)]) } } - public static func parse_receivedNotifyMessage(_ reader: BufferReader) -> ReceivedNotifyMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + public static func parse_postAddress(_ reader: BufferReader) -> PostAddress? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ReceivedNotifyMessage.receivedNotifyMessage(id: _1!, flags: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PostAddress.postAddress(streetLine1: _1!, streetLine2: _2!, city: _3!, state: _4!, countryIso2: _5!, postCode: _6!) } else { return nil @@ -603,133 +755,193 @@ public extension Api { } } public extension Api { - indirect enum RecentMeUrl: TypeConstructorDescription { - case recentMeUrlChat(url: String, chatId: Int64) - case recentMeUrlChatInvite(url: String, chatInvite: Api.ChatInvite) - case recentMeUrlStickerSet(url: String, set: Api.StickerSetCovered) - case recentMeUrlUnknown(url: String) - case recentMeUrlUser(url: String, userId: Int64) + enum PostInteractionCounters: TypeConstructorDescription { + case postInteractionCountersMessage(msgId: Int32, views: Int32, forwards: Int32, reactions: Int32) + case postInteractionCountersStory(storyId: Int32, views: Int32, forwards: Int32, reactions: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .recentMeUrlChat(let url, let chatId): - if boxed { - buffer.appendInt32(-1294306862) - } - serializeString(url, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) - break - case .recentMeUrlChatInvite(let url, let chatInvite): - if boxed { - buffer.appendInt32(-347535331) - } - serializeString(url, buffer: buffer, boxed: false) - chatInvite.serialize(buffer, true) - break - case .recentMeUrlStickerSet(let url, let set): - if boxed { - buffer.appendInt32(-1140172836) - } - serializeString(url, buffer: buffer, boxed: false) - set.serialize(buffer, true) - break - case .recentMeUrlUnknown(let url): + case .postInteractionCountersMessage(let msgId, let views, let forwards, let reactions): if boxed { - buffer.appendInt32(1189204285) + buffer.appendInt32(-419066241) } - serializeString(url, buffer: buffer, boxed: false) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(views, buffer: buffer, boxed: false) + serializeInt32(forwards, buffer: buffer, boxed: false) + serializeInt32(reactions, buffer: buffer, boxed: false) break - case .recentMeUrlUser(let url, let userId): + case .postInteractionCountersStory(let storyId, let views, let forwards, let reactions): if boxed { - buffer.appendInt32(-1188296222) + buffer.appendInt32(-1974989273) } - serializeString(url, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(storyId, buffer: buffer, boxed: false) + serializeInt32(views, buffer: buffer, boxed: false) + serializeInt32(forwards, buffer: buffer, boxed: false) + serializeInt32(reactions, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .recentMeUrlChat(let url, let chatId): - return ("recentMeUrlChat", [("url", url as Any), ("chatId", chatId as Any)]) - case .recentMeUrlChatInvite(let url, let chatInvite): - return ("recentMeUrlChatInvite", [("url", url as Any), ("chatInvite", chatInvite as Any)]) - case .recentMeUrlStickerSet(let url, let set): - return ("recentMeUrlStickerSet", [("url", url as Any), ("set", set as Any)]) - case .recentMeUrlUnknown(let url): - return ("recentMeUrlUnknown", [("url", url as Any)]) - case .recentMeUrlUser(let url, let userId): - return ("recentMeUrlUser", [("url", url as Any), ("userId", userId as Any)]) + case .postInteractionCountersMessage(let msgId, let views, let forwards, let reactions): + return ("postInteractionCountersMessage", [("msgId", msgId as Any), ("views", views as Any), ("forwards", forwards as Any), ("reactions", reactions as Any)]) + case .postInteractionCountersStory(let storyId, let views, let forwards, let reactions): + return ("postInteractionCountersStory", [("storyId", storyId as Any), ("views", views as Any), ("forwards", forwards as Any), ("reactions", reactions as Any)]) } } - public static func parse_recentMeUrlChat(_ reader: BufferReader) -> RecentMeUrl? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() + public static func parse_postInteractionCountersMessage(_ reader: BufferReader) -> PostInteractionCounters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlChat(url: _1!, chatId: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PostInteractionCounters.postInteractionCountersMessage(msgId: _1!, views: _2!, forwards: _3!, reactions: _4!) } else { return nil } } - public static func parse_recentMeUrlChatInvite(_ reader: BufferReader) -> RecentMeUrl? { - var _1: String? - _1 = parseString(reader) - var _2: Api.ChatInvite? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.ChatInvite - } + public static func parse_postInteractionCountersStory(_ reader: BufferReader) -> PostInteractionCounters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlChatInvite(url: _1!, chatInvite: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PostInteractionCounters.postInteractionCountersStory(storyId: _1!, views: _2!, forwards: _3!, reactions: _4!) } else { return nil } } - public static func parse_recentMeUrlStickerSet(_ reader: BufferReader) -> RecentMeUrl? { - var _1: String? - _1 = parseString(reader) - var _2: Api.StickerSetCovered? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StickerSetCovered - } + + } +} +public extension Api { + enum PremiumGiftCodeOption: TypeConstructorDescription { + case premiumGiftCodeOption(flags: Int32, users: Int32, months: Int32, storeProduct: String?, storeQuantity: Int32?, currency: String, amount: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .premiumGiftCodeOption(let flags, let users, let months, let storeProduct, let storeQuantity, let currency, let amount): + if boxed { + buffer.appendInt32(629052971) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(users, buffer: buffer, boxed: false) + serializeInt32(months, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(storeQuantity!, buffer: buffer, boxed: false)} + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .premiumGiftCodeOption(let flags, let users, let months, let storeProduct, let storeQuantity, let currency, let amount): + return ("premiumGiftCodeOption", [("flags", flags as Any), ("users", users as Any), ("months", months as Any), ("storeProduct", storeProduct as Any), ("storeQuantity", storeQuantity as Any), ("currency", currency as Any), ("amount", amount as Any)]) + } + } + + public static func parse_premiumGiftCodeOption(_ reader: BufferReader) -> PremiumGiftCodeOption? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + var _5: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } + var _6: String? + _6 = parseString(reader) + var _7: Int64? + _7 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlStickerSet(url: _1!, set: _2!) - } - else { - return nil - } - } - public static func parse_recentMeUrlUnknown(_ reader: BufferReader) -> RecentMeUrl? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.RecentMeUrl.recentMeUrlUnknown(url: _1!) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PremiumGiftCodeOption.premiumGiftCodeOption(flags: _1!, users: _2!, months: _3!, storeProduct: _4, storeQuantity: _5, currency: _6!, amount: _7!) } else { return nil } } - public static func parse_recentMeUrlUser(_ reader: BufferReader) -> RecentMeUrl? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() + + } +} +public extension Api { + enum PremiumGiftOption: TypeConstructorDescription { + case premiumGiftOption(flags: Int32, months: Int32, currency: String, amount: Int64, botUrl: String, storeProduct: String?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .premiumGiftOption(let flags, let months, let currency, let amount, let botUrl, let storeProduct): + if boxed { + buffer.appendInt32(1958953753) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(months, buffer: buffer, boxed: false) + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + serializeString(botUrl, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .premiumGiftOption(let flags, let months, let currency, let amount, let botUrl, let storeProduct): + return ("premiumGiftOption", [("flags", flags as Any), ("months", months as Any), ("currency", currency as Any), ("amount", amount as Any), ("botUrl", botUrl as Any), ("storeProduct", storeProduct as Any)]) + } + } + + public static func parse_premiumGiftOption(_ reader: BufferReader) -> PremiumGiftOption? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlUser(url: _1!, userId: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PremiumGiftOption.premiumGiftOption(flags: _1!, months: _2!, currency: _3!, amount: _4!, botUrl: _5!, storeProduct: _6) } else { return nil @@ -739,117 +951,105 @@ public extension Api { } } public extension Api { - enum ReplyMarkup: TypeConstructorDescription { - case replyInlineMarkup(rows: [Api.KeyboardButtonRow]) - case replyKeyboardForceReply(flags: Int32, placeholder: String?) - case replyKeyboardHide(flags: Int32) - case replyKeyboardMarkup(flags: Int32, rows: [Api.KeyboardButtonRow], placeholder: String?) + enum PremiumSubscriptionOption: TypeConstructorDescription { + case premiumSubscriptionOption(flags: Int32, transaction: String?, months: Int32, currency: String, amount: Int64, botUrl: String, storeProduct: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .replyInlineMarkup(let rows): - if boxed { - buffer.appendInt32(1218642516) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(rows.count)) - for item in rows { - item.serialize(buffer, true) - } - break - case .replyKeyboardForceReply(let flags, let placeholder): - if boxed { - buffer.appendInt32(-2035021048) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {serializeString(placeholder!, buffer: buffer, boxed: false)} - break - case .replyKeyboardHide(let flags): - if boxed { - buffer.appendInt32(-1606526075) - } - serializeInt32(flags, buffer: buffer, boxed: false) - break - case .replyKeyboardMarkup(let flags, let rows, let placeholder): + case .premiumSubscriptionOption(let flags, let transaction, let months, let currency, let amount, let botUrl, let storeProduct): if boxed { - buffer.appendInt32(-2049074735) + buffer.appendInt32(1596792306) } serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(rows.count)) - for item in rows { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 {serializeString(placeholder!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeString(transaction!, buffer: buffer, boxed: false)} + serializeInt32(months, buffer: buffer, boxed: false) + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + serializeString(botUrl, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .replyInlineMarkup(let rows): - return ("replyInlineMarkup", [("rows", rows as Any)]) - case .replyKeyboardForceReply(let flags, let placeholder): - return ("replyKeyboardForceReply", [("flags", flags as Any), ("placeholder", placeholder as Any)]) - case .replyKeyboardHide(let flags): - return ("replyKeyboardHide", [("flags", flags as Any)]) - case .replyKeyboardMarkup(let flags, let rows, let placeholder): - return ("replyKeyboardMarkup", [("flags", flags as Any), ("rows", rows as Any), ("placeholder", placeholder as Any)]) + case .premiumSubscriptionOption(let flags, let transaction, let months, let currency, let amount, let botUrl, let storeProduct): + return ("premiumSubscriptionOption", [("flags", flags as Any), ("transaction", transaction as Any), ("months", months as Any), ("currency", currency as Any), ("amount", amount as Any), ("botUrl", botUrl as Any), ("storeProduct", storeProduct as Any)]) } } - public static func parse_replyInlineMarkup(_ reader: BufferReader) -> ReplyMarkup? { - var _1: [Api.KeyboardButtonRow]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButtonRow.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.ReplyMarkup.replyInlineMarkup(rows: _1!) - } - else { - return nil - } - } - public static func parse_replyKeyboardForceReply(_ reader: BufferReader) -> ReplyMarkup? { + public static func parse_premiumSubscriptionOption(_ reader: BufferReader) -> PremiumSubscriptionOption? { var _1: Int32? _1 = reader.readInt32() var _2: String? if Int(_1!) & Int(1 << 3) != 0 {_2 = parseString(reader) } + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + var _6: String? + _6 = parseString(reader) + var _7: String? + if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - if _c1 && _c2 { - return Api.ReplyMarkup.replyKeyboardForceReply(flags: _1!, placeholder: _2) - } - else { - return nil - } - } - public static func parse_replyKeyboardHide(_ reader: BufferReader) -> ReplyMarkup? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.ReplyMarkup.replyKeyboardHide(flags: _1!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PremiumSubscriptionOption.premiumSubscriptionOption(flags: _1!, transaction: _2, months: _3!, currency: _4!, amount: _5!, botUrl: _6!, storeProduct: _7) } else { return nil } } - public static func parse_replyKeyboardMarkup(_ reader: BufferReader) -> ReplyMarkup? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.KeyboardButtonRow]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButtonRow.self) - } - var _3: String? - if Int(_1!) & Int(1 << 3) != 0 {_3 = parseString(reader) } + + } +} +public extension Api { + enum PrepaidGiveaway: TypeConstructorDescription { + case prepaidGiveaway(id: Int64, months: Int32, quantity: Int32, date: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .prepaidGiveaway(let id, let months, let quantity, let date): + if boxed { + buffer.appendInt32(-1303143084) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(months, buffer: buffer, boxed: false) + serializeInt32(quantity, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .prepaidGiveaway(let id, let months, let quantity, let date): + return ("prepaidGiveaway", [("id", id as Any), ("months", months as Any), ("quantity", quantity as Any), ("date", date as Any)]) + } + } + + public static func parse_prepaidGiveaway(_ reader: BufferReader) -> PrepaidGiveaway? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.ReplyMarkup.replyKeyboardMarkup(flags: _1!, rows: _2!, placeholder: _3) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PrepaidGiveaway.prepaidGiveaway(id: _1!, months: _2!, quantity: _3!, date: _4!) } else { return nil @@ -859,77 +1059,84 @@ public extension Api { } } public extension Api { - enum ReportReason: TypeConstructorDescription { - case inputReportReasonChildAbuse - case inputReportReasonCopyright - case inputReportReasonFake - case inputReportReasonGeoIrrelevant - case inputReportReasonIllegalDrugs - case inputReportReasonOther - case inputReportReasonPersonalDetails - case inputReportReasonPornography - case inputReportReasonSpam - case inputReportReasonViolence + enum PrivacyKey: TypeConstructorDescription { + case privacyKeyAbout + case privacyKeyAddedByPhone + case privacyKeyBirthday + case privacyKeyChatInvite + case privacyKeyForwards + case privacyKeyPhoneCall + case privacyKeyPhoneNumber + case privacyKeyPhoneP2P + case privacyKeyProfilePhoto + case privacyKeyStatusTimestamp + case privacyKeyVoiceMessages public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .inputReportReasonChildAbuse: + case .privacyKeyAbout: + if boxed { + buffer.appendInt32(-1534675103) + } + + break + case .privacyKeyAddedByPhone: if boxed { - buffer.appendInt32(-1376497949) + buffer.appendInt32(1124062251) } break - case .inputReportReasonCopyright: + case .privacyKeyBirthday: if boxed { - buffer.appendInt32(-1685456582) + buffer.appendInt32(536913176) } break - case .inputReportReasonFake: + case .privacyKeyChatInvite: if boxed { - buffer.appendInt32(-170010905) + buffer.appendInt32(1343122938) } break - case .inputReportReasonGeoIrrelevant: + case .privacyKeyForwards: if boxed { - buffer.appendInt32(-606798099) + buffer.appendInt32(1777096355) } break - case .inputReportReasonIllegalDrugs: + case .privacyKeyPhoneCall: if boxed { - buffer.appendInt32(177124030) + buffer.appendInt32(1030105979) } break - case .inputReportReasonOther: + case .privacyKeyPhoneNumber: if boxed { - buffer.appendInt32(-1041980751) + buffer.appendInt32(-778378131) } break - case .inputReportReasonPersonalDetails: + case .privacyKeyPhoneP2P: if boxed { - buffer.appendInt32(-1631091139) + buffer.appendInt32(961092808) } break - case .inputReportReasonPornography: + case .privacyKeyProfilePhoto: if boxed { - buffer.appendInt32(777640226) + buffer.appendInt32(-1777000467) } break - case .inputReportReasonSpam: + case .privacyKeyStatusTimestamp: if boxed { - buffer.appendInt32(1490799288) + buffer.appendInt32(-1137792208) } break - case .inputReportReasonViolence: + case .privacyKeyVoiceMessages: if boxed { - buffer.appendInt32(505595789) + buffer.appendInt32(110621716) } break @@ -938,58 +1145,63 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .inputReportReasonChildAbuse: - return ("inputReportReasonChildAbuse", []) - case .inputReportReasonCopyright: - return ("inputReportReasonCopyright", []) - case .inputReportReasonFake: - return ("inputReportReasonFake", []) - case .inputReportReasonGeoIrrelevant: - return ("inputReportReasonGeoIrrelevant", []) - case .inputReportReasonIllegalDrugs: - return ("inputReportReasonIllegalDrugs", []) - case .inputReportReasonOther: - return ("inputReportReasonOther", []) - case .inputReportReasonPersonalDetails: - return ("inputReportReasonPersonalDetails", []) - case .inputReportReasonPornography: - return ("inputReportReasonPornography", []) - case .inputReportReasonSpam: - return ("inputReportReasonSpam", []) - case .inputReportReasonViolence: - return ("inputReportReasonViolence", []) - } - } - - public static func parse_inputReportReasonChildAbuse(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonChildAbuse - } - public static func parse_inputReportReasonCopyright(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonCopyright - } - public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonFake - } - public static func parse_inputReportReasonGeoIrrelevant(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonGeoIrrelevant - } - public static func parse_inputReportReasonIllegalDrugs(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonIllegalDrugs - } - public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonOther - } - public static func parse_inputReportReasonPersonalDetails(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonPersonalDetails - } - public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonPornography - } - public static func parse_inputReportReasonSpam(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonSpam - } - public static func parse_inputReportReasonViolence(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonViolence + case .privacyKeyAbout: + return ("privacyKeyAbout", []) + case .privacyKeyAddedByPhone: + return ("privacyKeyAddedByPhone", []) + case .privacyKeyBirthday: + return ("privacyKeyBirthday", []) + case .privacyKeyChatInvite: + return ("privacyKeyChatInvite", []) + case .privacyKeyForwards: + return ("privacyKeyForwards", []) + case .privacyKeyPhoneCall: + return ("privacyKeyPhoneCall", []) + case .privacyKeyPhoneNumber: + return ("privacyKeyPhoneNumber", []) + case .privacyKeyPhoneP2P: + return ("privacyKeyPhoneP2P", []) + case .privacyKeyProfilePhoto: + return ("privacyKeyProfilePhoto", []) + case .privacyKeyStatusTimestamp: + return ("privacyKeyStatusTimestamp", []) + case .privacyKeyVoiceMessages: + return ("privacyKeyVoiceMessages", []) + } + } + + public static func parse_privacyKeyAbout(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyAbout + } + public static func parse_privacyKeyAddedByPhone(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyAddedByPhone + } + public static func parse_privacyKeyBirthday(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyBirthday + } + public static func parse_privacyKeyChatInvite(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyChatInvite + } + public static func parse_privacyKeyForwards(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyForwards + } + public static func parse_privacyKeyPhoneCall(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyPhoneCall + } + public static func parse_privacyKeyPhoneNumber(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyPhoneNumber + } + public static func parse_privacyKeyPhoneP2P(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyPhoneP2P + } + public static func parse_privacyKeyProfilePhoto(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyProfilePhoto + } + public static func parse_privacyKeyStatusTimestamp(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyStatusTimestamp + } + public static func parse_privacyKeyVoiceMessages(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyVoiceMessages } } diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index 51d1fb1d7fe..0880c7cd5a9 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -1,3 +1,43 @@ +public extension Api { + enum BotCommand: TypeConstructorDescription { + case botCommand(command: String, description: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botCommand(let command, let description): + if boxed { + buffer.appendInt32(-1032140601) + } + serializeString(command, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .botCommand(let command, let description): + return ("botCommand", [("command", command as Any), ("description", description as Any)]) + } + } + + public static func parse_botCommand(_ reader: BufferReader) -> BotCommand? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.BotCommand.botCommand(command: _1!, description: _2!) + } + else { + return nil + } + } + + } +} public extension Api { indirect enum BotCommandScope: TypeConstructorDescription { case botCommandScopeChatAdmins @@ -1160,53 +1200,3 @@ public extension Api { } } -public extension Api { - enum BusinessIntro: TypeConstructorDescription { - case businessIntro(flags: Int32, title: String, description: String, sticker: Api.Document?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .businessIntro(let flags, let title, let description, let sticker): - if boxed { - buffer.appendInt32(1510606445) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {sticker!.serialize(buffer, true)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .businessIntro(let flags, let title, let description, let sticker): - return ("businessIntro", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("sticker", sticker as Any)]) - } - } - - public static func parse_businessIntro(_ reader: BufferReader) -> BusinessIntro? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: Api.Document? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Document - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.BusinessIntro.businessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api20.swift b/submodules/TelegramApi/Sources/Api20.swift index 83924b0d6bb..2b1d7e1f79b 100644 --- a/submodules/TelegramApi/Sources/Api20.swift +++ b/submodules/TelegramApi/Sources/Api20.swift @@ -1,125 +1,187 @@ public extension Api { - enum RequestPeerType: TypeConstructorDescription { - case requestPeerTypeBroadcast(flags: Int32, hasUsername: Api.Bool?, userAdminRights: Api.ChatAdminRights?, botAdminRights: Api.ChatAdminRights?) - case requestPeerTypeChat(flags: Int32, hasUsername: Api.Bool?, forum: Api.Bool?, userAdminRights: Api.ChatAdminRights?, botAdminRights: Api.ChatAdminRights?) - case requestPeerTypeUser(flags: Int32, bot: Api.Bool?, premium: Api.Bool?) + enum PrivacyRule: TypeConstructorDescription { + case privacyValueAllowAll + case privacyValueAllowChatParticipants(chats: [Int64]) + case privacyValueAllowCloseFriends + case privacyValueAllowContacts + case privacyValueAllowPremium + case privacyValueAllowUsers(users: [Int64]) + case privacyValueDisallowAll + case privacyValueDisallowChatParticipants(chats: [Int64]) + case privacyValueDisallowContacts + case privacyValueDisallowUsers(users: [Int64]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .requestPeerTypeBroadcast(let flags, let hasUsername, let userAdminRights, let botAdminRights): + case .privacyValueAllowAll: if boxed { - buffer.appendInt32(865857388) + buffer.appendInt32(1698855810) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {hasUsername!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {userAdminRights!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {botAdminRights!.serialize(buffer, true)} + break - case .requestPeerTypeChat(let flags, let hasUsername, let forum, let userAdminRights, let botAdminRights): + case .privacyValueAllowChatParticipants(let chats): if boxed { - buffer.appendInt32(-906990053) + buffer.appendInt32(1796427406) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + serializeInt64(item, buffer: buffer, boxed: false) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {hasUsername!.serialize(buffer, true)} - if Int(flags) & Int(1 << 4) != 0 {forum!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {userAdminRights!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {botAdminRights!.serialize(buffer, true)} break - case .requestPeerTypeUser(let flags, let bot, let premium): + case .privacyValueAllowCloseFriends: if boxed { - buffer.appendInt32(1597737472) + buffer.appendInt32(-135735141) + } + + break + case .privacyValueAllowContacts: + if boxed { + buffer.appendInt32(-123988) + } + + break + case .privacyValueAllowPremium: + if boxed { + buffer.appendInt32(-320241333) + } + + break + case .privacyValueAllowUsers(let users): + if boxed { + buffer.appendInt32(-1198497870) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .privacyValueDisallowAll: + if boxed { + buffer.appendInt32(-1955338397) + } + + break + case .privacyValueDisallowChatParticipants(let chats): + if boxed { + buffer.appendInt32(1103656293) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .privacyValueDisallowContacts: + if boxed { + buffer.appendInt32(-125240806) + } + + break + case .privacyValueDisallowUsers(let users): + if boxed { + buffer.appendInt32(-463335103) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + serializeInt64(item, buffer: buffer, boxed: false) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {bot!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {premium!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .requestPeerTypeBroadcast(let flags, let hasUsername, let userAdminRights, let botAdminRights): - return ("requestPeerTypeBroadcast", [("flags", flags as Any), ("hasUsername", hasUsername as Any), ("userAdminRights", userAdminRights as Any), ("botAdminRights", botAdminRights as Any)]) - case .requestPeerTypeChat(let flags, let hasUsername, let forum, let userAdminRights, let botAdminRights): - return ("requestPeerTypeChat", [("flags", flags as Any), ("hasUsername", hasUsername as Any), ("forum", forum as Any), ("userAdminRights", userAdminRights as Any), ("botAdminRights", botAdminRights as Any)]) - case .requestPeerTypeUser(let flags, let bot, let premium): - return ("requestPeerTypeUser", [("flags", flags as Any), ("bot", bot as Any), ("premium", premium as Any)]) - } - } - - public static func parse_requestPeerTypeBroadcast(_ reader: BufferReader) -> RequestPeerType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _3: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } } - var _4: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } } + case .privacyValueAllowAll: + return ("privacyValueAllowAll", []) + case .privacyValueAllowChatParticipants(let chats): + return ("privacyValueAllowChatParticipants", [("chats", chats as Any)]) + case .privacyValueAllowCloseFriends: + return ("privacyValueAllowCloseFriends", []) + case .privacyValueAllowContacts: + return ("privacyValueAllowContacts", []) + case .privacyValueAllowPremium: + return ("privacyValueAllowPremium", []) + case .privacyValueAllowUsers(let users): + return ("privacyValueAllowUsers", [("users", users as Any)]) + case .privacyValueDisallowAll: + return ("privacyValueDisallowAll", []) + case .privacyValueDisallowChatParticipants(let chats): + return ("privacyValueDisallowChatParticipants", [("chats", chats as Any)]) + case .privacyValueDisallowContacts: + return ("privacyValueDisallowContacts", []) + case .privacyValueDisallowUsers(let users): + return ("privacyValueDisallowUsers", [("users", users as Any)]) + } + } + + public static func parse_privacyValueAllowAll(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueAllowAll + } + public static func parse_privacyValueAllowChatParticipants(_ reader: BufferReader) -> PrivacyRule? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.RequestPeerType.requestPeerTypeBroadcast(flags: _1!, hasUsername: _2, userAdminRights: _3, botAdminRights: _4) + if _c1 { + return Api.PrivacyRule.privacyValueAllowChatParticipants(chats: _1!) } else { return nil } } - public static func parse_requestPeerTypeChat(_ reader: BufferReader) -> RequestPeerType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _3: Api.Bool? - if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _4: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } } - var _5: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } } + public static func parse_privacyValueAllowCloseFriends(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueAllowCloseFriends + } + public static func parse_privacyValueAllowContacts(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueAllowContacts + } + public static func parse_privacyValueAllowPremium(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueAllowPremium + } + public static func parse_privacyValueAllowUsers(_ reader: BufferReader) -> PrivacyRule? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 4) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.RequestPeerType.requestPeerTypeChat(flags: _1!, hasUsername: _2, forum: _3, userAdminRights: _4, botAdminRights: _5) + if _c1 { + return Api.PrivacyRule.privacyValueAllowUsers(users: _1!) } else { return nil } } - public static func parse_requestPeerTypeUser(_ reader: BufferReader) -> RequestPeerType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _3: Api.Bool? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } } + public static func parse_privacyValueDisallowAll(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueDisallowAll + } + public static func parse_privacyValueDisallowChatParticipants(_ reader: BufferReader) -> PrivacyRule? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.RequestPeerType.requestPeerTypeUser(flags: _1!, bot: _2, premium: _3) + if _c1 { + return Api.PrivacyRule.privacyValueDisallowChatParticipants(chats: _1!) + } + else { + return nil + } + } + public static func parse_privacyValueDisallowContacts(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueDisallowContacts + } + public static func parse_privacyValueDisallowUsers(_ reader: BufferReader) -> PrivacyRule? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.PrivacyRule.privacyValueDisallowUsers(users: _1!) } else { return nil @@ -129,127 +191,63 @@ public extension Api { } } public extension Api { - enum RequestedPeer: TypeConstructorDescription { - case requestedPeerChannel(flags: Int32, channelId: Int64, title: String?, username: String?, photo: Api.Photo?) - case requestedPeerChat(flags: Int32, chatId: Int64, title: String?, photo: Api.Photo?) - case requestedPeerUser(flags: Int32, userId: Int64, firstName: String?, lastName: String?, username: String?, photo: Api.Photo?) + indirect enum PublicForward: TypeConstructorDescription { + case publicForwardMessage(message: Api.Message) + case publicForwardStory(peer: Api.Peer, story: Api.StoryItem) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .requestedPeerChannel(let flags, let channelId, let title, let username, let photo): + case .publicForwardMessage(let message): if boxed { - buffer.appendInt32(-1952185372) + buffer.appendInt32(32685898) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(username!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} + message.serialize(buffer, true) break - case .requestedPeerChat(let flags, let chatId, let title, let photo): + case .publicForwardStory(let peer, let story): if boxed { - buffer.appendInt32(1929860175) + buffer.appendInt32(-302797360) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} - break - case .requestedPeerUser(let flags, let userId, let firstName, let lastName, let username, let photo): - if boxed { - buffer.appendInt32(-701500310) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(firstName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeString(lastName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(username!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} + peer.serialize(buffer, true) + story.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .requestedPeerChannel(let flags, let channelId, let title, let username, let photo): - return ("requestedPeerChannel", [("flags", flags as Any), ("channelId", channelId as Any), ("title", title as Any), ("username", username as Any), ("photo", photo as Any)]) - case .requestedPeerChat(let flags, let chatId, let title, let photo): - return ("requestedPeerChat", [("flags", flags as Any), ("chatId", chatId as Any), ("title", title as Any), ("photo", photo as Any)]) - case .requestedPeerUser(let flags, let userId, let firstName, let lastName, let username, let photo): - return ("requestedPeerUser", [("flags", flags as Any), ("userId", userId as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("username", username as Any), ("photo", photo as Any)]) + case .publicForwardMessage(let message): + return ("publicForwardMessage", [("message", message as Any)]) + case .publicForwardStory(let peer, let story): + return ("publicForwardStory", [("peer", peer as Any), ("story", story as Any)]) } } - public static func parse_requestedPeerChannel(_ reader: BufferReader) -> RequestedPeer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - var _5: Api.Photo? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Photo - } } + public static func parse_publicForwardMessage(_ reader: BufferReader) -> PublicForward? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.RequestedPeer.requestedPeerChannel(flags: _1!, channelId: _2!, title: _3, username: _4, photo: _5) + if _c1 { + return Api.PublicForward.publicForwardMessage(message: _1!) } else { return nil } } - public static func parse_requestedPeerChat(_ reader: BufferReader) -> RequestedPeer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: Api.Photo? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Photo - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.RequestedPeer.requestedPeerChat(flags: _1!, chatId: _2!, title: _3, photo: _4) + public static func parse_publicForwardStory(_ reader: BufferReader) -> PublicForward? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer } - else { - return nil + var _2: Api.StoryItem? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StoryItem } - } - public static func parse_requestedPeerUser(_ reader: BufferReader) -> RequestedPeer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } - var _6: Api.Photo? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Photo - } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.RequestedPeer.requestedPeerUser(flags: _1!, userId: _2!, firstName: _3, lastName: _4, username: _5, photo: _6) + if _c1 && _c2 { + return Api.PublicForward.publicForwardStory(peer: _1!, story: _2!) } else { return nil @@ -259,41 +257,45 @@ public extension Api { } } public extension Api { - enum RestrictionReason: TypeConstructorDescription { - case restrictionReason(platform: String, reason: String, text: String) + enum QuickReply: TypeConstructorDescription { + case quickReply(shortcutId: Int32, shortcut: String, topMessage: Int32, count: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .restrictionReason(let platform, let reason, let text): + case .quickReply(let shortcutId, let shortcut, let topMessage, let count): if boxed { - buffer.appendInt32(-797791052) + buffer.appendInt32(110563371) } - serializeString(platform, buffer: buffer, boxed: false) - serializeString(reason, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + serializeString(shortcut, buffer: buffer, boxed: false) + serializeInt32(topMessage, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .restrictionReason(let platform, let reason, let text): - return ("restrictionReason", [("platform", platform as Any), ("reason", reason as Any), ("text", text as Any)]) + case .quickReply(let shortcutId, let shortcut, let topMessage, let count): + return ("quickReply", [("shortcutId", shortcutId as Any), ("shortcut", shortcut as Any), ("topMessage", topMessage as Any), ("count", count as Any)]) } } - public static func parse_restrictionReason(_ reader: BufferReader) -> RestrictionReason? { - var _1: String? - _1 = parseString(reader) + public static func parse_quickReply(_ reader: BufferReader) -> QuickReply? { + var _1: Int32? + _1 = reader.readInt32() var _2: String? _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.RestrictionReason.restrictionReason(platform: _1!, reason: _2!, text: _3!) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.QuickReply.quickReply(shortcutId: _1!, shortcut: _2!, topMessage: _3!, count: _4!) } else { return nil @@ -303,431 +305,115 @@ public extension Api { } } public extension Api { - indirect enum RichText: TypeConstructorDescription { - case textAnchor(text: Api.RichText, name: String) - case textBold(text: Api.RichText) - case textConcat(texts: [Api.RichText]) - case textEmail(text: Api.RichText, email: String) - case textEmpty - case textFixed(text: Api.RichText) - case textImage(documentId: Int64, w: Int32, h: Int32) - case textItalic(text: Api.RichText) - case textMarked(text: Api.RichText) - case textPhone(text: Api.RichText, phone: String) - case textPlain(text: String) - case textStrike(text: Api.RichText) - case textSubscript(text: Api.RichText) - case textSuperscript(text: Api.RichText) - case textUnderline(text: Api.RichText) - case textUrl(text: Api.RichText, url: String, webpageId: Int64) + enum Reaction: TypeConstructorDescription { + case reactionCustomEmoji(documentId: Int64) + case reactionEmoji(emoticon: String) + case reactionEmpty public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .textAnchor(let text, let name): - if boxed { - buffer.appendInt32(894777186) - } - text.serialize(buffer, true) - serializeString(name, buffer: buffer, boxed: false) - break - case .textBold(let text): - if boxed { - buffer.appendInt32(1730456516) - } - text.serialize(buffer, true) - break - case .textConcat(let texts): - if boxed { - buffer.appendInt32(2120376535) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(texts.count)) - for item in texts { - item.serialize(buffer, true) - } - break - case .textEmail(let text, let email): - if boxed { - buffer.appendInt32(-564523562) - } - text.serialize(buffer, true) - serializeString(email, buffer: buffer, boxed: false) - break - case .textEmpty: - if boxed { - buffer.appendInt32(-599948721) - } - - break - case .textFixed(let text): + case .reactionCustomEmoji(let documentId): if boxed { - buffer.appendInt32(1816074681) - } - text.serialize(buffer, true) - break - case .textImage(let documentId, let w, let h): - if boxed { - buffer.appendInt32(136105807) + buffer.appendInt32(-1992950669) } serializeInt64(documentId, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - break - case .textItalic(let text): - if boxed { - buffer.appendInt32(-653089380) - } - text.serialize(buffer, true) - break - case .textMarked(let text): - if boxed { - buffer.appendInt32(55281185) - } - text.serialize(buffer, true) - break - case .textPhone(let text, let phone): - if boxed { - buffer.appendInt32(483104362) - } - text.serialize(buffer, true) - serializeString(phone, buffer: buffer, boxed: false) - break - case .textPlain(let text): - if boxed { - buffer.appendInt32(1950782688) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .textStrike(let text): - if boxed { - buffer.appendInt32(-1678197867) - } - text.serialize(buffer, true) - break - case .textSubscript(let text): - if boxed { - buffer.appendInt32(-311786236) - } - text.serialize(buffer, true) break - case .textSuperscript(let text): + case .reactionEmoji(let emoticon): if boxed { - buffer.appendInt32(-939827711) + buffer.appendInt32(455247544) } - text.serialize(buffer, true) + serializeString(emoticon, buffer: buffer, boxed: false) break - case .textUnderline(let text): + case .reactionEmpty: if boxed { - buffer.appendInt32(-1054465340) + buffer.appendInt32(2046153753) } - text.serialize(buffer, true) - break - case .textUrl(let text, let url, let webpageId): - if boxed { - buffer.appendInt32(1009288385) - } - text.serialize(buffer, true) - serializeString(url, buffer: buffer, boxed: false) - serializeInt64(webpageId, buffer: buffer, boxed: false) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .textAnchor(let text, let name): - return ("textAnchor", [("text", text as Any), ("name", name as Any)]) - case .textBold(let text): - return ("textBold", [("text", text as Any)]) - case .textConcat(let texts): - return ("textConcat", [("texts", texts as Any)]) - case .textEmail(let text, let email): - return ("textEmail", [("text", text as Any), ("email", email as Any)]) - case .textEmpty: - return ("textEmpty", []) - case .textFixed(let text): - return ("textFixed", [("text", text as Any)]) - case .textImage(let documentId, let w, let h): - return ("textImage", [("documentId", documentId as Any), ("w", w as Any), ("h", h as Any)]) - case .textItalic(let text): - return ("textItalic", [("text", text as Any)]) - case .textMarked(let text): - return ("textMarked", [("text", text as Any)]) - case .textPhone(let text, let phone): - return ("textPhone", [("text", text as Any), ("phone", phone as Any)]) - case .textPlain(let text): - return ("textPlain", [("text", text as Any)]) - case .textStrike(let text): - return ("textStrike", [("text", text as Any)]) - case .textSubscript(let text): - return ("textSubscript", [("text", text as Any)]) - case .textSuperscript(let text): - return ("textSuperscript", [("text", text as Any)]) - case .textUnderline(let text): - return ("textUnderline", [("text", text as Any)]) - case .textUrl(let text, let url, let webpageId): - return ("textUrl", [("text", text as Any), ("url", url as Any), ("webpageId", webpageId as Any)]) + case .reactionCustomEmoji(let documentId): + return ("reactionCustomEmoji", [("documentId", documentId as Any)]) + case .reactionEmoji(let emoticon): + return ("reactionEmoji", [("emoticon", emoticon as Any)]) + case .reactionEmpty: + return ("reactionEmpty", []) } } - public static func parse_textAnchor(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RichText.textAnchor(text: _1!, name: _2!) - } - else { - return nil - } - } - public static func parse_textBold(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textBold(text: _1!) - } - else { - return nil - } - } - public static func parse_textConcat(_ reader: BufferReader) -> RichText? { - var _1: [Api.RichText]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RichText.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textConcat(texts: _1!) - } - else { - return nil - } - } - public static func parse_textEmail(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RichText.textEmail(text: _1!, email: _2!) - } - else { - return nil - } - } - public static func parse_textEmpty(_ reader: BufferReader) -> RichText? { - return Api.RichText.textEmpty - } - public static func parse_textFixed(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textFixed(text: _1!) - } - else { - return nil - } - } - public static func parse_textImage(_ reader: BufferReader) -> RichText? { + public static func parse_reactionCustomEmoji(_ reader: BufferReader) -> Reaction? { var _1: Int64? _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.RichText.textImage(documentId: _1!, w: _2!, h: _3!) - } - else { - return nil - } - } - public static func parse_textItalic(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } let _c1 = _1 != nil if _c1 { - return Api.RichText.textItalic(text: _1!) + return Api.Reaction.reactionCustomEmoji(documentId: _1!) } else { return nil } } - public static func parse_textMarked(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textMarked(text: _1!) - } - else { - return nil - } - } - public static func parse_textPhone(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RichText.textPhone(text: _1!, phone: _2!) - } - else { - return nil - } - } - public static func parse_textPlain(_ reader: BufferReader) -> RichText? { + public static func parse_reactionEmoji(_ reader: BufferReader) -> Reaction? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.RichText.textPlain(text: _1!) - } - else { - return nil - } - } - public static func parse_textStrike(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textStrike(text: _1!) - } - else { - return nil - } - } - public static func parse_textSubscript(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textSubscript(text: _1!) - } - else { - return nil - } - } - public static func parse_textSuperscript(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textSuperscript(text: _1!) - } - else { - return nil - } - } - public static func parse_textUnderline(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textUnderline(text: _1!) - } - else { - return nil - } - } - public static func parse_textUrl(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.RichText.textUrl(text: _1!, url: _2!, webpageId: _3!) + if _c1 { + return Api.Reaction.reactionEmoji(emoticon: _1!) } else { return nil } } + public static func parse_reactionEmpty(_ reader: BufferReader) -> Reaction? { + return Api.Reaction.reactionEmpty + } } } public extension Api { - enum SavedContact: TypeConstructorDescription { - case savedPhoneContact(phone: String, firstName: String, lastName: String, date: Int32) + enum ReactionCount: TypeConstructorDescription { + case reactionCount(flags: Int32, chosenOrder: Int32?, reaction: Api.Reaction, count: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedPhoneContact(let phone, let firstName, let lastName, let date): + case .reactionCount(let flags, let chosenOrder, let reaction, let count): if boxed { - buffer.appendInt32(289586518) + buffer.appendInt32(-1546531968) } - serializeString(phone, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(chosenOrder!, buffer: buffer, boxed: false)} + reaction.serialize(buffer, true) + serializeInt32(count, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedPhoneContact(let phone, let firstName, let lastName, let date): - return ("savedPhoneContact", [("phone", phone as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("date", date as Any)]) + case .reactionCount(let flags, let chosenOrder, let reaction, let count): + return ("reactionCount", [("flags", flags as Any), ("chosenOrder", chosenOrder as Any), ("reaction", reaction as Any), ("count", count as Any)]) } } - public static func parse_savedPhoneContact(_ reader: BufferReader) -> SavedContact? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) + public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _3: Api.Reaction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Reaction + } var _4: Int32? _4 = reader.readInt32() let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.SavedContact.savedPhoneContact(phone: _1!, firstName: _2!, lastName: _3!, date: _4!) + return Api.ReactionCount.reactionCount(flags: _1!, chosenOrder: _2, reaction: _3!, count: _4!) } else { return nil @@ -737,93 +423,97 @@ public extension Api { } } public extension Api { - enum SavedDialog: TypeConstructorDescription { - case savedDialog(flags: Int32, peer: Api.Peer, topMessage: Int32) + enum ReactionNotificationsFrom: TypeConstructorDescription { + case reactionNotificationsFromAll + case reactionNotificationsFromContacts public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedDialog(let flags, let peer, let topMessage): + case .reactionNotificationsFromAll: if boxed { - buffer.appendInt32(-1115174036) + buffer.appendInt32(1268654752) } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(topMessage, buffer: buffer, boxed: false) + + break + case .reactionNotificationsFromContacts: + if boxed { + buffer.appendInt32(-1161583078) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedDialog(let flags, let peer, let topMessage): - return ("savedDialog", [("flags", flags as Any), ("peer", peer as Any), ("topMessage", topMessage as Any)]) + case .reactionNotificationsFromAll: + return ("reactionNotificationsFromAll", []) + case .reactionNotificationsFromContacts: + return ("reactionNotificationsFromContacts", []) } } - public static func parse_savedDialog(_ reader: BufferReader) -> SavedDialog? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SavedDialog.savedDialog(flags: _1!, peer: _2!, topMessage: _3!) - } - else { - return nil - } + public static func parse_reactionNotificationsFromAll(_ reader: BufferReader) -> ReactionNotificationsFrom? { + return Api.ReactionNotificationsFrom.reactionNotificationsFromAll + } + public static func parse_reactionNotificationsFromContacts(_ reader: BufferReader) -> ReactionNotificationsFrom? { + return Api.ReactionNotificationsFrom.reactionNotificationsFromContacts } } } public extension Api { - enum SavedReactionTag: TypeConstructorDescription { - case savedReactionTag(flags: Int32, reaction: Api.Reaction, title: String?, count: Int32) + enum ReactionsNotifySettings: TypeConstructorDescription { + case reactionsNotifySettings(flags: Int32, messagesNotifyFrom: Api.ReactionNotificationsFrom?, storiesNotifyFrom: Api.ReactionNotificationsFrom?, sound: Api.NotificationSound, showPreviews: Api.Bool) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedReactionTag(let flags, let reaction, let title, let count): + case .reactionsNotifySettings(let flags, let messagesNotifyFrom, let storiesNotifyFrom, let sound, let showPreviews): if boxed { - buffer.appendInt32(-881854424) + buffer.appendInt32(1457736048) } serializeInt32(flags, buffer: buffer, boxed: false) - reaction.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - serializeInt32(count, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {messagesNotifyFrom!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {storiesNotifyFrom!.serialize(buffer, true)} + sound.serialize(buffer, true) + showPreviews.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedReactionTag(let flags, let reaction, let title, let count): - return ("savedReactionTag", [("flags", flags as Any), ("reaction", reaction as Any), ("title", title as Any), ("count", count as Any)]) + case .reactionsNotifySettings(let flags, let messagesNotifyFrom, let storiesNotifyFrom, let sound, let showPreviews): + return ("reactionsNotifySettings", [("flags", flags as Any), ("messagesNotifyFrom", messagesNotifyFrom as Any), ("storiesNotifyFrom", storiesNotifyFrom as Any), ("sound", sound as Any), ("showPreviews", showPreviews as Any)]) } } - public static func parse_savedReactionTag(_ reader: BufferReader) -> SavedReactionTag? { + public static func parse_reactionsNotifySettings(_ reader: BufferReader) -> ReactionsNotifySettings? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Reaction? + var _2: Api.ReactionNotificationsFrom? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom + } } + var _3: Api.ReactionNotificationsFrom? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom + } } + var _4: Api.NotificationSound? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Reaction + _4 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } + var _5: Api.Bool? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Bool } - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: Int32? - _4 = reader.readInt32() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.SavedReactionTag.savedReactionTag(flags: _1!, reaction: _2!, title: _3, count: _4!) + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.ReactionsNotifySettings.reactionsNotifySettings(flags: _1!, messagesNotifyFrom: _2, storiesNotifyFrom: _3, sound: _4!, showPreviews: _5!) } else { return nil @@ -833,45 +523,37 @@ public extension Api { } } public extension Api { - enum SearchResultsCalendarPeriod: TypeConstructorDescription { - case searchResultsCalendarPeriod(date: Int32, minMsgId: Int32, maxMsgId: Int32, count: Int32) + enum ReadParticipantDate: TypeConstructorDescription { + case readParticipantDate(userId: Int64, date: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .searchResultsCalendarPeriod(let date, let minMsgId, let maxMsgId, let count): + case .readParticipantDate(let userId, let date): if boxed { - buffer.appendInt32(-911191137) + buffer.appendInt32(1246753138) } + serializeInt64(userId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(minMsgId, buffer: buffer, boxed: false) - serializeInt32(maxMsgId, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .searchResultsCalendarPeriod(let date, let minMsgId, let maxMsgId, let count): - return ("searchResultsCalendarPeriod", [("date", date as Any), ("minMsgId", minMsgId as Any), ("maxMsgId", maxMsgId as Any), ("count", count as Any)]) + case .readParticipantDate(let userId, let date): + return ("readParticipantDate", [("userId", userId as Any), ("date", date as Any)]) } } - public static func parse_searchResultsCalendarPeriod(_ reader: BufferReader) -> SearchResultsCalendarPeriod? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_readParticipantDate(_ reader: BufferReader) -> ReadParticipantDate? { + var _1: Int64? + _1 = reader.readInt64() var _2: Int32? _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.SearchResultsCalendarPeriod.searchResultsCalendarPeriod(date: _1!, minMsgId: _2!, maxMsgId: _3!, count: _4!) + if _c1 && _c2 { + return Api.ReadParticipantDate.readParticipantDate(userId: _1!, date: _2!) } else { return nil @@ -881,41 +563,37 @@ public extension Api { } } public extension Api { - enum SearchResultsPosition: TypeConstructorDescription { - case searchResultPosition(msgId: Int32, date: Int32, offset: Int32) + enum ReceivedNotifyMessage: TypeConstructorDescription { + case receivedNotifyMessage(id: Int32, flags: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .searchResultPosition(let msgId, let date, let offset): + case .receivedNotifyMessage(let id, let flags): if boxed { - buffer.appendInt32(2137295719) + buffer.appendInt32(-1551583367) } - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .searchResultPosition(let msgId, let date, let offset): - return ("searchResultPosition", [("msgId", msgId as Any), ("date", date as Any), ("offset", offset as Any)]) + case .receivedNotifyMessage(let id, let flags): + return ("receivedNotifyMessage", [("id", id as Any), ("flags", flags as Any)]) } } - public static func parse_searchResultPosition(_ reader: BufferReader) -> SearchResultsPosition? { + public static func parse_receivedNotifyMessage(_ reader: BufferReader) -> ReceivedNotifyMessage? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SearchResultsPosition.searchResultPosition(msgId: _1!, date: _2!, offset: _3!) + if _c1 && _c2 { + return Api.ReceivedNotifyMessage.receivedNotifyMessage(id: _1!, flags: _2!) } else { return nil @@ -925,41 +603,133 @@ public extension Api { } } public extension Api { - enum SecureCredentialsEncrypted: TypeConstructorDescription { - case secureCredentialsEncrypted(data: Buffer, hash: Buffer, secret: Buffer) + indirect enum RecentMeUrl: TypeConstructorDescription { + case recentMeUrlChat(url: String, chatId: Int64) + case recentMeUrlChatInvite(url: String, chatInvite: Api.ChatInvite) + case recentMeUrlStickerSet(url: String, set: Api.StickerSetCovered) + case recentMeUrlUnknown(url: String) + case recentMeUrlUser(url: String, userId: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureCredentialsEncrypted(let data, let hash, let secret): + case .recentMeUrlChat(let url, let chatId): + if boxed { + buffer.appendInt32(-1294306862) + } + serializeString(url, buffer: buffer, boxed: false) + serializeInt64(chatId, buffer: buffer, boxed: false) + break + case .recentMeUrlChatInvite(let url, let chatInvite): + if boxed { + buffer.appendInt32(-347535331) + } + serializeString(url, buffer: buffer, boxed: false) + chatInvite.serialize(buffer, true) + break + case .recentMeUrlStickerSet(let url, let set): + if boxed { + buffer.appendInt32(-1140172836) + } + serializeString(url, buffer: buffer, boxed: false) + set.serialize(buffer, true) + break + case .recentMeUrlUnknown(let url): + if boxed { + buffer.appendInt32(1189204285) + } + serializeString(url, buffer: buffer, boxed: false) + break + case .recentMeUrlUser(let url, let userId): if boxed { - buffer.appendInt32(871426631) + buffer.appendInt32(-1188296222) } - serializeBytes(data, buffer: buffer, boxed: false) - serializeBytes(hash, buffer: buffer, boxed: false) - serializeBytes(secret, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .secureCredentialsEncrypted(let data, let hash, let secret): - return ("secureCredentialsEncrypted", [("data", data as Any), ("hash", hash as Any), ("secret", secret as Any)]) + case .recentMeUrlChat(let url, let chatId): + return ("recentMeUrlChat", [("url", url as Any), ("chatId", chatId as Any)]) + case .recentMeUrlChatInvite(let url, let chatInvite): + return ("recentMeUrlChatInvite", [("url", url as Any), ("chatInvite", chatInvite as Any)]) + case .recentMeUrlStickerSet(let url, let set): + return ("recentMeUrlStickerSet", [("url", url as Any), ("set", set as Any)]) + case .recentMeUrlUnknown(let url): + return ("recentMeUrlUnknown", [("url", url as Any)]) + case .recentMeUrlUser(let url, let userId): + return ("recentMeUrlUser", [("url", url as Any), ("userId", userId as Any)]) } } - public static func parse_secureCredentialsEncrypted(_ reader: BufferReader) -> SecureCredentialsEncrypted? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Buffer? - _3 = parseBytes(reader) + public static func parse_recentMeUrlChat(_ reader: BufferReader) -> RecentMeUrl? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureCredentialsEncrypted.secureCredentialsEncrypted(data: _1!, hash: _2!, secret: _3!) + if _c1 && _c2 { + return Api.RecentMeUrl.recentMeUrlChat(url: _1!, chatId: _2!) + } + else { + return nil + } + } + public static func parse_recentMeUrlChatInvite(_ reader: BufferReader) -> RecentMeUrl? { + var _1: String? + _1 = parseString(reader) + var _2: Api.ChatInvite? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ChatInvite + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RecentMeUrl.recentMeUrlChatInvite(url: _1!, chatInvite: _2!) + } + else { + return nil + } + } + public static func parse_recentMeUrlStickerSet(_ reader: BufferReader) -> RecentMeUrl? { + var _1: String? + _1 = parseString(reader) + var _2: Api.StickerSetCovered? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StickerSetCovered + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RecentMeUrl.recentMeUrlStickerSet(url: _1!, set: _2!) + } + else { + return nil + } + } + public static func parse_recentMeUrlUnknown(_ reader: BufferReader) -> RecentMeUrl? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.RecentMeUrl.recentMeUrlUnknown(url: _1!) + } + else { + return nil + } + } + public static func parse_recentMeUrlUser(_ reader: BufferReader) -> RecentMeUrl? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RecentMeUrl.recentMeUrlUser(url: _1!, userId: _2!) } else { return nil @@ -969,41 +739,117 @@ public extension Api { } } public extension Api { - enum SecureData: TypeConstructorDescription { - case secureData(data: Buffer, dataHash: Buffer, secret: Buffer) + enum ReplyMarkup: TypeConstructorDescription { + case replyInlineMarkup(rows: [Api.KeyboardButtonRow]) + case replyKeyboardForceReply(flags: Int32, placeholder: String?) + case replyKeyboardHide(flags: Int32) + case replyKeyboardMarkup(flags: Int32, rows: [Api.KeyboardButtonRow], placeholder: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureData(let data, let dataHash, let secret): + case .replyInlineMarkup(let rows): + if boxed { + buffer.appendInt32(1218642516) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(rows.count)) + for item in rows { + item.serialize(buffer, true) + } + break + case .replyKeyboardForceReply(let flags, let placeholder): + if boxed { + buffer.appendInt32(-2035021048) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {serializeString(placeholder!, buffer: buffer, boxed: false)} + break + case .replyKeyboardHide(let flags): if boxed { - buffer.appendInt32(-1964327229) + buffer.appendInt32(-1606526075) } - serializeBytes(data, buffer: buffer, boxed: false) - serializeBytes(dataHash, buffer: buffer, boxed: false) - serializeBytes(secret, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + break + case .replyKeyboardMarkup(let flags, let rows, let placeholder): + if boxed { + buffer.appendInt32(-2049074735) + } + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(rows.count)) + for item in rows { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 {serializeString(placeholder!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .secureData(let data, let dataHash, let secret): - return ("secureData", [("data", data as Any), ("dataHash", dataHash as Any), ("secret", secret as Any)]) + case .replyInlineMarkup(let rows): + return ("replyInlineMarkup", [("rows", rows as Any)]) + case .replyKeyboardForceReply(let flags, let placeholder): + return ("replyKeyboardForceReply", [("flags", flags as Any), ("placeholder", placeholder as Any)]) + case .replyKeyboardHide(let flags): + return ("replyKeyboardHide", [("flags", flags as Any)]) + case .replyKeyboardMarkup(let flags, let rows, let placeholder): + return ("replyKeyboardMarkup", [("flags", flags as Any), ("rows", rows as Any), ("placeholder", placeholder as Any)]) } } - public static func parse_secureData(_ reader: BufferReader) -> SecureData? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Buffer? - _3 = parseBytes(reader) + public static func parse_replyInlineMarkup(_ reader: BufferReader) -> ReplyMarkup? { + var _1: [Api.KeyboardButtonRow]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButtonRow.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.ReplyMarkup.replyInlineMarkup(rows: _1!) + } + else { + return nil + } + } + public static func parse_replyKeyboardForceReply(_ reader: BufferReader) -> ReplyMarkup? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 3) != 0 {_2 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil + if _c1 && _c2 { + return Api.ReplyMarkup.replyKeyboardForceReply(flags: _1!, placeholder: _2) + } + else { + return nil + } + } + public static func parse_replyKeyboardHide(_ reader: BufferReader) -> ReplyMarkup? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.ReplyMarkup.replyKeyboardHide(flags: _1!) + } + else { + return nil + } + } + public static func parse_replyKeyboardMarkup(_ reader: BufferReader) -> ReplyMarkup? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.KeyboardButtonRow]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButtonRow.self) + } + var _3: String? + if Int(_1!) & Int(1 << 3) != 0 {_3 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil + let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil if _c1 && _c2 && _c3 { - return Api.SecureData.secureData(data: _1!, dataHash: _2!, secret: _3!) + return Api.ReplyMarkup.replyKeyboardMarkup(flags: _1!, rows: _2!, placeholder: _3) } else { return nil @@ -1013,27 +859,77 @@ public extension Api { } } public extension Api { - enum SecureFile: TypeConstructorDescription { - case secureFile(id: Int64, accessHash: Int64, size: Int64, dcId: Int32, date: Int32, fileHash: Buffer, secret: Buffer) - case secureFileEmpty + enum ReportReason: TypeConstructorDescription { + case inputReportReasonChildAbuse + case inputReportReasonCopyright + case inputReportReasonFake + case inputReportReasonGeoIrrelevant + case inputReportReasonIllegalDrugs + case inputReportReasonOther + case inputReportReasonPersonalDetails + case inputReportReasonPornography + case inputReportReasonSpam + case inputReportReasonViolence public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureFile(let id, let accessHash, let size, let dcId, let date, let fileHash, let secret): + case .inputReportReasonChildAbuse: if boxed { - buffer.appendInt32(2097791614) + buffer.appendInt32(-1376497949) } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt64(size, buffer: buffer, boxed: false) - serializeInt32(dcId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeBytes(fileHash, buffer: buffer, boxed: false) - serializeBytes(secret, buffer: buffer, boxed: false) + + break + case .inputReportReasonCopyright: + if boxed { + buffer.appendInt32(-1685456582) + } + + break + case .inputReportReasonFake: + if boxed { + buffer.appendInt32(-170010905) + } + + break + case .inputReportReasonGeoIrrelevant: + if boxed { + buffer.appendInt32(-606798099) + } + + break + case .inputReportReasonIllegalDrugs: + if boxed { + buffer.appendInt32(177124030) + } + + break + case .inputReportReasonOther: + if boxed { + buffer.appendInt32(-1041980751) + } + + break + case .inputReportReasonPersonalDetails: + if boxed { + buffer.appendInt32(-1631091139) + } + + break + case .inputReportReasonPornography: + if boxed { + buffer.appendInt32(777640226) + } + + break + case .inputReportReasonSpam: + if boxed { + buffer.appendInt32(1490799288) + } + break - case .secureFileEmpty: + case .inputReportReasonViolence: if boxed { - buffer.appendInt32(1679398724) + buffer.appendInt32(505595789) } break @@ -1042,44 +938,58 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .secureFile(let id, let accessHash, let size, let dcId, let date, let fileHash, let secret): - return ("secureFile", [("id", id as Any), ("accessHash", accessHash as Any), ("size", size as Any), ("dcId", dcId as Any), ("date", date as Any), ("fileHash", fileHash as Any), ("secret", secret as Any)]) - case .secureFileEmpty: - return ("secureFileEmpty", []) - } - } - - public static func parse_secureFile(_ reader: BufferReader) -> SecureFile? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Buffer? - _6 = parseBytes(reader) - var _7: Buffer? - _7 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.SecureFile.secureFile(id: _1!, accessHash: _2!, size: _3!, dcId: _4!, date: _5!, fileHash: _6!, secret: _7!) - } - else { - return nil - } - } - public static func parse_secureFileEmpty(_ reader: BufferReader) -> SecureFile? { - return Api.SecureFile.secureFileEmpty + case .inputReportReasonChildAbuse: + return ("inputReportReasonChildAbuse", []) + case .inputReportReasonCopyright: + return ("inputReportReasonCopyright", []) + case .inputReportReasonFake: + return ("inputReportReasonFake", []) + case .inputReportReasonGeoIrrelevant: + return ("inputReportReasonGeoIrrelevant", []) + case .inputReportReasonIllegalDrugs: + return ("inputReportReasonIllegalDrugs", []) + case .inputReportReasonOther: + return ("inputReportReasonOther", []) + case .inputReportReasonPersonalDetails: + return ("inputReportReasonPersonalDetails", []) + case .inputReportReasonPornography: + return ("inputReportReasonPornography", []) + case .inputReportReasonSpam: + return ("inputReportReasonSpam", []) + case .inputReportReasonViolence: + return ("inputReportReasonViolence", []) + } + } + + public static func parse_inputReportReasonChildAbuse(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonChildAbuse + } + public static func parse_inputReportReasonCopyright(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonCopyright + } + public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonFake + } + public static func parse_inputReportReasonGeoIrrelevant(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonGeoIrrelevant + } + public static func parse_inputReportReasonIllegalDrugs(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonIllegalDrugs + } + public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonOther + } + public static func parse_inputReportReasonPersonalDetails(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonPersonalDetails + } + public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonPornography + } + public static func parse_inputReportReasonSpam(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonSpam + } + public static func parse_inputReportReasonViolence(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonViolence } } diff --git a/submodules/TelegramApi/Sources/Api21.swift b/submodules/TelegramApi/Sources/Api21.swift index 3d6fc545846..83924b0d6bb 100644 --- a/submodules/TelegramApi/Sources/Api21.swift +++ b/submodules/TelegramApi/Sources/Api21.swift @@ -1,119 +1,125 @@ public extension Api { - enum SecurePasswordKdfAlgo: TypeConstructorDescription { - case securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: Buffer) - case securePasswordKdfAlgoSHA512(salt: Buffer) - case securePasswordKdfAlgoUnknown + enum RequestPeerType: TypeConstructorDescription { + case requestPeerTypeBroadcast(flags: Int32, hasUsername: Api.Bool?, userAdminRights: Api.ChatAdminRights?, botAdminRights: Api.ChatAdminRights?) + case requestPeerTypeChat(flags: Int32, hasUsername: Api.Bool?, forum: Api.Bool?, userAdminRights: Api.ChatAdminRights?, botAdminRights: Api.ChatAdminRights?) + case requestPeerTypeUser(flags: Int32, bot: Api.Bool?, premium: Api.Bool?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(let salt): + case .requestPeerTypeBroadcast(let flags, let hasUsername, let userAdminRights, let botAdminRights): if boxed { - buffer.appendInt32(-1141711456) + buffer.appendInt32(865857388) } - serializeBytes(salt, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {hasUsername!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {userAdminRights!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {botAdminRights!.serialize(buffer, true)} break - case .securePasswordKdfAlgoSHA512(let salt): + case .requestPeerTypeChat(let flags, let hasUsername, let forum, let userAdminRights, let botAdminRights): if boxed { - buffer.appendInt32(-2042159726) + buffer.appendInt32(-906990053) } - serializeBytes(salt, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {hasUsername!.serialize(buffer, true)} + if Int(flags) & Int(1 << 4) != 0 {forum!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {userAdminRights!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {botAdminRights!.serialize(buffer, true)} break - case .securePasswordKdfAlgoUnknown: + case .requestPeerTypeUser(let flags, let bot, let premium): if boxed { - buffer.appendInt32(4883767) + buffer.appendInt32(1597737472) } - + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {bot!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {premium!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(let salt): - return ("securePasswordKdfAlgoPBKDF2HMACSHA512iter100000", [("salt", salt as Any)]) - case .securePasswordKdfAlgoSHA512(let salt): - return ("securePasswordKdfAlgoSHA512", [("salt", salt as Any)]) - case .securePasswordKdfAlgoUnknown: - return ("securePasswordKdfAlgoUnknown", []) + case .requestPeerTypeBroadcast(let flags, let hasUsername, let userAdminRights, let botAdminRights): + return ("requestPeerTypeBroadcast", [("flags", flags as Any), ("hasUsername", hasUsername as Any), ("userAdminRights", userAdminRights as Any), ("botAdminRights", botAdminRights as Any)]) + case .requestPeerTypeChat(let flags, let hasUsername, let forum, let userAdminRights, let botAdminRights): + return ("requestPeerTypeChat", [("flags", flags as Any), ("hasUsername", hasUsername as Any), ("forum", forum as Any), ("userAdminRights", userAdminRights as Any), ("botAdminRights", botAdminRights as Any)]) + case .requestPeerTypeUser(let flags, let bot, let premium): + return ("requestPeerTypeUser", [("flags", flags as Any), ("bot", bot as Any), ("premium", premium as Any)]) } } - public static func parse_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { - var _1: Buffer? - _1 = parseBytes(reader) - let _c1 = _1 != nil - if _c1 { - return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: _1!) - } - else { - return nil - } - } - public static func parse_securePasswordKdfAlgoSHA512(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { - var _1: Buffer? - _1 = parseBytes(reader) + public static func parse_requestPeerTypeBroadcast(_ reader: BufferReader) -> RequestPeerType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _3: Api.ChatAdminRights? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } } + var _4: Api.ChatAdminRights? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } } let _c1 = _1 != nil - if _c1 { - return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoSHA512(salt: _1!) + let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.RequestPeerType.requestPeerTypeBroadcast(flags: _1!, hasUsername: _2, userAdminRights: _3, botAdminRights: _4) } else { return nil } } - public static func parse_securePasswordKdfAlgoUnknown(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { - return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoUnknown - } - - } -} -public extension Api { - enum SecurePlainData: TypeConstructorDescription { - case securePlainEmail(email: String) - case securePlainPhone(phone: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .securePlainEmail(let email): - if boxed { - buffer.appendInt32(569137759) - } - serializeString(email, buffer: buffer, boxed: false) - break - case .securePlainPhone(let phone): - if boxed { - buffer.appendInt32(2103482845) - } - serializeString(phone, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .securePlainEmail(let email): - return ("securePlainEmail", [("email", email as Any)]) - case .securePlainPhone(let phone): - return ("securePlainPhone", [("phone", phone as Any)]) - } - } - - public static func parse_securePlainEmail(_ reader: BufferReader) -> SecurePlainData? { - var _1: String? - _1 = parseString(reader) + public static func parse_requestPeerTypeChat(_ reader: BufferReader) -> RequestPeerType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _3: Api.Bool? + if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _4: Api.ChatAdminRights? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } } + var _5: Api.ChatAdminRights? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } } let _c1 = _1 != nil - if _c1 { - return Api.SecurePlainData.securePlainEmail(email: _1!) + let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 4) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.RequestPeerType.requestPeerTypeChat(flags: _1!, hasUsername: _2, forum: _3, userAdminRights: _4, botAdminRights: _5) } else { return nil } } - public static func parse_securePlainPhone(_ reader: BufferReader) -> SecurePlainData? { - var _1: String? - _1 = parseString(reader) + public static func parse_requestPeerTypeUser(_ reader: BufferReader) -> RequestPeerType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _3: Api.Bool? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } } let _c1 = _1 != nil - if _c1 { - return Api.SecurePlainData.securePlainPhone(phone: _1!) + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.RequestPeerType.requestPeerTypeUser(flags: _1!, bot: _2, premium: _3) } else { return nil @@ -123,111 +129,127 @@ public extension Api { } } public extension Api { - enum SecureRequiredType: TypeConstructorDescription { - case secureRequiredType(flags: Int32, type: Api.SecureValueType) - case secureRequiredTypeOneOf(types: [Api.SecureRequiredType]) + enum RequestedPeer: TypeConstructorDescription { + case requestedPeerChannel(flags: Int32, channelId: Int64, title: String?, username: String?, photo: Api.Photo?) + case requestedPeerChat(flags: Int32, chatId: Int64, title: String?, photo: Api.Photo?) + case requestedPeerUser(flags: Int32, userId: Int64, firstName: String?, lastName: String?, username: String?, photo: Api.Photo?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureRequiredType(let flags, let type): + case .requestedPeerChannel(let flags, let channelId, let title, let username, let photo): if boxed { - buffer.appendInt32(-2103600678) + buffer.appendInt32(-1952185372) } serializeInt32(flags, buffer: buffer, boxed: false) - type.serialize(buffer, true) + serializeInt64(channelId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(username!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} break - case .secureRequiredTypeOneOf(let types): + case .requestedPeerChat(let flags, let chatId, let title, let photo): if boxed { - buffer.appendInt32(41187252) + buffer.appendInt32(1929860175) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(types.count)) - for item in types { - item.serialize(buffer, true) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(chatId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} + break + case .requestedPeerUser(let flags, let userId, let firstName, let lastName, let username, let photo): + if boxed { + buffer.appendInt32(-701500310) } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(firstName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeString(lastName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(username!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .secureRequiredType(let flags, let type): - return ("secureRequiredType", [("flags", flags as Any), ("type", type as Any)]) - case .secureRequiredTypeOneOf(let types): - return ("secureRequiredTypeOneOf", [("types", types as Any)]) + case .requestedPeerChannel(let flags, let channelId, let title, let username, let photo): + return ("requestedPeerChannel", [("flags", flags as Any), ("channelId", channelId as Any), ("title", title as Any), ("username", username as Any), ("photo", photo as Any)]) + case .requestedPeerChat(let flags, let chatId, let title, let photo): + return ("requestedPeerChat", [("flags", flags as Any), ("chatId", chatId as Any), ("title", title as Any), ("photo", photo as Any)]) + case .requestedPeerUser(let flags, let userId, let firstName, let lastName, let username, let photo): + return ("requestedPeerUser", [("flags", flags as Any), ("userId", userId as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("username", username as Any), ("photo", photo as Any)]) } } - public static func parse_secureRequiredType(_ reader: BufferReader) -> SecureRequiredType? { + public static func parse_requestedPeerChannel(_ reader: BufferReader) -> RequestedPeer? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.SecureValueType? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: String? + if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } + var _5: Api.Photo? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Photo + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SecureRequiredType.secureRequiredType(flags: _1!, type: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.RequestedPeer.requestedPeerChannel(flags: _1!, channelId: _2!, title: _3, username: _4, photo: _5) } else { return nil } } - public static func parse_secureRequiredTypeOneOf(_ reader: BufferReader) -> SecureRequiredType? { - var _1: [Api.SecureRequiredType]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureRequiredType.self) - } + public static func parse_requestedPeerChat(_ reader: BufferReader) -> RequestedPeer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: Api.Photo? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Photo + } } let _c1 = _1 != nil - if _c1 { - return Api.SecureRequiredType.secureRequiredTypeOneOf(types: _1!) + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.RequestedPeer.requestedPeerChat(flags: _1!, chatId: _2!, title: _3, photo: _4) } else { return nil } } - - } -} -public extension Api { - enum SecureSecretSettings: TypeConstructorDescription { - case secureSecretSettings(secureAlgo: Api.SecurePasswordKdfAlgo, secureSecret: Buffer, secureSecretId: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .secureSecretSettings(let secureAlgo, let secureSecret, let secureSecretId): - if boxed { - buffer.appendInt32(354925740) - } - secureAlgo.serialize(buffer, true) - serializeBytes(secureSecret, buffer: buffer, boxed: false) - serializeInt64(secureSecretId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .secureSecretSettings(let secureAlgo, let secureSecret, let secureSecretId): - return ("secureSecretSettings", [("secureAlgo", secureAlgo as Any), ("secureSecret", secureSecret as Any), ("secureSecretId", secureSecretId as Any)]) - } - } - - public static func parse_secureSecretSettings(_ reader: BufferReader) -> SecureSecretSettings? { - var _1: Api.SecurePasswordKdfAlgo? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecurePasswordKdfAlgo - } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Int64? - _3 = reader.readInt64() + public static func parse_requestedPeerUser(_ reader: BufferReader) -> RequestedPeer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + var _5: String? + if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } + var _6: Api.Photo? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Photo + } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureSecretSettings.secureSecretSettings(secureAlgo: _1!, secureSecret: _2!, secureSecretId: _3!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.RequestedPeer.requestedPeerUser(flags: _1!, userId: _2!, firstName: _3, lastName: _4, username: _5, photo: _6) } else { return nil @@ -237,93 +259,41 @@ public extension Api { } } public extension Api { - enum SecureValue: TypeConstructorDescription { - case secureValue(flags: Int32, type: Api.SecureValueType, data: Api.SecureData?, frontSide: Api.SecureFile?, reverseSide: Api.SecureFile?, selfie: Api.SecureFile?, translation: [Api.SecureFile]?, files: [Api.SecureFile]?, plainData: Api.SecurePlainData?, hash: Buffer) + enum RestrictionReason: TypeConstructorDescription { + case restrictionReason(platform: String, reason: String, text: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureValue(let flags, let type, let data, let frontSide, let reverseSide, let selfie, let translation, let files, let plainData, let hash): + case .restrictionReason(let platform, let reason, let text): if boxed { - buffer.appendInt32(411017418) + buffer.appendInt32(-797791052) } - serializeInt32(flags, buffer: buffer, boxed: false) - type.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {data!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {frontSide!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {reverseSide!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {selfie!.serialize(buffer, true)} - if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(translation!.count)) - for item in translation! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(files!.count)) - for item in files! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 5) != 0 {plainData!.serialize(buffer, true)} - serializeBytes(hash, buffer: buffer, boxed: false) + serializeString(platform, buffer: buffer, boxed: false) + serializeString(reason, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .secureValue(let flags, let type, let data, let frontSide, let reverseSide, let selfie, let translation, let files, let plainData, let hash): - return ("secureValue", [("flags", flags as Any), ("type", type as Any), ("data", data as Any), ("frontSide", frontSide as Any), ("reverseSide", reverseSide as Any), ("selfie", selfie as Any), ("translation", translation as Any), ("files", files as Any), ("plainData", plainData as Any), ("hash", hash as Any)]) + case .restrictionReason(let platform, let reason, let text): + return ("restrictionReason", [("platform", platform as Any), ("reason", reason as Any), ("text", text as Any)]) } } - public static func parse_secureValue(_ reader: BufferReader) -> SecureValue? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.SecureValueType? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _3: Api.SecureData? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.SecureData - } } - var _4: Api.SecureFile? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.SecureFile - } } - var _5: Api.SecureFile? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.SecureFile - } } - var _6: Api.SecureFile? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.SecureFile - } } - var _7: [Api.SecureFile]? - if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureFile.self) - } } - var _8: [Api.SecureFile]? - if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureFile.self) - } } - var _9: Api.SecurePlainData? - if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.SecurePlainData - } } - var _10: Buffer? - _10 = parseBytes(reader) + public static func parse_restrictionReason(_ reader: BufferReader) -> RestrictionReason? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil - let _c10 = _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.SecureValue.secureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9, hash: _10!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.RestrictionReason.restrictionReason(platform: _1!, reason: _2!, text: _3!) } else { return nil @@ -333,299 +303,383 @@ public extension Api { } } public extension Api { - enum SecureValueError: TypeConstructorDescription { - case secureValueError(type: Api.SecureValueType, hash: Buffer, text: String) - case secureValueErrorData(type: Api.SecureValueType, dataHash: Buffer, field: String, text: String) - case secureValueErrorFile(type: Api.SecureValueType, fileHash: Buffer, text: String) - case secureValueErrorFiles(type: Api.SecureValueType, fileHash: [Buffer], text: String) - case secureValueErrorFrontSide(type: Api.SecureValueType, fileHash: Buffer, text: String) - case secureValueErrorReverseSide(type: Api.SecureValueType, fileHash: Buffer, text: String) - case secureValueErrorSelfie(type: Api.SecureValueType, fileHash: Buffer, text: String) - case secureValueErrorTranslationFile(type: Api.SecureValueType, fileHash: Buffer, text: String) - case secureValueErrorTranslationFiles(type: Api.SecureValueType, fileHash: [Buffer], text: String) + indirect enum RichText: TypeConstructorDescription { + case textAnchor(text: Api.RichText, name: String) + case textBold(text: Api.RichText) + case textConcat(texts: [Api.RichText]) + case textEmail(text: Api.RichText, email: String) + case textEmpty + case textFixed(text: Api.RichText) + case textImage(documentId: Int64, w: Int32, h: Int32) + case textItalic(text: Api.RichText) + case textMarked(text: Api.RichText) + case textPhone(text: Api.RichText, phone: String) + case textPlain(text: String) + case textStrike(text: Api.RichText) + case textSubscript(text: Api.RichText) + case textSuperscript(text: Api.RichText) + case textUnderline(text: Api.RichText) + case textUrl(text: Api.RichText, url: String, webpageId: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureValueError(let type, let hash, let text): + case .textAnchor(let text, let name): if boxed { - buffer.appendInt32(-2036501105) + buffer.appendInt32(894777186) } - type.serialize(buffer, true) - serializeBytes(hash, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) + text.serialize(buffer, true) + serializeString(name, buffer: buffer, boxed: false) break - case .secureValueErrorData(let type, let dataHash, let field, let text): + case .textBold(let text): if boxed { - buffer.appendInt32(-391902247) + buffer.appendInt32(1730456516) } - type.serialize(buffer, true) - serializeBytes(dataHash, buffer: buffer, boxed: false) - serializeString(field, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) + text.serialize(buffer, true) break - case .secureValueErrorFile(let type, let fileHash, let text): + case .textConcat(let texts): if boxed { - buffer.appendInt32(2054162547) + buffer.appendInt32(2120376535) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(texts.count)) + for item in texts { + item.serialize(buffer, true) } - type.serialize(buffer, true) - serializeBytes(fileHash, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) break - case .secureValueErrorFiles(let type, let fileHash, let text): + case .textEmail(let text, let email): if boxed { - buffer.appendInt32(1717706985) + buffer.appendInt32(-564523562) } - type.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(fileHash.count)) - for item in fileHash { - serializeBytes(item, buffer: buffer, boxed: false) + text.serialize(buffer, true) + serializeString(email, buffer: buffer, boxed: false) + break + case .textEmpty: + if boxed { + buffer.appendInt32(-599948721) } - serializeString(text, buffer: buffer, boxed: false) + break - case .secureValueErrorFrontSide(let type, let fileHash, let text): + case .textFixed(let text): if boxed { - buffer.appendInt32(12467706) + buffer.appendInt32(1816074681) } - type.serialize(buffer, true) - serializeBytes(fileHash, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) + text.serialize(buffer, true) break - case .secureValueErrorReverseSide(let type, let fileHash, let text): + case .textImage(let documentId, let w, let h): if boxed { - buffer.appendInt32(-2037765467) + buffer.appendInt32(136105807) } - type.serialize(buffer, true) - serializeBytes(fileHash, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) + serializeInt64(documentId, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) break - case .secureValueErrorSelfie(let type, let fileHash, let text): + case .textItalic(let text): if boxed { - buffer.appendInt32(-449327402) + buffer.appendInt32(-653089380) } - type.serialize(buffer, true) - serializeBytes(fileHash, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) + text.serialize(buffer, true) break - case .secureValueErrorTranslationFile(let type, let fileHash, let text): + case .textMarked(let text): if boxed { - buffer.appendInt32(-1592506512) + buffer.appendInt32(55281185) } - type.serialize(buffer, true) - serializeBytes(fileHash, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) + text.serialize(buffer, true) break - case .secureValueErrorTranslationFiles(let type, let fileHash, let text): + case .textPhone(let text, let phone): if boxed { - buffer.appendInt32(878931416) + buffer.appendInt32(483104362) } - type.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(fileHash.count)) - for item in fileHash { - serializeBytes(item, buffer: buffer, boxed: false) + text.serialize(buffer, true) + serializeString(phone, buffer: buffer, boxed: false) + break + case .textPlain(let text): + if boxed { + buffer.appendInt32(1950782688) } serializeString(text, buffer: buffer, boxed: false) break + case .textStrike(let text): + if boxed { + buffer.appendInt32(-1678197867) + } + text.serialize(buffer, true) + break + case .textSubscript(let text): + if boxed { + buffer.appendInt32(-311786236) + } + text.serialize(buffer, true) + break + case .textSuperscript(let text): + if boxed { + buffer.appendInt32(-939827711) + } + text.serialize(buffer, true) + break + case .textUnderline(let text): + if boxed { + buffer.appendInt32(-1054465340) + } + text.serialize(buffer, true) + break + case .textUrl(let text, let url, let webpageId): + if boxed { + buffer.appendInt32(1009288385) + } + text.serialize(buffer, true) + serializeString(url, buffer: buffer, boxed: false) + serializeInt64(webpageId, buffer: buffer, boxed: false) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .secureValueError(let type, let hash, let text): - return ("secureValueError", [("type", type as Any), ("hash", hash as Any), ("text", text as Any)]) - case .secureValueErrorData(let type, let dataHash, let field, let text): - return ("secureValueErrorData", [("type", type as Any), ("dataHash", dataHash as Any), ("field", field as Any), ("text", text as Any)]) - case .secureValueErrorFile(let type, let fileHash, let text): - return ("secureValueErrorFile", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) - case .secureValueErrorFiles(let type, let fileHash, let text): - return ("secureValueErrorFiles", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) - case .secureValueErrorFrontSide(let type, let fileHash, let text): - return ("secureValueErrorFrontSide", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) - case .secureValueErrorReverseSide(let type, let fileHash, let text): - return ("secureValueErrorReverseSide", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) - case .secureValueErrorSelfie(let type, let fileHash, let text): - return ("secureValueErrorSelfie", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) - case .secureValueErrorTranslationFile(let type, let fileHash, let text): - return ("secureValueErrorTranslationFile", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) - case .secureValueErrorTranslationFiles(let type, let fileHash, let text): - return ("secureValueErrorTranslationFiles", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) - } - } - - public static func parse_secureValueError(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? + case .textAnchor(let text, let name): + return ("textAnchor", [("text", text as Any), ("name", name as Any)]) + case .textBold(let text): + return ("textBold", [("text", text as Any)]) + case .textConcat(let texts): + return ("textConcat", [("texts", texts as Any)]) + case .textEmail(let text, let email): + return ("textEmail", [("text", text as Any), ("email", email as Any)]) + case .textEmpty: + return ("textEmpty", []) + case .textFixed(let text): + return ("textFixed", [("text", text as Any)]) + case .textImage(let documentId, let w, let h): + return ("textImage", [("documentId", documentId as Any), ("w", w as Any), ("h", h as Any)]) + case .textItalic(let text): + return ("textItalic", [("text", text as Any)]) + case .textMarked(let text): + return ("textMarked", [("text", text as Any)]) + case .textPhone(let text, let phone): + return ("textPhone", [("text", text as Any), ("phone", phone as Any)]) + case .textPlain(let text): + return ("textPlain", [("text", text as Any)]) + case .textStrike(let text): + return ("textStrike", [("text", text as Any)]) + case .textSubscript(let text): + return ("textSubscript", [("text", text as Any)]) + case .textSuperscript(let text): + return ("textSuperscript", [("text", text as Any)]) + case .textUnderline(let text): + return ("textUnderline", [("text", text as Any)]) + case .textUrl(let text, let url, let webpageId): + return ("textUrl", [("text", text as Any), ("url", url as Any), ("webpageId", webpageId as Any)]) + } + } + + public static func parse_textAnchor(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueError(type: _1!, hash: _2!, text: _3!) + if _c1 && _c2 { + return Api.RichText.textAnchor(text: _1!, name: _2!) } else { return nil } } - public static func parse_secureValueErrorData(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? + public static func parse_textBold(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.SecureValueError.secureValueErrorData(type: _1!, dataHash: _2!, field: _3!, text: _4!) + if _c1 { + return Api.RichText.textBold(text: _1!) } else { return nil } } - public static func parse_secureValueErrorFile(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + public static func parse_textConcat(_ reader: BufferReader) -> RichText? { + var _1: [Api.RichText]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RichText.self) } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorFile(type: _1!, fileHash: _2!, text: _3!) + if _c1 { + return Api.RichText.textConcat(texts: _1!) } else { return nil } } - public static func parse_secureValueErrorFiles(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? + public static func parse_textEmail(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: [Buffer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _3: String? - _3 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorFiles(type: _1!, fileHash: _2!, text: _3!) + if _c1 && _c2 { + return Api.RichText.textEmail(text: _1!, email: _2!) } else { return nil } } - public static func parse_secureValueErrorFrontSide(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? + public static func parse_textEmpty(_ reader: BufferReader) -> RichText? { + return Api.RichText.textEmpty + } + public static func parse_textFixed(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorFrontSide(type: _1!, fileHash: _2!, text: _3!) + if _c1 { + return Api.RichText.textFixed(text: _1!) } else { return nil } } - public static func parse_secureValueErrorReverseSide(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) + public static func parse_textImage(_ reader: BufferReader) -> RichText? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorReverseSide(type: _1!, fileHash: _2!, text: _3!) + return Api.RichText.textImage(documentId: _1!, w: _2!, h: _3!) } else { return nil } } - public static func parse_secureValueErrorSelfie(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? + public static func parse_textItalic(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorSelfie(type: _1!, fileHash: _2!, text: _3!) + if _c1 { + return Api.RichText.textItalic(text: _1!) } else { return nil } } - public static func parse_secureValueErrorTranslationFile(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? + public static func parse_textMarked(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textMarked(text: _1!) + } + else { + return nil + } + } + public static func parse_textPhone(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorTranslationFile(type: _1!, fileHash: _2!, text: _3!) + if _c1 && _c2 { + return Api.RichText.textPhone(text: _1!, phone: _2!) } else { return nil } } - public static func parse_secureValueErrorTranslationFiles(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? + public static func parse_textPlain(_ reader: BufferReader) -> RichText? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textPlain(text: _1!) + } + else { + return nil + } + } + public static func parse_textStrike(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _2: [Buffer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textStrike(text: _1!) + } + else { + return nil + } + } + public static func parse_textSubscript(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _3: String? - _3 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textSubscript(text: _1!) + } + else { + return nil + } + } + public static func parse_textSuperscript(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textSuperscript(text: _1!) + } + else { + return nil + } + } + public static func parse_textUnderline(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textUnderline(text: _1!) + } + else { + return nil + } + } + public static func parse_textUrl(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorTranslationFiles(type: _1!, fileHash: _2!, text: _3!) + return Api.RichText.textUrl(text: _1!, url: _2!, webpageId: _3!) } else { return nil @@ -635,39 +689,45 @@ public extension Api { } } public extension Api { - enum SecureValueHash: TypeConstructorDescription { - case secureValueHash(type: Api.SecureValueType, hash: Buffer) + enum SavedContact: TypeConstructorDescription { + case savedPhoneContact(phone: String, firstName: String, lastName: String, date: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureValueHash(let type, let hash): + case .savedPhoneContact(let phone, let firstName, let lastName, let date): if boxed { - buffer.appendInt32(-316748368) + buffer.appendInt32(289586518) } - type.serialize(buffer, true) - serializeBytes(hash, buffer: buffer, boxed: false) + serializeString(phone, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .secureValueHash(let type, let hash): - return ("secureValueHash", [("type", type as Any), ("hash", hash as Any)]) + case .savedPhoneContact(let phone, let firstName, let lastName, let date): + return ("savedPhoneContact", [("phone", phone as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("date", date as Any)]) } } - public static func parse_secureValueHash(_ reader: BufferReader) -> SecureValueHash? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: Buffer? - _2 = parseBytes(reader) + public static func parse_savedPhoneContact(_ reader: BufferReader) -> SavedContact? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SecureValueHash.secureValueHash(type: _1!, hash: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.SavedContact.savedPhoneContact(phone: _1!, firstName: _2!, lastName: _3!, date: _4!) } else { return nil @@ -677,98 +737,303 @@ public extension Api { } } public extension Api { - enum SecureValueType: TypeConstructorDescription { - case secureValueTypeAddress - case secureValueTypeBankStatement - case secureValueTypeDriverLicense - case secureValueTypeEmail - case secureValueTypeIdentityCard - case secureValueTypeInternalPassport - case secureValueTypePassport - case secureValueTypePassportRegistration - case secureValueTypePersonalDetails - case secureValueTypePhone - case secureValueTypeRentalAgreement - case secureValueTypeTemporaryRegistration - case secureValueTypeUtilityBill + enum SavedDialog: TypeConstructorDescription { + case savedDialog(flags: Int32, peer: Api.Peer, topMessage: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureValueTypeAddress: - if boxed { - buffer.appendInt32(-874308058) - } - - break - case .secureValueTypeBankStatement: - if boxed { - buffer.appendInt32(-1995211763) - } - - break - case .secureValueTypeDriverLicense: - if boxed { - buffer.appendInt32(115615172) - } - - break - case .secureValueTypeEmail: + case .savedDialog(let flags, let peer, let topMessage): if boxed { - buffer.appendInt32(-1908627474) + buffer.appendInt32(-1115174036) } - - break - case .secureValueTypeIdentityCard: - if boxed { - buffer.appendInt32(-1596951477) - } - - break - case .secureValueTypeInternalPassport: - if boxed { - buffer.appendInt32(-1717268701) - } - + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(topMessage, buffer: buffer, boxed: false) break - case .secureValueTypePassport: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .savedDialog(let flags, let peer, let topMessage): + return ("savedDialog", [("flags", flags as Any), ("peer", peer as Any), ("topMessage", topMessage as Any)]) + } + } + + public static func parse_savedDialog(_ reader: BufferReader) -> SavedDialog? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SavedDialog.savedDialog(flags: _1!, peer: _2!, topMessage: _3!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum SavedReactionTag: TypeConstructorDescription { + case savedReactionTag(flags: Int32, reaction: Api.Reaction, title: String?, count: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedReactionTag(let flags, let reaction, let title, let count): if boxed { - buffer.appendInt32(1034709504) + buffer.appendInt32(-881854424) } - + serializeInt32(flags, buffer: buffer, boxed: false) + reaction.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + serializeInt32(count, buffer: buffer, boxed: false) break - case .secureValueTypePassportRegistration: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .savedReactionTag(let flags, let reaction, let title, let count): + return ("savedReactionTag", [("flags", flags as Any), ("reaction", reaction as Any), ("title", title as Any), ("count", count as Any)]) + } + } + + public static func parse_savedReactionTag(_ reader: BufferReader) -> SavedReactionTag? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Reaction? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Reaction + } + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.SavedReactionTag.savedReactionTag(flags: _1!, reaction: _2!, title: _3, count: _4!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum SearchResultsCalendarPeriod: TypeConstructorDescription { + case searchResultsCalendarPeriod(date: Int32, minMsgId: Int32, maxMsgId: Int32, count: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .searchResultsCalendarPeriod(let date, let minMsgId, let maxMsgId, let count): if boxed { - buffer.appendInt32(-1713143702) + buffer.appendInt32(-911191137) } - + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(minMsgId, buffer: buffer, boxed: false) + serializeInt32(maxMsgId, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) break - case .secureValueTypePersonalDetails: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .searchResultsCalendarPeriod(let date, let minMsgId, let maxMsgId, let count): + return ("searchResultsCalendarPeriod", [("date", date as Any), ("minMsgId", minMsgId as Any), ("maxMsgId", maxMsgId as Any), ("count", count as Any)]) + } + } + + public static func parse_searchResultsCalendarPeriod(_ reader: BufferReader) -> SearchResultsCalendarPeriod? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.SearchResultsCalendarPeriod.searchResultsCalendarPeriod(date: _1!, minMsgId: _2!, maxMsgId: _3!, count: _4!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum SearchResultsPosition: TypeConstructorDescription { + case searchResultPosition(msgId: Int32, date: Int32, offset: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .searchResultPosition(let msgId, let date, let offset): if boxed { - buffer.appendInt32(-1658158621) + buffer.appendInt32(2137295719) } - + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) break - case .secureValueTypePhone: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .searchResultPosition(let msgId, let date, let offset): + return ("searchResultPosition", [("msgId", msgId as Any), ("date", date as Any), ("offset", offset as Any)]) + } + } + + public static func parse_searchResultPosition(_ reader: BufferReader) -> SearchResultsPosition? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SearchResultsPosition.searchResultPosition(msgId: _1!, date: _2!, offset: _3!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum SecureCredentialsEncrypted: TypeConstructorDescription { + case secureCredentialsEncrypted(data: Buffer, hash: Buffer, secret: Buffer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .secureCredentialsEncrypted(let data, let hash, let secret): if boxed { - buffer.appendInt32(-1289704741) + buffer.appendInt32(871426631) } - + serializeBytes(data, buffer: buffer, boxed: false) + serializeBytes(hash, buffer: buffer, boxed: false) + serializeBytes(secret, buffer: buffer, boxed: false) break - case .secureValueTypeRentalAgreement: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .secureCredentialsEncrypted(let data, let hash, let secret): + return ("secureCredentialsEncrypted", [("data", data as Any), ("hash", hash as Any), ("secret", secret as Any)]) + } + } + + public static func parse_secureCredentialsEncrypted(_ reader: BufferReader) -> SecureCredentialsEncrypted? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureCredentialsEncrypted.secureCredentialsEncrypted(data: _1!, hash: _2!, secret: _3!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum SecureData: TypeConstructorDescription { + case secureData(data: Buffer, dataHash: Buffer, secret: Buffer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .secureData(let data, let dataHash, let secret): if boxed { - buffer.appendInt32(-1954007928) + buffer.appendInt32(-1964327229) } - + serializeBytes(data, buffer: buffer, boxed: false) + serializeBytes(dataHash, buffer: buffer, boxed: false) + serializeBytes(secret, buffer: buffer, boxed: false) break - case .secureValueTypeTemporaryRegistration: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .secureData(let data, let dataHash, let secret): + return ("secureData", [("data", data as Any), ("dataHash", dataHash as Any), ("secret", secret as Any)]) + } + } + + public static func parse_secureData(_ reader: BufferReader) -> SecureData? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureData.secureData(data: _1!, dataHash: _2!, secret: _3!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum SecureFile: TypeConstructorDescription { + case secureFile(id: Int64, accessHash: Int64, size: Int64, dcId: Int32, date: Int32, fileHash: Buffer, secret: Buffer) + case secureFileEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .secureFile(let id, let accessHash, let size, let dcId, let date, let fileHash, let secret): if boxed { - buffer.appendInt32(-368907213) + buffer.appendInt32(2097791614) } - + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt64(size, buffer: buffer, boxed: false) + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeBytes(fileHash, buffer: buffer, boxed: false) + serializeBytes(secret, buffer: buffer, boxed: false) break - case .secureValueTypeUtilityBill: + case .secureFileEmpty: if boxed { - buffer.appendInt32(-63531698) + buffer.appendInt32(1679398724) } break @@ -777,73 +1042,44 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .secureValueTypeAddress: - return ("secureValueTypeAddress", []) - case .secureValueTypeBankStatement: - return ("secureValueTypeBankStatement", []) - case .secureValueTypeDriverLicense: - return ("secureValueTypeDriverLicense", []) - case .secureValueTypeEmail: - return ("secureValueTypeEmail", []) - case .secureValueTypeIdentityCard: - return ("secureValueTypeIdentityCard", []) - case .secureValueTypeInternalPassport: - return ("secureValueTypeInternalPassport", []) - case .secureValueTypePassport: - return ("secureValueTypePassport", []) - case .secureValueTypePassportRegistration: - return ("secureValueTypePassportRegistration", []) - case .secureValueTypePersonalDetails: - return ("secureValueTypePersonalDetails", []) - case .secureValueTypePhone: - return ("secureValueTypePhone", []) - case .secureValueTypeRentalAgreement: - return ("secureValueTypeRentalAgreement", []) - case .secureValueTypeTemporaryRegistration: - return ("secureValueTypeTemporaryRegistration", []) - case .secureValueTypeUtilityBill: - return ("secureValueTypeUtilityBill", []) - } - } - - public static func parse_secureValueTypeAddress(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeAddress - } - public static func parse_secureValueTypeBankStatement(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeBankStatement - } - public static func parse_secureValueTypeDriverLicense(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeDriverLicense - } - public static func parse_secureValueTypeEmail(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeEmail - } - public static func parse_secureValueTypeIdentityCard(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeIdentityCard - } - public static func parse_secureValueTypeInternalPassport(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeInternalPassport - } - public static func parse_secureValueTypePassport(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypePassport - } - public static func parse_secureValueTypePassportRegistration(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypePassportRegistration - } - public static func parse_secureValueTypePersonalDetails(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypePersonalDetails - } - public static func parse_secureValueTypePhone(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypePhone - } - public static func parse_secureValueTypeRentalAgreement(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeRentalAgreement - } - public static func parse_secureValueTypeTemporaryRegistration(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeTemporaryRegistration - } - public static func parse_secureValueTypeUtilityBill(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeUtilityBill + case .secureFile(let id, let accessHash, let size, let dcId, let date, let fileHash, let secret): + return ("secureFile", [("id", id as Any), ("accessHash", accessHash as Any), ("size", size as Any), ("dcId", dcId as Any), ("date", date as Any), ("fileHash", fileHash as Any), ("secret", secret as Any)]) + case .secureFileEmpty: + return ("secureFileEmpty", []) + } + } + + public static func parse_secureFile(_ reader: BufferReader) -> SecureFile? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Buffer? + _6 = parseBytes(reader) + var _7: Buffer? + _7 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.SecureFile.secureFile(id: _1!, accessHash: _2!, size: _3!, dcId: _4!, date: _5!, fileHash: _6!, secret: _7!) + } + else { + return nil + } + } + public static func parse_secureFileEmpty(_ reader: BufferReader) -> SecureFile? { + return Api.SecureFile.secureFileEmpty } } diff --git a/submodules/TelegramApi/Sources/Api22.swift b/submodules/TelegramApi/Sources/Api22.swift index d92743523eb..3d6fc545846 100644 --- a/submodules/TelegramApi/Sources/Api22.swift +++ b/submodules/TelegramApi/Sources/Api22.swift @@ -1,366 +1,148 @@ public extension Api { - enum SendAsPeer: TypeConstructorDescription { - case sendAsPeer(flags: Int32, peer: Api.Peer) + enum SecurePasswordKdfAlgo: TypeConstructorDescription { + case securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: Buffer) + case securePasswordKdfAlgoSHA512(salt: Buffer) + case securePasswordKdfAlgoUnknown public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sendAsPeer(let flags, let peer): + case .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(let salt): if boxed { - buffer.appendInt32(-1206095820) + buffer.appendInt32(-1141711456) } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) + serializeBytes(salt, buffer: buffer, boxed: false) + break + case .securePasswordKdfAlgoSHA512(let salt): + if boxed { + buffer.appendInt32(-2042159726) + } + serializeBytes(salt, buffer: buffer, boxed: false) + break + case .securePasswordKdfAlgoUnknown: + if boxed { + buffer.appendInt32(4883767) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sendAsPeer(let flags, let peer): - return ("sendAsPeer", [("flags", flags as Any), ("peer", peer as Any)]) + case .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(let salt): + return ("securePasswordKdfAlgoPBKDF2HMACSHA512iter100000", [("salt", salt as Any)]) + case .securePasswordKdfAlgoSHA512(let salt): + return ("securePasswordKdfAlgoSHA512", [("salt", salt as Any)]) + case .securePasswordKdfAlgoUnknown: + return ("securePasswordKdfAlgoUnknown", []) } } - public static func parse_sendAsPeer(_ reader: BufferReader) -> SendAsPeer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer + public static func parse_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { + var _1: Buffer? + _1 = parseBytes(reader) + let _c1 = _1 != nil + if _c1 { + return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: _1!) + } + else { + return nil } + } + public static func parse_securePasswordKdfAlgoSHA512(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { + var _1: Buffer? + _1 = parseBytes(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SendAsPeer.sendAsPeer(flags: _1!, peer: _2!) + if _c1 { + return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoSHA512(salt: _1!) } else { return nil } } + public static func parse_securePasswordKdfAlgoUnknown(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { + return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoUnknown + } } } public extension Api { - enum SendMessageAction: TypeConstructorDescription { - case sendMessageCancelAction - case sendMessageChooseContactAction - case sendMessageChooseStickerAction - case sendMessageEmojiInteraction(emoticon: String, msgId: Int32, interaction: Api.DataJSON) - case sendMessageEmojiInteractionSeen(emoticon: String) - case sendMessageGamePlayAction - case sendMessageGeoLocationAction - case sendMessageHistoryImportAction(progress: Int32) - case sendMessageRecordAudioAction - case sendMessageRecordRoundAction - case sendMessageRecordVideoAction - case sendMessageTypingAction - case sendMessageUploadAudioAction(progress: Int32) - case sendMessageUploadDocumentAction(progress: Int32) - case sendMessageUploadPhotoAction(progress: Int32) - case sendMessageUploadRoundAction(progress: Int32) - case sendMessageUploadVideoAction(progress: Int32) - case speakingInGroupCallAction + enum SecurePlainData: TypeConstructorDescription { + case securePlainEmail(email: String) + case securePlainPhone(phone: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sendMessageCancelAction: - if boxed { - buffer.appendInt32(-44119819) - } - - break - case .sendMessageChooseContactAction: - if boxed { - buffer.appendInt32(1653390447) - } - - break - case .sendMessageChooseStickerAction: - if boxed { - buffer.appendInt32(-1336228175) - } - - break - case .sendMessageEmojiInteraction(let emoticon, let msgId, let interaction): - if boxed { - buffer.appendInt32(630664139) - } - serializeString(emoticon, buffer: buffer, boxed: false) - serializeInt32(msgId, buffer: buffer, boxed: false) - interaction.serialize(buffer, true) - break - case .sendMessageEmojiInteractionSeen(let emoticon): - if boxed { - buffer.appendInt32(-1234857938) - } - serializeString(emoticon, buffer: buffer, boxed: false) - break - case .sendMessageGamePlayAction: - if boxed { - buffer.appendInt32(-580219064) - } - - break - case .sendMessageGeoLocationAction: - if boxed { - buffer.appendInt32(393186209) - } - - break - case .sendMessageHistoryImportAction(let progress): - if boxed { - buffer.appendInt32(-606432698) - } - serializeInt32(progress, buffer: buffer, boxed: false) - break - case .sendMessageRecordAudioAction: - if boxed { - buffer.appendInt32(-718310409) - } - - break - case .sendMessageRecordRoundAction: - if boxed { - buffer.appendInt32(-1997373508) - } - - break - case .sendMessageRecordVideoAction: - if boxed { - buffer.appendInt32(-1584933265) - } - - break - case .sendMessageTypingAction: + case .securePlainEmail(let email): if boxed { - buffer.appendInt32(381645902) + buffer.appendInt32(569137759) } - - break - case .sendMessageUploadAudioAction(let progress): - if boxed { - buffer.appendInt32(-212740181) - } - serializeInt32(progress, buffer: buffer, boxed: false) - break - case .sendMessageUploadDocumentAction(let progress): - if boxed { - buffer.appendInt32(-1441998364) - } - serializeInt32(progress, buffer: buffer, boxed: false) - break - case .sendMessageUploadPhotoAction(let progress): - if boxed { - buffer.appendInt32(-774682074) - } - serializeInt32(progress, buffer: buffer, boxed: false) - break - case .sendMessageUploadRoundAction(let progress): - if boxed { - buffer.appendInt32(608050278) - } - serializeInt32(progress, buffer: buffer, boxed: false) + serializeString(email, buffer: buffer, boxed: false) break - case .sendMessageUploadVideoAction(let progress): + case .securePlainPhone(let phone): if boxed { - buffer.appendInt32(-378127636) + buffer.appendInt32(2103482845) } - serializeInt32(progress, buffer: buffer, boxed: false) - break - case .speakingInGroupCallAction: - if boxed { - buffer.appendInt32(-651419003) - } - + serializeString(phone, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sendMessageCancelAction: - return ("sendMessageCancelAction", []) - case .sendMessageChooseContactAction: - return ("sendMessageChooseContactAction", []) - case .sendMessageChooseStickerAction: - return ("sendMessageChooseStickerAction", []) - case .sendMessageEmojiInteraction(let emoticon, let msgId, let interaction): - return ("sendMessageEmojiInteraction", [("emoticon", emoticon as Any), ("msgId", msgId as Any), ("interaction", interaction as Any)]) - case .sendMessageEmojiInteractionSeen(let emoticon): - return ("sendMessageEmojiInteractionSeen", [("emoticon", emoticon as Any)]) - case .sendMessageGamePlayAction: - return ("sendMessageGamePlayAction", []) - case .sendMessageGeoLocationAction: - return ("sendMessageGeoLocationAction", []) - case .sendMessageHistoryImportAction(let progress): - return ("sendMessageHistoryImportAction", [("progress", progress as Any)]) - case .sendMessageRecordAudioAction: - return ("sendMessageRecordAudioAction", []) - case .sendMessageRecordRoundAction: - return ("sendMessageRecordRoundAction", []) - case .sendMessageRecordVideoAction: - return ("sendMessageRecordVideoAction", []) - case .sendMessageTypingAction: - return ("sendMessageTypingAction", []) - case .sendMessageUploadAudioAction(let progress): - return ("sendMessageUploadAudioAction", [("progress", progress as Any)]) - case .sendMessageUploadDocumentAction(let progress): - return ("sendMessageUploadDocumentAction", [("progress", progress as Any)]) - case .sendMessageUploadPhotoAction(let progress): - return ("sendMessageUploadPhotoAction", [("progress", progress as Any)]) - case .sendMessageUploadRoundAction(let progress): - return ("sendMessageUploadRoundAction", [("progress", progress as Any)]) - case .sendMessageUploadVideoAction(let progress): - return ("sendMessageUploadVideoAction", [("progress", progress as Any)]) - case .speakingInGroupCallAction: - return ("speakingInGroupCallAction", []) - } - } - - public static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageCancelAction - } - public static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageChooseContactAction - } - public static func parse_sendMessageChooseStickerAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageChooseStickerAction - } - public static func parse_sendMessageEmojiInteraction(_ reader: BufferReader) -> SendMessageAction? { + case .securePlainEmail(let email): + return ("securePlainEmail", [("email", email as Any)]) + case .securePlainPhone(let phone): + return ("securePlainPhone", [("phone", phone as Any)]) + } + } + + public static func parse_securePlainEmail(_ reader: BufferReader) -> SecurePlainData? { var _1: String? _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.DataJSON? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.DataJSON - } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SendMessageAction.sendMessageEmojiInteraction(emoticon: _1!, msgId: _2!, interaction: _3!) + if _c1 { + return Api.SecurePlainData.securePlainEmail(email: _1!) } else { return nil } } - public static func parse_sendMessageEmojiInteractionSeen(_ reader: BufferReader) -> SendMessageAction? { + public static func parse_securePlainPhone(_ reader: BufferReader) -> SecurePlainData? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.SendMessageAction.sendMessageEmojiInteractionSeen(emoticon: _1!) - } - else { - return nil - } - } - public static func parse_sendMessageGamePlayAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageGamePlayAction - } - public static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageGeoLocationAction - } - public static func parse_sendMessageHistoryImportAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageHistoryImportAction(progress: _1!) + return Api.SecurePlainData.securePlainPhone(phone: _1!) } else { return nil } } - public static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageRecordAudioAction - } - public static func parse_sendMessageRecordRoundAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageRecordRoundAction - } - public static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageRecordVideoAction - } - public static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageTypingAction - } - public static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadAudioAction(progress: _1!) - } - else { - return nil - } - } - public static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadDocumentAction(progress: _1!) - } - else { - return nil - } - } - public static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadPhotoAction(progress: _1!) - } - else { - return nil - } - } - public static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadRoundAction(progress: _1!) - } - else { - return nil - } - } - public static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadVideoAction(progress: _1!) - } - else { - return nil - } - } - public static func parse_speakingInGroupCallAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.speakingInGroupCallAction - } } } public extension Api { - enum ShippingOption: TypeConstructorDescription { - case shippingOption(id: String, title: String, prices: [Api.LabeledPrice]) + enum SecureRequiredType: TypeConstructorDescription { + case secureRequiredType(flags: Int32, type: Api.SecureValueType) + case secureRequiredTypeOneOf(types: [Api.SecureRequiredType]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .shippingOption(let id, let title, let prices): + case .secureRequiredType(let flags, let type): if boxed { - buffer.appendInt32(-1239335713) + buffer.appendInt32(-2103600678) + } + serializeInt32(flags, buffer: buffer, boxed: false) + type.serialize(buffer, true) + break + case .secureRequiredTypeOneOf(let types): + if boxed { + buffer.appendInt32(41187252) } - serializeString(id, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(prices.count)) - for item in prices { + buffer.appendInt32(Int32(types.count)) + for item in types { item.serialize(buffer, true) } break @@ -369,61 +151,37 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .shippingOption(let id, let title, let prices): - return ("shippingOption", [("id", id as Any), ("title", title as Any), ("prices", prices as Any)]) + case .secureRequiredType(let flags, let type): + return ("secureRequiredType", [("flags", flags as Any), ("type", type as Any)]) + case .secureRequiredTypeOneOf(let types): + return ("secureRequiredTypeOneOf", [("types", types as Any)]) } } - public static func parse_shippingOption(_ reader: BufferReader) -> ShippingOption? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: [Api.LabeledPrice]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LabeledPrice.self) + public static func parse_secureRequiredType(_ reader: BufferReader) -> SecureRequiredType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.SecureValueType? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.SecureValueType } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.ShippingOption.shippingOption(id: _1!, title: _2!, prices: _3!) + if _c1 && _c2 { + return Api.SecureRequiredType.secureRequiredType(flags: _1!, type: _2!) } else { return nil } } - - } -} -public extension Api { - enum SimpleWebViewResult: TypeConstructorDescription { - case simpleWebViewResultUrl(url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .simpleWebViewResultUrl(let url): - if boxed { - buffer.appendInt32(-2010155333) - } - serializeString(url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .simpleWebViewResultUrl(let url): - return ("simpleWebViewResultUrl", [("url", url as Any)]) - } - } - - public static func parse_simpleWebViewResultUrl(_ reader: BufferReader) -> SimpleWebViewResult? { - var _1: String? - _1 = parseString(reader) + public static func parse_secureRequiredTypeOneOf(_ reader: BufferReader) -> SecureRequiredType? { + var _1: [Api.SecureRequiredType]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureRequiredType.self) + } let _c1 = _1 != nil if _c1 { - return Api.SimpleWebViewResult.simpleWebViewResultUrl(url: _1!) + return Api.SecureRequiredType.secureRequiredTypeOneOf(types: _1!) } else { return nil @@ -433,41 +191,43 @@ public extension Api { } } public extension Api { - enum SmsJob: TypeConstructorDescription { - case smsJob(jobId: String, phoneNumber: String, text: String) + enum SecureSecretSettings: TypeConstructorDescription { + case secureSecretSettings(secureAlgo: Api.SecurePasswordKdfAlgo, secureSecret: Buffer, secureSecretId: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .smsJob(let jobId, let phoneNumber, let text): + case .secureSecretSettings(let secureAlgo, let secureSecret, let secureSecretId): if boxed { - buffer.appendInt32(-425595208) + buffer.appendInt32(354925740) } - serializeString(jobId, buffer: buffer, boxed: false) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) + secureAlgo.serialize(buffer, true) + serializeBytes(secureSecret, buffer: buffer, boxed: false) + serializeInt64(secureSecretId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .smsJob(let jobId, let phoneNumber, let text): - return ("smsJob", [("jobId", jobId as Any), ("phoneNumber", phoneNumber as Any), ("text", text as Any)]) + case .secureSecretSettings(let secureAlgo, let secureSecret, let secureSecretId): + return ("secureSecretSettings", [("secureAlgo", secureAlgo as Any), ("secureSecret", secureSecret as Any), ("secureSecretId", secureSecretId as Any)]) } } - public static func parse_smsJob(_ reader: BufferReader) -> SmsJob? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) + public static func parse_secureSecretSettings(_ reader: BufferReader) -> SecureSecretSettings? { + var _1: Api.SecurePasswordKdfAlgo? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecurePasswordKdfAlgo + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int64? + _3 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.SmsJob.smsJob(jobId: _1!, phoneNumber: _2!, text: _3!) + return Api.SecureSecretSettings.secureSecretSettings(secureAlgo: _1!, secureSecret: _2!, secureSecretId: _3!) } else { return nil @@ -477,83 +237,93 @@ public extension Api { } } public extension Api { - enum SponsoredMessage: TypeConstructorDescription { - case sponsoredMessage(flags: Int32, randomId: Buffer, url: String, title: String, message: String, entities: [Api.MessageEntity]?, photo: Api.Photo?, color: Api.PeerColor?, buttonText: String, sponsorInfo: String?, additionalInfo: String?) + enum SecureValue: TypeConstructorDescription { + case secureValue(flags: Int32, type: Api.SecureValueType, data: Api.SecureData?, frontSide: Api.SecureFile?, reverseSide: Api.SecureFile?, selfie: Api.SecureFile?, translation: [Api.SecureFile]?, files: [Api.SecureFile]?, plainData: Api.SecurePlainData?, hash: Buffer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let color, let buttonText, let sponsorInfo, let additionalInfo): + case .secureValue(let flags, let type, let data, let frontSide, let reverseSide, let selfie, let translation, let files, let plainData, let hash): if boxed { - buffer.appendInt32(-1108478618) + buffer.appendInt32(411017418) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeBytes(randomId, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { + type.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {data!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {frontSide!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {reverseSide!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {selfie!.serialize(buffer, true)} + if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(translation!.count)) + for item in translation! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(files!.count)) + for item in files! { item.serialize(buffer, true) }} - if Int(flags) & Int(1 << 6) != 0 {photo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 13) != 0 {color!.serialize(buffer, true)} - serializeString(buttonText, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 7) != 0 {serializeString(sponsorInfo!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 8) != 0 {serializeString(additionalInfo!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {plainData!.serialize(buffer, true)} + serializeBytes(hash, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let color, let buttonText, let sponsorInfo, let additionalInfo): - return ("sponsoredMessage", [("flags", flags as Any), ("randomId", randomId as Any), ("url", url as Any), ("title", title as Any), ("message", message as Any), ("entities", entities as Any), ("photo", photo as Any), ("color", color as Any), ("buttonText", buttonText as Any), ("sponsorInfo", sponsorInfo as Any), ("additionalInfo", additionalInfo as Any)]) + case .secureValue(let flags, let type, let data, let frontSide, let reverseSide, let selfie, let translation, let files, let plainData, let hash): + return ("secureValue", [("flags", flags as Any), ("type", type as Any), ("data", data as Any), ("frontSide", frontSide as Any), ("reverseSide", reverseSide as Any), ("selfie", selfie as Any), ("translation", translation as Any), ("files", files as Any), ("plainData", plainData as Any), ("hash", hash as Any)]) } } - public static func parse_sponsoredMessage(_ reader: BufferReader) -> SponsoredMessage? { + public static func parse_secureValue(_ reader: BufferReader) -> SecureValue? { var _1: Int32? _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + var _2: Api.SecureValueType? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _3: Api.SecureData? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.SecureData + } } + var _4: Api.SecureFile? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.SecureFile + } } + var _5: Api.SecureFile? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.SecureFile + } } + var _6: Api.SecureFile? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.SecureFile } } - var _7: Api.Photo? - if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Photo + var _7: [Api.SecureFile]? + if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureFile.self) } } - var _8: Api.PeerColor? - if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.PeerColor + var _8: [Api.SecureFile]? + if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureFile.self) } } - var _9: String? - _9 = parseString(reader) - var _10: String? - if Int(_1!) & Int(1 << 7) != 0 {_10 = parseString(reader) } - var _11: String? - if Int(_1!) & Int(1 << 8) != 0 {_11 = parseString(reader) } + var _9: Api.SecurePlainData? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.SecurePlainData + } } + var _10: Buffer? + _10 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 13) == 0) || _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 7) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 8) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, url: _3!, title: _4!, message: _5!, entities: _6, photo: _7, color: _8, buttonText: _9!, sponsorInfo: _10, additionalInfo: _11) + let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil + let _c10 = _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.SecureValue.secureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9, hash: _10!) } else { return nil @@ -563,251 +333,299 @@ public extension Api { } } public extension Api { - enum SponsoredMessageReportOption: TypeConstructorDescription { - case sponsoredMessageReportOption(text: String, option: Buffer) + enum SecureValueError: TypeConstructorDescription { + case secureValueError(type: Api.SecureValueType, hash: Buffer, text: String) + case secureValueErrorData(type: Api.SecureValueType, dataHash: Buffer, field: String, text: String) + case secureValueErrorFile(type: Api.SecureValueType, fileHash: Buffer, text: String) + case secureValueErrorFiles(type: Api.SecureValueType, fileHash: [Buffer], text: String) + case secureValueErrorFrontSide(type: Api.SecureValueType, fileHash: Buffer, text: String) + case secureValueErrorReverseSide(type: Api.SecureValueType, fileHash: Buffer, text: String) + case secureValueErrorSelfie(type: Api.SecureValueType, fileHash: Buffer, text: String) + case secureValueErrorTranslationFile(type: Api.SecureValueType, fileHash: Buffer, text: String) + case secureValueErrorTranslationFiles(type: Api.SecureValueType, fileHash: [Buffer], text: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sponsoredMessageReportOption(let text, let option): + case .secureValueError(let type, let hash, let text): if boxed { - buffer.appendInt32(1124938064) + buffer.appendInt32(-2036501105) + } + type.serialize(buffer, true) + serializeBytes(hash, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + break + case .secureValueErrorData(let type, let dataHash, let field, let text): + if boxed { + buffer.appendInt32(-391902247) + } + type.serialize(buffer, true) + serializeBytes(dataHash, buffer: buffer, boxed: false) + serializeString(field, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + break + case .secureValueErrorFile(let type, let fileHash, let text): + if boxed { + buffer.appendInt32(2054162547) + } + type.serialize(buffer, true) + serializeBytes(fileHash, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + break + case .secureValueErrorFiles(let type, let fileHash, let text): + if boxed { + buffer.appendInt32(1717706985) + } + type.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(fileHash.count)) + for item in fileHash { + serializeBytes(item, buffer: buffer, boxed: false) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .secureValueErrorFrontSide(let type, let fileHash, let text): + if boxed { + buffer.appendInt32(12467706) + } + type.serialize(buffer, true) + serializeBytes(fileHash, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + break + case .secureValueErrorReverseSide(let type, let fileHash, let text): + if boxed { + buffer.appendInt32(-2037765467) + } + type.serialize(buffer, true) + serializeBytes(fileHash, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + break + case .secureValueErrorSelfie(let type, let fileHash, let text): + if boxed { + buffer.appendInt32(-449327402) + } + type.serialize(buffer, true) + serializeBytes(fileHash, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + break + case .secureValueErrorTranslationFile(let type, let fileHash, let text): + if boxed { + buffer.appendInt32(-1592506512) + } + type.serialize(buffer, true) + serializeBytes(fileHash, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + break + case .secureValueErrorTranslationFiles(let type, let fileHash, let text): + if boxed { + buffer.appendInt32(878931416) + } + type.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(fileHash.count)) + for item in fileHash { + serializeBytes(item, buffer: buffer, boxed: false) } serializeString(text, buffer: buffer, boxed: false) - serializeBytes(option, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sponsoredMessageReportOption(let text, let option): - return ("sponsoredMessageReportOption", [("text", text as Any), ("option", option as Any)]) - } - } - - public static func parse_sponsoredMessageReportOption(_ reader: BufferReader) -> SponsoredMessageReportOption? { - var _1: String? - _1 = parseString(reader) + case .secureValueError(let type, let hash, let text): + return ("secureValueError", [("type", type as Any), ("hash", hash as Any), ("text", text as Any)]) + case .secureValueErrorData(let type, let dataHash, let field, let text): + return ("secureValueErrorData", [("type", type as Any), ("dataHash", dataHash as Any), ("field", field as Any), ("text", text as Any)]) + case .secureValueErrorFile(let type, let fileHash, let text): + return ("secureValueErrorFile", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) + case .secureValueErrorFiles(let type, let fileHash, let text): + return ("secureValueErrorFiles", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) + case .secureValueErrorFrontSide(let type, let fileHash, let text): + return ("secureValueErrorFrontSide", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) + case .secureValueErrorReverseSide(let type, let fileHash, let text): + return ("secureValueErrorReverseSide", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) + case .secureValueErrorSelfie(let type, let fileHash, let text): + return ("secureValueErrorSelfie", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) + case .secureValueErrorTranslationFile(let type, let fileHash, let text): + return ("secureValueErrorTranslationFile", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) + case .secureValueErrorTranslationFiles(let type, let fileHash, let text): + return ("secureValueErrorTranslationFiles", [("type", type as Any), ("fileHash", fileHash as Any), ("text", text as Any)]) + } + } + + public static func parse_secureValueError(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } var _2: Buffer? _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SponsoredMessageReportOption.sponsoredMessageReportOption(text: _1!, option: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueError(type: _1!, hash: _2!, text: _3!) } else { return nil } } - - } -} -public extension Api { - enum StatsAbsValueAndPrev: TypeConstructorDescription { - case statsAbsValueAndPrev(current: Double, previous: Double) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsAbsValueAndPrev(let current, let previous): - if boxed { - buffer.appendInt32(-884757282) - } - serializeDouble(current, buffer: buffer, boxed: false) - serializeDouble(previous, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .statsAbsValueAndPrev(let current, let previous): - return ("statsAbsValueAndPrev", [("current", current as Any), ("previous", previous as Any)]) - } - } - - public static func parse_statsAbsValueAndPrev(_ reader: BufferReader) -> StatsAbsValueAndPrev? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() + public static func parse_secureValueErrorData(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StatsAbsValueAndPrev.statsAbsValueAndPrev(current: _1!, previous: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.SecureValueError.secureValueErrorData(type: _1!, dataHash: _2!, field: _3!, text: _4!) } else { return nil } } - - } -} -public extension Api { - enum StatsDateRangeDays: TypeConstructorDescription { - case statsDateRangeDays(minDate: Int32, maxDate: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsDateRangeDays(let minDate, let maxDate): - if boxed { - buffer.appendInt32(-1237848657) - } - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .statsDateRangeDays(let minDate, let maxDate): - return ("statsDateRangeDays", [("minDate", minDate as Any), ("maxDate", maxDate as Any)]) - } - } - - public static func parse_statsDateRangeDays(_ reader: BufferReader) -> StatsDateRangeDays? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + public static func parse_secureValueErrorFile(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StatsDateRangeDays.statsDateRangeDays(minDate: _1!, maxDate: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorFile(type: _1!, fileHash: _2!, text: _3!) } else { return nil } } - - } -} -public extension Api { - enum StatsGraph: TypeConstructorDescription { - case statsGraph(flags: Int32, json: Api.DataJSON, zoomToken: String?) - case statsGraphAsync(token: String) - case statsGraphError(error: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsGraph(let flags, let json, let zoomToken): - if boxed { - buffer.appendInt32(-1901828938) - } - serializeInt32(flags, buffer: buffer, boxed: false) - json.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeString(zoomToken!, buffer: buffer, boxed: false)} - break - case .statsGraphAsync(let token): - if boxed { - buffer.appendInt32(1244130093) - } - serializeString(token, buffer: buffer, boxed: false) - break - case .statsGraphError(let error): - if boxed { - buffer.appendInt32(-1092839390) - } - serializeString(error, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .statsGraph(let flags, let json, let zoomToken): - return ("statsGraph", [("flags", flags as Any), ("json", json as Any), ("zoomToken", zoomToken as Any)]) - case .statsGraphAsync(let token): - return ("statsGraphAsync", [("token", token as Any)]) - case .statsGraphError(let error): - return ("statsGraphError", [("error", error as Any)]) - } - } - - public static func parse_statsGraph(_ reader: BufferReader) -> StatsGraph? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.DataJSON? + public static func parse_secureValueErrorFiles(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: [Buffer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) } var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.StatsGraph.statsGraph(flags: _1!, json: _2!, zoomToken: _3) + return Api.SecureValueError.secureValueErrorFiles(type: _1!, fileHash: _2!, text: _3!) } else { return nil } } - public static func parse_statsGraphAsync(_ reader: BufferReader) -> StatsGraph? { - var _1: String? - _1 = parseString(reader) + public static func parse_secureValueErrorFrontSide(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.StatsGraph.statsGraphAsync(token: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorFrontSide(type: _1!, fileHash: _2!, text: _3!) } else { return nil } } - public static func parse_statsGraphError(_ reader: BufferReader) -> StatsGraph? { - var _1: String? - _1 = parseString(reader) + public static func parse_secureValueErrorReverseSide(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.StatsGraph.statsGraphError(error: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorReverseSide(type: _1!, fileHash: _2!, text: _3!) } else { return nil } } - - } -} -public extension Api { - enum StatsGroupTopAdmin: TypeConstructorDescription { - case statsGroupTopAdmin(userId: Int64, deleted: Int32, kicked: Int32, banned: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsGroupTopAdmin(let userId, let deleted, let kicked, let banned): - if boxed { - buffer.appendInt32(-682079097) - } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt32(deleted, buffer: buffer, boxed: false) - serializeInt32(kicked, buffer: buffer, boxed: false) - serializeInt32(banned, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .statsGroupTopAdmin(let userId, let deleted, let kicked, let banned): - return ("statsGroupTopAdmin", [("userId", userId as Any), ("deleted", deleted as Any), ("kicked", kicked as Any), ("banned", banned as Any)]) - } - } - - public static func parse_statsGroupTopAdmin(_ reader: BufferReader) -> StatsGroupTopAdmin? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() + public static func parse_secureValueErrorSelfie(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StatsGroupTopAdmin.statsGroupTopAdmin(userId: _1!, deleted: _2!, kicked: _3!, banned: _4!) + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorSelfie(type: _1!, fileHash: _2!, text: _3!) + } + else { + return nil + } + } + public static func parse_secureValueErrorTranslationFile(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorTranslationFile(type: _1!, fileHash: _2!, text: _3!) + } + else { + return nil + } + } + public static func parse_secureValueErrorTranslationFiles(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: [Buffer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + } + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorTranslationFiles(type: _1!, fileHash: _2!, text: _3!) } else { return nil @@ -817,37 +635,39 @@ public extension Api { } } public extension Api { - enum StatsGroupTopInviter: TypeConstructorDescription { - case statsGroupTopInviter(userId: Int64, invitations: Int32) + enum SecureValueHash: TypeConstructorDescription { + case secureValueHash(type: Api.SecureValueType, hash: Buffer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .statsGroupTopInviter(let userId, let invitations): + case .secureValueHash(let type, let hash): if boxed { - buffer.appendInt32(1398765469) + buffer.appendInt32(-316748368) } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt32(invitations, buffer: buffer, boxed: false) + type.serialize(buffer, true) + serializeBytes(hash, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .statsGroupTopInviter(let userId, let invitations): - return ("statsGroupTopInviter", [("userId", userId as Any), ("invitations", invitations as Any)]) + case .secureValueHash(let type, let hash): + return ("secureValueHash", [("type", type as Any), ("hash", hash as Any)]) } } - public static func parse_statsGroupTopInviter(_ reader: BufferReader) -> StatsGroupTopInviter? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() + public static func parse_secureValueHash(_ reader: BufferReader) -> SecureValueHash? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.StatsGroupTopInviter.statsGroupTopInviter(userId: _1!, invitations: _2!) + return Api.SecureValueHash.secureValueHash(type: _1!, hash: _2!) } else { return nil @@ -857,45 +677,173 @@ public extension Api { } } public extension Api { - enum StatsGroupTopPoster: TypeConstructorDescription { - case statsGroupTopPoster(userId: Int64, messages: Int32, avgChars: Int32) + enum SecureValueType: TypeConstructorDescription { + case secureValueTypeAddress + case secureValueTypeBankStatement + case secureValueTypeDriverLicense + case secureValueTypeEmail + case secureValueTypeIdentityCard + case secureValueTypeInternalPassport + case secureValueTypePassport + case secureValueTypePassportRegistration + case secureValueTypePersonalDetails + case secureValueTypePhone + case secureValueTypeRentalAgreement + case secureValueTypeTemporaryRegistration + case secureValueTypeUtilityBill public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .statsGroupTopPoster(let userId, let messages, let avgChars): + case .secureValueTypeAddress: + if boxed { + buffer.appendInt32(-874308058) + } + + break + case .secureValueTypeBankStatement: + if boxed { + buffer.appendInt32(-1995211763) + } + + break + case .secureValueTypeDriverLicense: + if boxed { + buffer.appendInt32(115615172) + } + + break + case .secureValueTypeEmail: if boxed { - buffer.appendInt32(-1660637285) + buffer.appendInt32(-1908627474) } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt32(messages, buffer: buffer, boxed: false) - serializeInt32(avgChars, buffer: buffer, boxed: false) + + break + case .secureValueTypeIdentityCard: + if boxed { + buffer.appendInt32(-1596951477) + } + + break + case .secureValueTypeInternalPassport: + if boxed { + buffer.appendInt32(-1717268701) + } + + break + case .secureValueTypePassport: + if boxed { + buffer.appendInt32(1034709504) + } + + break + case .secureValueTypePassportRegistration: + if boxed { + buffer.appendInt32(-1713143702) + } + + break + case .secureValueTypePersonalDetails: + if boxed { + buffer.appendInt32(-1658158621) + } + + break + case .secureValueTypePhone: + if boxed { + buffer.appendInt32(-1289704741) + } + + break + case .secureValueTypeRentalAgreement: + if boxed { + buffer.appendInt32(-1954007928) + } + + break + case .secureValueTypeTemporaryRegistration: + if boxed { + buffer.appendInt32(-368907213) + } + + break + case .secureValueTypeUtilityBill: + if boxed { + buffer.appendInt32(-63531698) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .statsGroupTopPoster(let userId, let messages, let avgChars): - return ("statsGroupTopPoster", [("userId", userId as Any), ("messages", messages as Any), ("avgChars", avgChars as Any)]) - } - } - - public static func parse_statsGroupTopPoster(_ reader: BufferReader) -> StatsGroupTopPoster? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StatsGroupTopPoster.statsGroupTopPoster(userId: _1!, messages: _2!, avgChars: _3!) - } - else { - return nil - } + case .secureValueTypeAddress: + return ("secureValueTypeAddress", []) + case .secureValueTypeBankStatement: + return ("secureValueTypeBankStatement", []) + case .secureValueTypeDriverLicense: + return ("secureValueTypeDriverLicense", []) + case .secureValueTypeEmail: + return ("secureValueTypeEmail", []) + case .secureValueTypeIdentityCard: + return ("secureValueTypeIdentityCard", []) + case .secureValueTypeInternalPassport: + return ("secureValueTypeInternalPassport", []) + case .secureValueTypePassport: + return ("secureValueTypePassport", []) + case .secureValueTypePassportRegistration: + return ("secureValueTypePassportRegistration", []) + case .secureValueTypePersonalDetails: + return ("secureValueTypePersonalDetails", []) + case .secureValueTypePhone: + return ("secureValueTypePhone", []) + case .secureValueTypeRentalAgreement: + return ("secureValueTypeRentalAgreement", []) + case .secureValueTypeTemporaryRegistration: + return ("secureValueTypeTemporaryRegistration", []) + case .secureValueTypeUtilityBill: + return ("secureValueTypeUtilityBill", []) + } + } + + public static func parse_secureValueTypeAddress(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeAddress + } + public static func parse_secureValueTypeBankStatement(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeBankStatement + } + public static func parse_secureValueTypeDriverLicense(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeDriverLicense + } + public static func parse_secureValueTypeEmail(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeEmail + } + public static func parse_secureValueTypeIdentityCard(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeIdentityCard + } + public static func parse_secureValueTypeInternalPassport(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeInternalPassport + } + public static func parse_secureValueTypePassport(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypePassport + } + public static func parse_secureValueTypePassportRegistration(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypePassportRegistration + } + public static func parse_secureValueTypePersonalDetails(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypePersonalDetails + } + public static func parse_secureValueTypePhone(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypePhone + } + public static func parse_secureValueTypeRentalAgreement(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeRentalAgreement + } + public static func parse_secureValueTypeTemporaryRegistration(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeTemporaryRegistration + } + public static func parse_secureValueTypeUtilityBill(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeUtilityBill } } diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index 330e0e1d870..733ac161c8c 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -1,35 +1,37 @@ public extension Api { - enum StatsPercentValue: TypeConstructorDescription { - case statsPercentValue(part: Double, total: Double) + enum SendAsPeer: TypeConstructorDescription { + case sendAsPeer(flags: Int32, peer: Api.Peer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .statsPercentValue(let part, let total): + case .sendAsPeer(let flags, let peer): if boxed { - buffer.appendInt32(-875679776) + buffer.appendInt32(-1206095820) } - serializeDouble(part, buffer: buffer, boxed: false) - serializeDouble(total, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .statsPercentValue(let part, let total): - return ("statsPercentValue", [("part", part as Any), ("total", total as Any)]) + case .sendAsPeer(let flags, let peer): + return ("sendAsPeer", [("flags", flags as Any), ("peer", peer as Any)]) } } - public static func parse_statsPercentValue(_ reader: BufferReader) -> StatsPercentValue? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() + public static func parse_sendAsPeer(_ reader: BufferReader) -> SendAsPeer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.StatsPercentValue.statsPercentValue(part: _1!, total: _2!) + return Api.SendAsPeer.sendAsPeer(flags: _1!, peer: _2!) } else { return nil @@ -39,825 +41,353 @@ public extension Api { } } public extension Api { - enum StatsURL: TypeConstructorDescription { - case statsURL(url: String) + enum SendMessageAction: TypeConstructorDescription { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageChooseStickerAction + case sendMessageEmojiInteraction(emoticon: String, msgId: Int32, interaction: Api.DataJSON) + case sendMessageEmojiInteractionSeen(emoticon: String) + case sendMessageGamePlayAction + case sendMessageGeoLocationAction + case sendMessageHistoryImportAction(progress: Int32) + case sendMessageRecordAudioAction + case sendMessageRecordRoundAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction(progress: Int32) + case sendMessageUploadDocumentAction(progress: Int32) + case sendMessageUploadPhotoAction(progress: Int32) + case sendMessageUploadRoundAction(progress: Int32) + case sendMessageUploadVideoAction(progress: Int32) + case speakingInGroupCallAction public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .statsURL(let url): + case .sendMessageCancelAction: if boxed { - buffer.appendInt32(1202287072) + buffer.appendInt32(-44119819) } - serializeString(url, buffer: buffer, boxed: false) + break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .statsURL(let url): - return ("statsURL", [("url", url as Any)]) - } - } - - public static func parse_statsURL(_ reader: BufferReader) -> StatsURL? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.StatsURL.statsURL(url: _1!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum StickerKeyword: TypeConstructorDescription { - case stickerKeyword(documentId: Int64, keyword: [String]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stickerKeyword(let documentId, let keyword): + case .sendMessageChooseContactAction: if boxed { - buffer.appendInt32(-50416996) + buffer.appendInt32(1653390447) } - serializeInt64(documentId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(keyword.count)) - for item in keyword { - serializeString(item, buffer: buffer, boxed: false) + + break + case .sendMessageChooseStickerAction: + if boxed { + buffer.appendInt32(-1336228175) } + break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .stickerKeyword(let documentId, let keyword): - return ("stickerKeyword", [("documentId", documentId as Any), ("keyword", keyword as Any)]) - } - } - - public static func parse_stickerKeyword(_ reader: BufferReader) -> StickerKeyword? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [String]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerKeyword.stickerKeyword(documentId: _1!, keyword: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum StickerPack: TypeConstructorDescription { - case stickerPack(emoticon: String, documents: [Int64]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stickerPack(let emoticon, let documents): + case .sendMessageEmojiInteraction(let emoticon, let msgId, let interaction): if boxed { - buffer.appendInt32(313694676) + buffer.appendInt32(630664139) } serializeString(emoticon, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documents.count)) - for item in documents { - serializeInt64(item, buffer: buffer, boxed: false) - } + serializeInt32(msgId, buffer: buffer, boxed: false) + interaction.serialize(buffer, true) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .stickerPack(let emoticon, let documents): - return ("stickerPack", [("emoticon", emoticon as Any), ("documents", documents as Any)]) - } - } - - public static func parse_stickerPack(_ reader: BufferReader) -> StickerPack? { - var _1: String? - _1 = parseString(reader) - var _2: [Int64]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerPack.stickerPack(emoticon: _1!, documents: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum StickerSet: TypeConstructorDescription { - case stickerSet(flags: Int32, installedDate: Int32?, id: Int64, accessHash: Int64, title: String, shortName: String, thumbs: [Api.PhotoSize]?, thumbDcId: Int32?, thumbVersion: Int32?, thumbDocumentId: Int64?, count: Int32, hash: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stickerSet(let flags, let installedDate, let id, let accessHash, let title, let shortName, let thumbs, let thumbDcId, let thumbVersion, let thumbDocumentId, let count, let hash): + case .sendMessageEmojiInteractionSeen(let emoticon): if boxed { - buffer.appendInt32(768691932) + buffer.appendInt32(-1234857938) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(installedDate!, buffer: buffer, boxed: false)} - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(shortName, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(thumbs!.count)) - for item in thumbs! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(thumbDcId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(thumbVersion!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 8) != 0 {serializeInt64(thumbDocumentId!, buffer: buffer, boxed: false)} - serializeInt32(count, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) + serializeString(emoticon, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .stickerSet(let flags, let installedDate, let id, let accessHash, let title, let shortName, let thumbs, let thumbDcId, let thumbVersion, let thumbDocumentId, let count, let hash): - return ("stickerSet", [("flags", flags as Any), ("installedDate", installedDate as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("title", title as Any), ("shortName", shortName as Any), ("thumbs", thumbs as Any), ("thumbDcId", thumbDcId as Any), ("thumbVersion", thumbVersion as Any), ("thumbDocumentId", thumbDocumentId as Any), ("count", count as Any), ("hash", hash as Any)]) - } - } - - public static func parse_stickerSet(_ reader: BufferReader) -> StickerSet? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - var _7: [Api.PhotoSize]? - if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) - } } - var _8: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() } - var _9: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_9 = reader.readInt32() } - var _10: Int64? - if Int(_1!) & Int(1 << 8) != 0 {_10 = reader.readInt64() } - var _11: Int32? - _11 = reader.readInt32() - var _12: Int32? - _12 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 8) == 0) || _10 != nil - let _c11 = _11 != nil - let _c12 = _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.StickerSet.stickerSet(flags: _1!, installedDate: _2, id: _3!, accessHash: _4!, title: _5!, shortName: _6!, thumbs: _7, thumbDcId: _8, thumbVersion: _9, thumbDocumentId: _10, count: _11!, hash: _12!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum StickerSetCovered: TypeConstructorDescription { - case stickerSetCovered(set: Api.StickerSet, cover: Api.Document) - case stickerSetFullCovered(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) - case stickerSetMultiCovered(set: Api.StickerSet, covers: [Api.Document]) - case stickerSetNoCovered(set: Api.StickerSet) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stickerSetCovered(let set, let cover): + case .sendMessageGamePlayAction: if boxed { - buffer.appendInt32(1678812626) + buffer.appendInt32(-580219064) } - set.serialize(buffer, true) - cover.serialize(buffer, true) + break - case .stickerSetFullCovered(let set, let packs, let keywords, let documents): + case .sendMessageGeoLocationAction: if boxed { - buffer.appendInt32(1087454222) + buffer.appendInt32(393186209) } - set.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(packs.count)) - for item in packs { - item.serialize(buffer, true) + + break + case .sendMessageHistoryImportAction(let progress): + if boxed { + buffer.appendInt32(-606432698) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(keywords.count)) - for item in keywords { - item.serialize(buffer, true) + serializeInt32(progress, buffer: buffer, boxed: false) + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documents.count)) - for item in documents { - item.serialize(buffer, true) + + break + case .sendMessageRecordRoundAction: + if boxed { + buffer.appendInt32(-1997373508) } + break - case .stickerSetMultiCovered(let set, let covers): + case .sendMessageRecordVideoAction: if boxed { - buffer.appendInt32(872932635) + buffer.appendInt32(-1584933265) } - set.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(covers.count)) - for item in covers { - item.serialize(buffer, true) + + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) } + break - case .stickerSetNoCovered(let set): + case .sendMessageUploadAudioAction(let progress): if boxed { - buffer.appendInt32(2008112412) + buffer.appendInt32(-212740181) } - set.serialize(buffer, true) + serializeInt32(progress, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .stickerSetCovered(let set, let cover): - return ("stickerSetCovered", [("set", set as Any), ("cover", cover as Any)]) - case .stickerSetFullCovered(let set, let packs, let keywords, let documents): - return ("stickerSetFullCovered", [("set", set as Any), ("packs", packs as Any), ("keywords", keywords as Any), ("documents", documents as Any)]) - case .stickerSetMultiCovered(let set, let covers): - return ("stickerSetMultiCovered", [("set", set as Any), ("covers", covers as Any)]) - case .stickerSetNoCovered(let set): - return ("stickerSetNoCovered", [("set", set as Any)]) - } - } - - public static func parse_stickerSetCovered(_ reader: BufferReader) -> StickerSetCovered? { - var _1: Api.StickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StickerSet - } - var _2: Api.Document? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Document - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerSetCovered.stickerSetCovered(set: _1!, cover: _2!) - } - else { - return nil - } - } - public static func parse_stickerSetFullCovered(_ reader: BufferReader) -> StickerSetCovered? { - var _1: Api.StickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StickerSet - } - var _2: [Api.StickerPack]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) - } - var _3: [Api.StickerKeyword]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self) - } - var _4: [Api.Document]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StickerSetCovered.stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!) - } - else { - return nil - } - } - public static func parse_stickerSetMultiCovered(_ reader: BufferReader) -> StickerSetCovered? { - var _1: Api.StickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StickerSet - } - var _2: [Api.Document]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerSetCovered.stickerSetMultiCovered(set: _1!, covers: _2!) - } - else { - return nil - } - } - public static func parse_stickerSetNoCovered(_ reader: BufferReader) -> StickerSetCovered? { - var _1: Api.StickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StickerSet - } - let _c1 = _1 != nil - if _c1 { - return Api.StickerSetCovered.stickerSetNoCovered(set: _1!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum StoriesStealthMode: TypeConstructorDescription { - case storiesStealthMode(flags: Int32, activeUntilDate: Int32?, cooldownUntilDate: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storiesStealthMode(let flags, let activeUntilDate, let cooldownUntilDate): + case .sendMessageUploadDocumentAction(let progress): if boxed { - buffer.appendInt32(1898850301) + buffer.appendInt32(-1441998364) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(activeUntilDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(cooldownUntilDate!, buffer: buffer, boxed: false)} + serializeInt32(progress, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .storiesStealthMode(let flags, let activeUntilDate, let cooldownUntilDate): - return ("storiesStealthMode", [("flags", flags as Any), ("activeUntilDate", activeUntilDate as Any), ("cooldownUntilDate", cooldownUntilDate as Any)]) - } - } - - public static func parse_storiesStealthMode(_ reader: BufferReader) -> StoriesStealthMode? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoriesStealthMode.storiesStealthMode(flags: _1!, activeUntilDate: _2, cooldownUntilDate: _3) - } - else { - return nil - } - } - - } -} -public extension Api { - enum StoryFwdHeader: TypeConstructorDescription { - case storyFwdHeader(flags: Int32, from: Api.Peer?, fromName: String?, storyId: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyFwdHeader(let flags, let from, let fromName, let storyId): + case .sendMessageUploadPhotoAction(let progress): if boxed { - buffer.appendInt32(-1205411504) + buffer.appendInt32(-774682074) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {from!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(fromName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(storyId!, buffer: buffer, boxed: false)} + serializeInt32(progress, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .storyFwdHeader(let flags, let from, let fromName, let storyId): - return ("storyFwdHeader", [("flags", flags as Any), ("from", from as Any), ("fromName", fromName as Any), ("storyId", storyId as Any)]) - } - } - - public static func parse_storyFwdHeader(_ reader: BufferReader) -> StoryFwdHeader? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StoryFwdHeader.storyFwdHeader(flags: _1!, from: _2, fromName: _3, storyId: _4) - } - else { - return nil - } - } - - } -} -public extension Api { - indirect enum StoryItem: TypeConstructorDescription { - case storyItem(flags: Int32, id: Int32, date: Int32, fromId: Api.Peer?, fwdFrom: Api.StoryFwdHeader?, expireDate: Int32, caption: String?, entities: [Api.MessageEntity]?, media: Api.MessageMedia, mediaAreas: [Api.MediaArea]?, privacy: [Api.PrivacyRule]?, views: Api.StoryViews?, sentReaction: Api.Reaction?) - case storyItemDeleted(id: Int32) - case storyItemSkipped(flags: Int32, id: Int32, date: Int32, expireDate: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyItem(let flags, let id, let date, let fromId, let fwdFrom, let expireDate, let caption, let entities, let media, let mediaAreas, let privacy, let views, let sentReaction): + case .sendMessageUploadRoundAction(let progress): if boxed { - buffer.appendInt32(2041735716) + buffer.appendInt32(608050278) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 18) != 0 {fromId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {fwdFrom!.serialize(buffer, true)} - serializeInt32(expireDate, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(caption!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - media.serialize(buffer, true) - if Int(flags) & Int(1 << 14) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(mediaAreas!.count)) - for item in mediaAreas! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(privacy!.count)) - for item in privacy! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 3) != 0 {views!.serialize(buffer, true)} - if Int(flags) & Int(1 << 15) != 0 {sentReaction!.serialize(buffer, true)} + serializeInt32(progress, buffer: buffer, boxed: false) break - case .storyItemDeleted(let id): + case .sendMessageUploadVideoAction(let progress): if boxed { - buffer.appendInt32(1374088783) + buffer.appendInt32(-378127636) } - serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(progress, buffer: buffer, boxed: false) break - case .storyItemSkipped(let flags, let id, let date, let expireDate): + case .speakingInGroupCallAction: if boxed { - buffer.appendInt32(-5388013) + buffer.appendInt32(-651419003) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(expireDate, buffer: buffer, boxed: false) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .storyItem(let flags, let id, let date, let fromId, let fwdFrom, let expireDate, let caption, let entities, let media, let mediaAreas, let privacy, let views, let sentReaction): - return ("storyItem", [("flags", flags as Any), ("id", id as Any), ("date", date as Any), ("fromId", fromId as Any), ("fwdFrom", fwdFrom as Any), ("expireDate", expireDate as Any), ("caption", caption as Any), ("entities", entities as Any), ("media", media as Any), ("mediaAreas", mediaAreas as Any), ("privacy", privacy as Any), ("views", views as Any), ("sentReaction", sentReaction as Any)]) - case .storyItemDeleted(let id): - return ("storyItemDeleted", [("id", id as Any)]) - case .storyItemSkipped(let flags, let id, let date, let expireDate): - return ("storyItemSkipped", [("flags", flags as Any), ("id", id as Any), ("date", date as Any), ("expireDate", expireDate as Any)]) - } - } - - public static func parse_storyItem(_ reader: BufferReader) -> StoryItem? { - var _1: Int32? - _1 = reader.readInt32() + case .sendMessageCancelAction: + return ("sendMessageCancelAction", []) + case .sendMessageChooseContactAction: + return ("sendMessageChooseContactAction", []) + case .sendMessageChooseStickerAction: + return ("sendMessageChooseStickerAction", []) + case .sendMessageEmojiInteraction(let emoticon, let msgId, let interaction): + return ("sendMessageEmojiInteraction", [("emoticon", emoticon as Any), ("msgId", msgId as Any), ("interaction", interaction as Any)]) + case .sendMessageEmojiInteractionSeen(let emoticon): + return ("sendMessageEmojiInteractionSeen", [("emoticon", emoticon as Any)]) + case .sendMessageGamePlayAction: + return ("sendMessageGamePlayAction", []) + case .sendMessageGeoLocationAction: + return ("sendMessageGeoLocationAction", []) + case .sendMessageHistoryImportAction(let progress): + return ("sendMessageHistoryImportAction", [("progress", progress as Any)]) + case .sendMessageRecordAudioAction: + return ("sendMessageRecordAudioAction", []) + case .sendMessageRecordRoundAction: + return ("sendMessageRecordRoundAction", []) + case .sendMessageRecordVideoAction: + return ("sendMessageRecordVideoAction", []) + case .sendMessageTypingAction: + return ("sendMessageTypingAction", []) + case .sendMessageUploadAudioAction(let progress): + return ("sendMessageUploadAudioAction", [("progress", progress as Any)]) + case .sendMessageUploadDocumentAction(let progress): + return ("sendMessageUploadDocumentAction", [("progress", progress as Any)]) + case .sendMessageUploadPhotoAction(let progress): + return ("sendMessageUploadPhotoAction", [("progress", progress as Any)]) + case .sendMessageUploadRoundAction(let progress): + return ("sendMessageUploadRoundAction", [("progress", progress as Any)]) + case .sendMessageUploadVideoAction(let progress): + return ("sendMessageUploadVideoAction", [("progress", progress as Any)]) + case .speakingInGroupCallAction: + return ("speakingInGroupCallAction", []) + } + } + + public static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageCancelAction + } + public static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageChooseContactAction + } + public static func parse_sendMessageChooseStickerAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageChooseStickerAction + } + public static func parse_sendMessageEmojiInteraction(_ reader: BufferReader) -> SendMessageAction? { + var _1: String? + _1 = parseString(reader) var _2: Int32? _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Peer? - if Int(_1!) & Int(1 << 18) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _5: Api.StoryFwdHeader? - if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.StoryFwdHeader - } } - var _6: Int32? - _6 = reader.readInt32() - var _7: String? - if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) } - var _8: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - var _9: Api.MessageMedia? + var _3: Api.DataJSON? if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageMedia + _3 = Api.parse(reader, signature: signature) as? Api.DataJSON } - var _10: [Api.MediaArea]? - if Int(_1!) & Int(1 << 14) != 0 {if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MediaArea.self) - } } - var _11: [Api.PrivacyRule]? - if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() { - _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) - } } - var _12: Api.StoryViews? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.StoryViews - } } - var _13: Api.Reaction? - if Int(_1!) & Int(1 << 15) != 0 {if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.Reaction - } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 18) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 17) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 2) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.StoryItem.storyItem(flags: _1!, id: _2!, date: _3!, fromId: _4, fwdFrom: _5, expireDate: _6!, caption: _7, entities: _8, media: _9!, mediaAreas: _10, privacy: _11, views: _12, sentReaction: _13) + if _c1 && _c2 && _c3 { + return Api.SendMessageAction.sendMessageEmojiInteraction(emoticon: _1!, msgId: _2!, interaction: _3!) } else { return nil } } - public static func parse_storyItemDeleted(_ reader: BufferReader) -> StoryItem? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_sendMessageEmojiInteractionSeen(_ reader: BufferReader) -> SendMessageAction? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.StoryItem.storyItemDeleted(id: _1!) + return Api.SendMessageAction.sendMessageEmojiInteractionSeen(emoticon: _1!) } else { return nil } } - public static func parse_storyItemSkipped(_ reader: BufferReader) -> StoryItem? { + public static func parse_sendMessageGamePlayAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageGamePlayAction + } + public static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageGeoLocationAction + } + public static func parse_sendMessageHistoryImportAction(_ reader: BufferReader) -> SendMessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StoryItem.storyItemSkipped(flags: _1!, id: _2!, date: _3!, expireDate: _4!) + if _c1 { + return Api.SendMessageAction.sendMessageHistoryImportAction(progress: _1!) } else { return nil } } - - } -} -public extension Api { - indirect enum StoryReaction: TypeConstructorDescription { - case storyReaction(peerId: Api.Peer, date: Int32, reaction: Api.Reaction) - case storyReactionPublicForward(message: Api.Message) - case storyReactionPublicRepost(peerId: Api.Peer, story: Api.StoryItem) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyReaction(let peerId, let date, let reaction): - if boxed { - buffer.appendInt32(1620104917) - } - peerId.serialize(buffer, true) - serializeInt32(date, buffer: buffer, boxed: false) - reaction.serialize(buffer, true) - break - case .storyReactionPublicForward(let message): - if boxed { - buffer.appendInt32(-1146411453) - } - message.serialize(buffer, true) - break - case .storyReactionPublicRepost(let peerId, let story): - if boxed { - buffer.appendInt32(-808644845) - } - peerId.serialize(buffer, true) - story.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .storyReaction(let peerId, let date, let reaction): - return ("storyReaction", [("peerId", peerId as Any), ("date", date as Any), ("reaction", reaction as Any)]) - case .storyReactionPublicForward(let message): - return ("storyReactionPublicForward", [("message", message as Any)]) - case .storyReactionPublicRepost(let peerId, let story): - return ("storyReactionPublicRepost", [("peerId", peerId as Any), ("story", story as Any)]) - } - } - - public static func parse_storyReaction(_ reader: BufferReader) -> StoryReaction? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + public static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageRecordAudioAction + } + public static func parse_sendMessageRecordRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageRecordRoundAction + } + public static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageRecordVideoAction + } + public static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageTypingAction + } + public static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.SendMessageAction.sendMessageUploadAudioAction(progress: _1!) } - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Reaction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Reaction + else { + return nil } + } + public static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoryReaction.storyReaction(peerId: _1!, date: _2!, reaction: _3!) + if _c1 { + return Api.SendMessageAction.sendMessageUploadDocumentAction(progress: _1!) } else { return nil } } - public static func parse_storyReactionPublicForward(_ reader: BufferReader) -> StoryReaction? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } + public static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { - return Api.StoryReaction.storyReactionPublicForward(message: _1!) + return Api.SendMessageAction.sendMessageUploadPhotoAction(progress: _1!) } else { return nil } } - public static func parse_storyReactionPublicRepost(_ reader: BufferReader) -> StoryReaction? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + public static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.SendMessageAction.sendMessageUploadRoundAction(progress: _1!) } - var _2: Api.StoryItem? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StoryItem + else { + return nil } + } + public static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StoryReaction.storyReactionPublicRepost(peerId: _1!, story: _2!) + if _c1 { + return Api.SendMessageAction.sendMessageUploadVideoAction(progress: _1!) } else { return nil } } + public static func parse_speakingInGroupCallAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.speakingInGroupCallAction + } } } public extension Api { - indirect enum StoryView: TypeConstructorDescription { - case storyView(flags: Int32, userId: Int64, date: Int32, reaction: Api.Reaction?) - case storyViewPublicForward(flags: Int32, message: Api.Message) - case storyViewPublicRepost(flags: Int32, peerId: Api.Peer, story: Api.StoryItem) + enum ShippingOption: TypeConstructorDescription { + case shippingOption(id: String, title: String, prices: [Api.LabeledPrice]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .storyView(let flags, let userId, let date, let reaction): - if boxed { - buffer.appendInt32(-1329730875) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {reaction!.serialize(buffer, true)} - break - case .storyViewPublicForward(let flags, let message): + case .shippingOption(let id, let title, let prices): if boxed { - buffer.appendInt32(-1870436597) + buffer.appendInt32(-1239335713) } - serializeInt32(flags, buffer: buffer, boxed: false) - message.serialize(buffer, true) - break - case .storyViewPublicRepost(let flags, let peerId, let story): - if boxed { - buffer.appendInt32(-1116418231) + serializeString(id, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(prices.count)) + for item in prices { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) - peerId.serialize(buffer, true) - story.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .storyView(let flags, let userId, let date, let reaction): - return ("storyView", [("flags", flags as Any), ("userId", userId as Any), ("date", date as Any), ("reaction", reaction as Any)]) - case .storyViewPublicForward(let flags, let message): - return ("storyViewPublicForward", [("flags", flags as Any), ("message", message as Any)]) - case .storyViewPublicRepost(let flags, let peerId, let story): - return ("storyViewPublicRepost", [("flags", flags as Any), ("peerId", peerId as Any), ("story", story as Any)]) + case .shippingOption(let id, let title, let prices): + return ("shippingOption", [("id", id as Any), ("title", title as Any), ("prices", prices as Any)]) } } - public static func parse_storyView(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Reaction? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Reaction - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StoryView.storyView(flags: _1!, userId: _2!, date: _3!, reaction: _4) - } - else { - return nil - } - } - public static func parse_storyViewPublicForward(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Message? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StoryView.storyViewPublicForward(flags: _1!, message: _2!) - } - else { - return nil - } - } - public static func parse_storyViewPublicRepost(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Api.StoryItem? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StoryItem + public static func parse_shippingOption(_ reader: BufferReader) -> ShippingOption? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: [Api.LabeledPrice]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LabeledPrice.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.StoryView.storyViewPublicRepost(flags: _1!, peerId: _2!, story: _3!) + return Api.ShippingOption.shippingOption(id: _1!, title: _2!, prices: _3!) } else { return nil @@ -867,65 +397,33 @@ public extension Api { } } public extension Api { - enum StoryViews: TypeConstructorDescription { - case storyViews(flags: Int32, viewsCount: Int32, forwardsCount: Int32?, reactions: [Api.ReactionCount]?, reactionsCount: Int32?, recentViewers: [Int64]?) + enum SimpleWebViewResult: TypeConstructorDescription { + case simpleWebViewResultUrl(url: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): + case .simpleWebViewResultUrl(let url): if boxed { - buffer.appendInt32(-1923523370) + buffer.appendInt32(-2010155333) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(viewsCount, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(forwardsCount!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reactions!.count)) - for item in reactions! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(reactionsCount!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentViewers!.count)) - for item in recentViewers! { - serializeInt64(item, buffer: buffer, boxed: false) - }} + serializeString(url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): - return ("storyViews", [("flags", flags as Any), ("viewsCount", viewsCount as Any), ("forwardsCount", forwardsCount as Any), ("reactions", reactions as Any), ("reactionsCount", reactionsCount as Any), ("recentViewers", recentViewers as Any)]) + case .simpleWebViewResultUrl(let url): + return ("simpleWebViewResultUrl", [("url", url as Any)]) } } - public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } - var _4: [Api.ReactionCount]? - if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) - } } - var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } - var _6: [Int64]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } } + public static func parse_simpleWebViewResultUrl(_ reader: BufferReader) -> SimpleWebViewResult? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StoryViews.storyViews(flags: _1!, viewsCount: _2!, forwardsCount: _3, reactions: _4, reactionsCount: _5, recentViewers: _6) + if _c1 { + return Api.SimpleWebViewResult.simpleWebViewResultUrl(url: _1!) } else { return nil @@ -935,43 +433,41 @@ public extension Api { } } public extension Api { - enum TextWithEntities: TypeConstructorDescription { - case textWithEntities(text: String, entities: [Api.MessageEntity]) + enum SmsJob: TypeConstructorDescription { + case smsJob(jobId: String, phoneNumber: String, text: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .textWithEntities(let text, let entities): + case .smsJob(let jobId, let phoneNumber, let text): if boxed { - buffer.appendInt32(1964978502) + buffer.appendInt32(-425595208) } + serializeString(jobId, buffer: buffer, boxed: false) + serializeString(phoneNumber, buffer: buffer, boxed: false) serializeString(text, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities.count)) - for item in entities { - item.serialize(buffer, true) - } break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .textWithEntities(let text, let entities): - return ("textWithEntities", [("text", text as Any), ("entities", entities as Any)]) + case .smsJob(let jobId, let phoneNumber, let text): + return ("smsJob", [("jobId", jobId as Any), ("phoneNumber", phoneNumber as Any), ("text", text as Any)]) } } - public static func parse_textWithEntities(_ reader: BufferReader) -> TextWithEntities? { + public static func parse_smsJob(_ reader: BufferReader) -> SmsJob? { var _1: String? _1 = parseString(reader) - var _2: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.TextWithEntities.textWithEntities(text: _1!, entities: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SmsJob.smsJob(jobId: _1!, phoneNumber: _2!, text: _3!) } else { return nil @@ -981,73 +477,83 @@ public extension Api { } } public extension Api { - enum Theme: TypeConstructorDescription { - case theme(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: [Api.ThemeSettings]?, emoticon: String?, installsCount: Int32?) + enum SponsoredMessage: TypeConstructorDescription { + case sponsoredMessage(flags: Int32, randomId: Buffer, url: String, title: String, message: String, entities: [Api.MessageEntity]?, photo: Api.Photo?, color: Api.PeerColor?, buttonText: String, sponsorInfo: String?, additionalInfo: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let emoticon, let installsCount): + case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let color, let buttonText, let sponsorInfo, let additionalInfo): if boxed { - buffer.appendInt32(-1609668650) + buffer.appendInt32(-1108478618) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeString(slug, buffer: buffer, boxed: false) + serializeBytes(randomId, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(settings!.count)) - for item in settings! { + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { item.serialize(buffer, true) }} - if Int(flags) & Int(1 << 6) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(installsCount!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {photo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 13) != 0 {color!.serialize(buffer, true)} + serializeString(buttonText, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 7) != 0 {serializeString(sponsorInfo!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 8) != 0 {serializeString(additionalInfo!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let emoticon, let installsCount): - return ("theme", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("slug", slug as Any), ("title", title as Any), ("document", document as Any), ("settings", settings as Any), ("emoticon", emoticon as Any), ("installsCount", installsCount as Any)]) + case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let color, let buttonText, let sponsorInfo, let additionalInfo): + return ("sponsoredMessage", [("flags", flags as Any), ("randomId", randomId as Any), ("url", url as Any), ("title", title as Any), ("message", message as Any), ("entities", entities as Any), ("photo", photo as Any), ("color", color as Any), ("buttonText", buttonText as Any), ("sponsorInfo", sponsorInfo as Any), ("additionalInfo", additionalInfo as Any)]) } } - public static func parse_theme(_ reader: BufferReader) -> Theme? { + public static func parse_sponsoredMessage(_ reader: BufferReader) -> SponsoredMessage? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) var _4: String? _4 = parseString(reader) var _5: String? _5 = parseString(reader) - var _6: Api.Document? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Document + var _6: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } - var _7: [Api.ThemeSettings]? - if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ThemeSettings.self) + var _7: Api.Photo? + if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Photo } } - var _8: String? - if Int(_1!) & Int(1 << 6) != 0 {_8 = parseString(reader) } - var _9: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_9 = reader.readInt32() } + var _8: Api.PeerColor? + if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.PeerColor + } } + var _9: String? + _9 = parseString(reader) + var _10: String? + if Int(_1!) & Int(1 << 7) != 0 {_10 = parseString(reader) } + var _11: String? + if Int(_1!) & Int(1 << 8) != 0 {_11 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, emoticon: _8, installsCount: _9) + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 13) == 0) || _8 != nil + let _c9 = _9 != nil + let _c10 = (Int(_1!) & Int(1 << 7) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 8) == 0) || _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, url: _3!, title: _4!, message: _5!, entities: _6, photo: _7, color: _8, buttonText: _9!, sponsorInfo: _10, additionalInfo: _11) } else { return nil @@ -1057,63 +563,37 @@ public extension Api { } } public extension Api { - enum ThemeSettings: TypeConstructorDescription { - case themeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, outboxAccentColor: Int32?, messageColors: [Int32]?, wallpaper: Api.WallPaper?) + enum SponsoredMessageReportOption: TypeConstructorDescription { + case sponsoredMessageReportOption(text: String, option: Buffer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper): + case .sponsoredMessageReportOption(let text, let option): if boxed { - buffer.appendInt32(-94849324) + buffer.appendInt32(1124938064) } - serializeInt32(flags, buffer: buffer, boxed: false) - baseTheme.serialize(buffer, true) - serializeInt32(accentColor, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(outboxAccentColor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messageColors!.count)) - for item in messageColors! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - if Int(flags) & Int(1 << 1) != 0 {wallpaper!.serialize(buffer, true)} + serializeString(text, buffer: buffer, boxed: false) + serializeBytes(option, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper): - return ("themeSettings", [("flags", flags as Any), ("baseTheme", baseTheme as Any), ("accentColor", accentColor as Any), ("outboxAccentColor", outboxAccentColor as Any), ("messageColors", messageColors as Any), ("wallpaper", wallpaper as Any)]) + case .sponsoredMessageReportOption(let text, let option): + return ("sponsoredMessageReportOption", [("text", text as Any), ("option", option as Any)]) } } - public static func parse_themeSettings(_ reader: BufferReader) -> ThemeSettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.BaseTheme? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BaseTheme - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() } - var _5: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } } - var _6: Api.WallPaper? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WallPaper - } } + public static func parse_sponsoredMessageReportOption(_ reader: BufferReader) -> SponsoredMessageReportOption? { + var _1: String? + _1 = parseString(reader) + var _2: Buffer? + _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.ThemeSettings.themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6) + if _c1 && _c2 { + return Api.SponsoredMessageReportOption.sponsoredMessageReportOption(text: _1!, option: _2!) } else { return nil @@ -1123,41 +603,49 @@ public extension Api { } } public extension Api { - enum Timezone: TypeConstructorDescription { - case timezone(id: String, name: String, utcOffset: Int32) + enum StarsTopupOption: TypeConstructorDescription { + case starsTopupOption(flags: Int32, stars: Int64, storeProduct: String?, currency: String, amount: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .timezone(let id, let name, let utcOffset): + case .starsTopupOption(let flags, let stars, let storeProduct, let currency, let amount): if boxed { - buffer.appendInt32(-7173643) + buffer.appendInt32(198776256) } - serializeString(id, buffer: buffer, boxed: false) - serializeString(name, buffer: buffer, boxed: false) - serializeInt32(utcOffset, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(stars, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)} + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .timezone(let id, let name, let utcOffset): - return ("timezone", [("id", id as Any), ("name", name as Any), ("utcOffset", utcOffset as Any)]) + case .starsTopupOption(let flags, let stars, let storeProduct, let currency, let amount): + return ("starsTopupOption", [("flags", flags as Any), ("stars", stars as Any), ("storeProduct", storeProduct as Any), ("currency", currency as Any), ("amount", amount as Any)]) } } - public static func parse_timezone(_ reader: BufferReader) -> Timezone? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() + public static func parse_starsTopupOption(_ reader: BufferReader) -> StarsTopupOption? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Timezone.timezone(id: _1!, name: _2!, utcOffset: _3!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.StarsTopupOption.starsTopupOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!) } else { return nil @@ -1167,39 +655,65 @@ public extension Api { } } public extension Api { - enum TopPeer: TypeConstructorDescription { - case topPeer(peer: Api.Peer, rating: Double) + enum StarsTransaction: TypeConstructorDescription { + case starsTransaction(flags: Int32, id: String, stars: Int64, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .topPeer(let peer, let rating): + case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo): if boxed { - buffer.appendInt32(-305282981) + buffer.appendInt32(-865044046) } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(id, buffer: buffer, boxed: false) + serializeInt64(stars, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) peer.serialize(buffer, true) - serializeDouble(rating, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(description!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .topPeer(let peer, let rating): - return ("topPeer", [("peer", peer as Any), ("rating", rating as Any)]) + case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo): + return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any)]) } } - public static func parse_topPeer(_ reader: BufferReader) -> TopPeer? { - var _1: Api.Peer? + public static func parse_starsTransaction(_ reader: BufferReader) -> StarsTransaction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Api.StarsTransactionPeer? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + _5 = Api.parse(reader, signature: signature) as? Api.StarsTransactionPeer } - var _2: Double? - _2 = reader.readDouble() + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } + var _7: String? + if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } + var _8: Api.WebDocument? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.TopPeer.topPeer(peer: _1!, rating: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8) } else { return nil @@ -1209,63 +723,49 @@ public extension Api { } } public extension Api { - enum TopPeerCategory: TypeConstructorDescription { - case topPeerCategoryBotsInline - case topPeerCategoryBotsPM - case topPeerCategoryChannels - case topPeerCategoryCorrespondents - case topPeerCategoryForwardChats - case topPeerCategoryForwardUsers - case topPeerCategoryGroups - case topPeerCategoryPhoneCalls + enum StarsTransactionPeer: TypeConstructorDescription { + case starsTransactionPeer(peer: Api.Peer) + case starsTransactionPeerAppStore + case starsTransactionPeerFragment + case starsTransactionPeerPlayMarket + case starsTransactionPeerPremiumBot + case starsTransactionPeerUnsupported public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .topPeerCategoryBotsInline: - if boxed { - buffer.appendInt32(344356834) - } - - break - case .topPeerCategoryBotsPM: - if boxed { - buffer.appendInt32(-1419371685) - } - - break - case .topPeerCategoryChannels: + case .starsTransactionPeer(let peer): if boxed { - buffer.appendInt32(371037736) + buffer.appendInt32(-670195363) } - + peer.serialize(buffer, true) break - case .topPeerCategoryCorrespondents: + case .starsTransactionPeerAppStore: if boxed { - buffer.appendInt32(104314861) + buffer.appendInt32(-1269320843) } break - case .topPeerCategoryForwardChats: + case .starsTransactionPeerFragment: if boxed { - buffer.appendInt32(-68239120) + buffer.appendInt32(-382740222) } break - case .topPeerCategoryForwardUsers: + case .starsTransactionPeerPlayMarket: if boxed { - buffer.appendInt32(-1472172887) + buffer.appendInt32(2069236235) } break - case .topPeerCategoryGroups: + case .starsTransactionPeerPremiumBot: if boxed { - buffer.appendInt32(-1122524854) + buffer.appendInt32(621656824) } break - case .topPeerCategoryPhoneCalls: + case .starsTransactionPeerUnsupported: if boxed { - buffer.appendInt32(511092620) + buffer.appendInt32(-1779253276) } break @@ -1274,48 +774,48 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .topPeerCategoryBotsInline: - return ("topPeerCategoryBotsInline", []) - case .topPeerCategoryBotsPM: - return ("topPeerCategoryBotsPM", []) - case .topPeerCategoryChannels: - return ("topPeerCategoryChannels", []) - case .topPeerCategoryCorrespondents: - return ("topPeerCategoryCorrespondents", []) - case .topPeerCategoryForwardChats: - return ("topPeerCategoryForwardChats", []) - case .topPeerCategoryForwardUsers: - return ("topPeerCategoryForwardUsers", []) - case .topPeerCategoryGroups: - return ("topPeerCategoryGroups", []) - case .topPeerCategoryPhoneCalls: - return ("topPeerCategoryPhoneCalls", []) - } - } - - public static func parse_topPeerCategoryBotsInline(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryBotsInline - } - public static func parse_topPeerCategoryBotsPM(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryBotsPM - } - public static func parse_topPeerCategoryChannels(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryChannels + case .starsTransactionPeer(let peer): + return ("starsTransactionPeer", [("peer", peer as Any)]) + case .starsTransactionPeerAppStore: + return ("starsTransactionPeerAppStore", []) + case .starsTransactionPeerFragment: + return ("starsTransactionPeerFragment", []) + case .starsTransactionPeerPlayMarket: + return ("starsTransactionPeerPlayMarket", []) + case .starsTransactionPeerPremiumBot: + return ("starsTransactionPeerPremiumBot", []) + case .starsTransactionPeerUnsupported: + return ("starsTransactionPeerUnsupported", []) + } + } + + public static func parse_starsTransactionPeer(_ reader: BufferReader) -> StarsTransactionPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + let _c1 = _1 != nil + if _c1 { + return Api.StarsTransactionPeer.starsTransactionPeer(peer: _1!) + } + else { + return nil + } } - public static func parse_topPeerCategoryCorrespondents(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryCorrespondents + public static func parse_starsTransactionPeerAppStore(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerAppStore } - public static func parse_topPeerCategoryForwardChats(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryForwardChats + public static func parse_starsTransactionPeerFragment(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerFragment } - public static func parse_topPeerCategoryForwardUsers(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryForwardUsers + public static func parse_starsTransactionPeerPlayMarket(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerPlayMarket } - public static func parse_topPeerCategoryGroups(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryGroups + public static func parse_starsTransactionPeerPremiumBot(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerPremiumBot } - public static func parse_topPeerCategoryPhoneCalls(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryPhoneCalls + public static func parse_starsTransactionPeerUnsupported(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerUnsupported } } diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index eb7a764d63d..f42e53ce099 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -1,47 +1,75 @@ public extension Api { - enum TopPeerCategoryPeers: TypeConstructorDescription { - case topPeerCategoryPeers(category: Api.TopPeerCategory, count: Int32, peers: [Api.TopPeer]) + enum StatsAbsValueAndPrev: TypeConstructorDescription { + case statsAbsValueAndPrev(current: Double, previous: Double) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .topPeerCategoryPeers(let category, let count, let peers): + case .statsAbsValueAndPrev(let current, let previous): if boxed { - buffer.appendInt32(-75283823) - } - category.serialize(buffer, true) - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) + buffer.appendInt32(-884757282) } + serializeDouble(current, buffer: buffer, boxed: false) + serializeDouble(previous, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .topPeerCategoryPeers(let category, let count, let peers): - return ("topPeerCategoryPeers", [("category", category as Any), ("count", count as Any), ("peers", peers as Any)]) + case .statsAbsValueAndPrev(let current, let previous): + return ("statsAbsValueAndPrev", [("current", current as Any), ("previous", previous as Any)]) } } - public static func parse_topPeerCategoryPeers(_ reader: BufferReader) -> TopPeerCategoryPeers? { - var _1: Api.TopPeerCategory? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.TopPeerCategory + public static func parse_statsAbsValueAndPrev(_ reader: BufferReader) -> StatsAbsValueAndPrev? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StatsAbsValueAndPrev.statsAbsValueAndPrev(current: _1!, previous: _2!) } + else { + return nil + } + } + + } +} +public extension Api { + enum StatsDateRangeDays: TypeConstructorDescription { + case statsDateRangeDays(minDate: Int32, maxDate: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsDateRangeDays(let minDate, let maxDate): + if boxed { + buffer.appendInt32(-1237848657) + } + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(maxDate, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .statsDateRangeDays(let minDate, let maxDate): + return ("statsDateRangeDays", [("minDate", minDate as Any), ("maxDate", maxDate as Any)]) + } + } + + public static func parse_statsDateRangeDays(_ reader: BufferReader) -> StatsDateRangeDays? { + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: [Api.TopPeer]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeer.self) - } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.TopPeerCategoryPeers.topPeerCategoryPeers(category: _1!, count: _2!, peers: _3!) + if _c1 && _c2 { + return Api.StatsDateRangeDays.statsDateRangeDays(minDate: _1!, maxDate: _2!) } else { return nil @@ -51,3782 +79,963 @@ public extension Api { } } public extension Api { - indirect enum Update: TypeConstructorDescription { - case updateAttachMenuBots - case updateAutoSaveSettings - case updateBotBusinessConnect(connection: Api.BotBusinessConnection, qts: Int32) - case updateBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, peer: Api.Peer, msgId: Int32, chatInstance: Int64, data: Buffer?, gameShortName: String?) - case updateBotChatBoost(peer: Api.Peer, boost: Api.Boost, qts: Int32) - case updateBotChatInviteRequester(peer: Api.Peer, date: Int32, userId: Int64, about: String, invite: Api.ExportedChatInvite, qts: Int32) - case updateBotCommands(peer: Api.Peer, botId: Int64, commands: [Api.BotCommand]) - case updateBotDeleteBusinessMessage(connectionId: String, peer: Api.Peer, messages: [Int32], qts: Int32) - case updateBotEditBusinessMessage(flags: Int32, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, qts: Int32) - case updateBotInlineQuery(flags: Int32, queryId: Int64, userId: Int64, query: String, geo: Api.GeoPoint?, peerType: Api.InlineQueryPeerType?, offset: String) - case updateBotInlineSend(flags: Int32, userId: Int64, query: String, geo: Api.GeoPoint?, id: String, msgId: Api.InputBotInlineMessageID?) - case updateBotMenuButton(botId: Int64, button: Api.BotMenuButton) - case updateBotMessageReaction(peer: Api.Peer, msgId: Int32, date: Int32, actor: Api.Peer, oldReactions: [Api.Reaction], newReactions: [Api.Reaction], qts: Int32) - case updateBotMessageReactions(peer: Api.Peer, msgId: Int32, date: Int32, reactions: [Api.ReactionCount], qts: Int32) - case updateBotNewBusinessMessage(flags: Int32, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, qts: Int32) - case updateBotPrecheckoutQuery(flags: Int32, queryId: Int64, userId: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, currency: String, totalAmount: Int64) - case updateBotShippingQuery(queryId: Int64, userId: Int64, payload: Buffer, shippingAddress: Api.PostAddress) - case updateBotStopped(userId: Int64, date: Int32, stopped: Api.Bool, qts: Int32) - case updateBotWebhookJSON(data: Api.DataJSON) - case updateBotWebhookJSONQuery(queryId: Int64, data: Api.DataJSON, timeout: Int32) - case updateBroadcastRevenueTransactions(balances: Api.BroadcastRevenueBalances) - case updateChannel(channelId: Int64) - case updateChannelAvailableMessages(channelId: Int64, availableMinId: Int32) - case updateChannelMessageForwards(channelId: Int64, id: Int32, forwards: Int32) - case updateChannelMessageViews(channelId: Int64, id: Int32, views: Int32) - case updateChannelParticipant(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) - case updateChannelPinnedTopic(flags: Int32, channelId: Int64, topicId: Int32) - case updateChannelPinnedTopics(flags: Int32, channelId: Int64, order: [Int32]?) - case updateChannelReadMessagesContents(flags: Int32, channelId: Int64, topMsgId: Int32?, messages: [Int32]) - case updateChannelTooLong(flags: Int32, channelId: Int64, pts: Int32?) - case updateChannelUserTyping(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction) - case updateChannelViewForumAsMessages(channelId: Int64, enabled: Api.Bool) - case updateChannelWebPage(channelId: Int64, webpage: Api.WebPage, pts: Int32, ptsCount: Int32) - case updateChat(chatId: Int64) - case updateChatDefaultBannedRights(peer: Api.Peer, defaultBannedRights: Api.ChatBannedRights, version: Int32) - case updateChatParticipant(flags: Int32, chatId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChatParticipant?, newParticipant: Api.ChatParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) - case updateChatParticipantAdd(chatId: Int64, userId: Int64, inviterId: Int64, date: Int32, version: Int32) - case updateChatParticipantAdmin(chatId: Int64, userId: Int64, isAdmin: Api.Bool, version: Int32) - case updateChatParticipantDelete(chatId: Int64, userId: Int64, version: Int32) - case updateChatParticipants(participants: Api.ChatParticipants) - case updateChatUserTyping(chatId: Int64, fromId: Api.Peer, action: Api.SendMessageAction) - case updateConfig - case updateContactsReset - case updateDcOptions(dcOptions: [Api.DcOption]) - case updateDeleteChannelMessages(channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) - case updateDeleteMessages(messages: [Int32], pts: Int32, ptsCount: Int32) - case updateDeleteQuickReply(shortcutId: Int32) - case updateDeleteQuickReplyMessages(shortcutId: Int32, messages: [Int32]) - case updateDeleteScheduledMessages(peer: Api.Peer, messages: [Int32]) - case updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?) - case updateDialogFilterOrder(order: [Int32]) - case updateDialogFilters - case updateDialogPinned(flags: Int32, folderId: Int32?, peer: Api.DialogPeer) - case updateDialogUnreadMark(flags: Int32, peer: Api.DialogPeer) - case updateDraftMessage(flags: Int32, peer: Api.Peer, topMsgId: Int32?, draft: Api.DraftMessage) - case updateEditChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32) - case updateEditMessage(message: Api.Message, pts: Int32, ptsCount: Int32) - case updateEncryptedChatTyping(chatId: Int32) - case updateEncryptedMessagesRead(chatId: Int32, maxDate: Int32, date: Int32) - case updateEncryption(chat: Api.EncryptedChat, date: Int32) - case updateFavedStickers - case updateFolderPeers(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32) - case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32) - case updateGroupCall(chatId: Int64, call: Api.GroupCall) - case updateGroupCallConnection(flags: Int32, params: Api.DataJSON) - case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32) - case updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) - case updateLangPack(difference: Api.LangPackDifference) - case updateLangPackTooLong(langCode: String) - case updateLoginToken - case updateMessageExtendedMedia(peer: Api.Peer, msgId: Int32, extendedMedia: Api.MessageExtendedMedia) - case updateMessageID(id: Int32, randomId: Int64) - case updateMessagePoll(flags: Int32, pollId: Int64, poll: Api.Poll?, results: Api.PollResults) - case updateMessagePollVote(pollId: Int64, peer: Api.Peer, options: [Buffer], qts: Int32) - case updateMessageReactions(flags: Int32, peer: Api.Peer, msgId: Int32, topMsgId: Int32?, reactions: Api.MessageReactions) - case updateMoveStickerSetToTop(flags: Int32, stickerset: Int64) - case updateNewAuthorization(flags: Int32, hash: Int64, date: Int32?, device: String?, location: String?) - case updateNewChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32) - case updateNewEncryptedMessage(message: Api.EncryptedMessage, qts: Int32) - case updateNewMessage(message: Api.Message, pts: Int32, ptsCount: Int32) - case updateNewQuickReply(quickReply: Api.QuickReply) - case updateNewScheduledMessage(message: Api.Message) - case updateNewStickerSet(stickerset: Api.messages.StickerSet) - case updateNewStoryReaction(storyId: Int32, peer: Api.Peer, reaction: Api.Reaction) - case updateNotifySettings(peer: Api.NotifyPeer, notifySettings: Api.PeerNotifySettings) - case updatePeerBlocked(flags: Int32, peerId: Api.Peer) - case updatePeerHistoryTTL(flags: Int32, peer: Api.Peer, ttlPeriod: Int32?) - case updatePeerLocated(peers: [Api.PeerLocated]) - case updatePeerSettings(peer: Api.Peer, settings: Api.PeerSettings) - case updatePeerWallpaper(flags: Int32, peer: Api.Peer, wallpaper: Api.WallPaper?) - case updatePendingJoinRequests(peer: Api.Peer, requestsPending: Int32, recentRequesters: [Int64]) - case updatePhoneCall(phoneCall: Api.PhoneCall) - case updatePhoneCallSignalingData(phoneCallId: Int64, data: Buffer) - case updatePinnedChannelMessages(flags: Int32, channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) - case updatePinnedDialogs(flags: Int32, folderId: Int32?, order: [Api.DialogPeer]?) - case updatePinnedMessages(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32) - case updatePinnedSavedDialogs(flags: Int32, order: [Api.DialogPeer]?) - case updatePrivacy(key: Api.PrivacyKey, rules: [Api.PrivacyRule]) - case updatePtsChanged - case updateQuickReplies(quickReplies: [Api.QuickReply]) - case updateQuickReplyMessage(message: Api.Message) - case updateReadChannelDiscussionInbox(flags: Int32, channelId: Int64, topMsgId: Int32, readMaxId: Int32, broadcastId: Int64?, broadcastPost: Int32?) - case updateReadChannelDiscussionOutbox(channelId: Int64, topMsgId: Int32, readMaxId: Int32) - case updateReadChannelInbox(flags: Int32, folderId: Int32?, channelId: Int64, maxId: Int32, stillUnreadCount: Int32, pts: Int32) - case updateReadChannelOutbox(channelId: Int64, maxId: Int32) - case updateReadFeaturedEmojiStickers - case updateReadFeaturedStickers - case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32) - case updateReadHistoryOutbox(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32) - case updateReadMessagesContents(flags: Int32, messages: [Int32], pts: Int32, ptsCount: Int32, date: Int32?) - case updateReadStories(peer: Api.Peer, maxId: Int32) - case updateRecentEmojiStatuses - case updateRecentReactions - case updateRecentStickers - case updateSavedDialogPinned(flags: Int32, peer: Api.DialogPeer) - case updateSavedGifs - case updateSavedReactionTags - case updateSavedRingtones - case updateSentStoryReaction(peer: Api.Peer, storyId: Int32, reaction: Api.Reaction) - case updateServiceNotification(flags: Int32, inboxDate: Int32?, type: String, message: String, media: Api.MessageMedia, entities: [Api.MessageEntity]) - case updateSmsJob(jobId: String) - case updateStickerSets(flags: Int32) - case updateStickerSetsOrder(flags: Int32, order: [Int64]) - case updateStoriesStealthMode(stealthMode: Api.StoriesStealthMode) - case updateStory(peer: Api.Peer, story: Api.StoryItem) - case updateStoryID(id: Int32, randomId: Int64) - case updateTheme(theme: Api.Theme) - case updateTranscribedAudio(flags: Int32, peer: Api.Peer, msgId: Int32, transcriptionId: Int64, text: String) - case updateUser(userId: Int64) - case updateUserEmojiStatus(userId: Int64, emojiStatus: Api.EmojiStatus) - case updateUserName(userId: Int64, firstName: String, lastName: String, usernames: [Api.Username]) - case updateUserPhone(userId: Int64, phone: String) - case updateUserStatus(userId: Int64, status: Api.UserStatus) - case updateUserTyping(userId: Int64, action: Api.SendMessageAction) - case updateWebPage(webpage: Api.WebPage, pts: Int32, ptsCount: Int32) - case updateWebViewResultSent(queryId: Int64) + enum StatsGraph: TypeConstructorDescription { + case statsGraph(flags: Int32, json: Api.DataJSON, zoomToken: String?) + case statsGraphAsync(token: String) + case statsGraphError(error: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .updateAttachMenuBots: + case .statsGraph(let flags, let json, let zoomToken): if boxed { - buffer.appendInt32(397910539) + buffer.appendInt32(-1901828938) } - + serializeInt32(flags, buffer: buffer, boxed: false) + json.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(zoomToken!, buffer: buffer, boxed: false)} break - case .updateAutoSaveSettings: + case .statsGraphAsync(let token): if boxed { - buffer.appendInt32(-335171433) + buffer.appendInt32(1244130093) } - + serializeString(token, buffer: buffer, boxed: false) break - case .updateBotBusinessConnect(let connection, let qts): + case .statsGraphError(let error): if boxed { - buffer.appendInt32(-1964652166) + buffer.appendInt32(-1092839390) } - connection.serialize(buffer, true) - serializeInt32(qts, buffer: buffer, boxed: false) + serializeString(error, buffer: buffer, boxed: false) break - case .updateBotCallbackQuery(let flags, let queryId, let userId, let peer, let msgId, let chatInstance, let data, let gameShortName): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .statsGraph(let flags, let json, let zoomToken): + return ("statsGraph", [("flags", flags as Any), ("json", json as Any), ("zoomToken", zoomToken as Any)]) + case .statsGraphAsync(let token): + return ("statsGraphAsync", [("token", token as Any)]) + case .statsGraphError(let error): + return ("statsGraphError", [("error", error as Any)]) + } + } + + public static func parse_statsGraph(_ reader: BufferReader) -> StatsGraph? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DataJSON? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.StatsGraph.statsGraph(flags: _1!, json: _2!, zoomToken: _3) + } + else { + return nil + } + } + public static func parse_statsGraphAsync(_ reader: BufferReader) -> StatsGraph? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.StatsGraph.statsGraphAsync(token: _1!) + } + else { + return nil + } + } + public static func parse_statsGraphError(_ reader: BufferReader) -> StatsGraph? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.StatsGraph.statsGraphError(error: _1!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum StatsGroupTopAdmin: TypeConstructorDescription { + case statsGroupTopAdmin(userId: Int64, deleted: Int32, kicked: Int32, banned: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsGroupTopAdmin(let userId, let deleted, let kicked, let banned): if boxed { - buffer.appendInt32(-1177566067) + buffer.appendInt32(-682079097) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) serializeInt64(userId, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(chatInstance, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(gameShortName!, buffer: buffer, boxed: false)} - break - case .updateBotChatBoost(let peer, let boost, let qts): - if boxed { - buffer.appendInt32(-1873947492) - } - peer.serialize(buffer, true) - boost.serialize(buffer, true) - serializeInt32(qts, buffer: buffer, boxed: false) + serializeInt32(deleted, buffer: buffer, boxed: false) + serializeInt32(kicked, buffer: buffer, boxed: false) + serializeInt32(banned, buffer: buffer, boxed: false) break - case .updateBotChatInviteRequester(let peer, let date, let userId, let about, let invite, let qts): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .statsGroupTopAdmin(let userId, let deleted, let kicked, let banned): + return ("statsGroupTopAdmin", [("userId", userId as Any), ("deleted", deleted as Any), ("kicked", kicked as Any), ("banned", banned as Any)]) + } + } + + public static func parse_statsGroupTopAdmin(_ reader: BufferReader) -> StatsGroupTopAdmin? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StatsGroupTopAdmin.statsGroupTopAdmin(userId: _1!, deleted: _2!, kicked: _3!, banned: _4!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum StatsGroupTopInviter: TypeConstructorDescription { + case statsGroupTopInviter(userId: Int64, invitations: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsGroupTopInviter(let userId, let invitations): if boxed { - buffer.appendInt32(299870598) + buffer.appendInt32(1398765469) } - peer.serialize(buffer, true) - serializeInt32(date, buffer: buffer, boxed: false) serializeInt64(userId, buffer: buffer, boxed: false) - serializeString(about, buffer: buffer, boxed: false) - invite.serialize(buffer, true) - serializeInt32(qts, buffer: buffer, boxed: false) - break - case .updateBotCommands(let peer, let botId, let commands): - if boxed { - buffer.appendInt32(1299263278) - } - peer.serialize(buffer, true) - serializeInt64(botId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(commands.count)) - for item in commands { - item.serialize(buffer, true) - } - break - case .updateBotDeleteBusinessMessage(let connectionId, let peer, let messages, let qts): - if boxed { - buffer.appendInt32(-1607821266) - } - serializeString(connectionId, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(qts, buffer: buffer, boxed: false) - break - case .updateBotEditBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): - if boxed { - buffer.appendInt32(132077692) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(connectionId, buffer: buffer, boxed: false) - message.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {replyToMessage!.serialize(buffer, true)} - serializeInt32(qts, buffer: buffer, boxed: false) + serializeInt32(invitations, buffer: buffer, boxed: false) break - case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .statsGroupTopInviter(let userId, let invitations): + return ("statsGroupTopInviter", [("userId", userId as Any), ("invitations", invitations as Any)]) + } + } + + public static func parse_statsGroupTopInviter(_ reader: BufferReader) -> StatsGroupTopInviter? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StatsGroupTopInviter.statsGroupTopInviter(userId: _1!, invitations: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum StatsGroupTopPoster: TypeConstructorDescription { + case statsGroupTopPoster(userId: Int64, messages: Int32, avgChars: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsGroupTopPoster(let userId, let messages, let avgChars): if boxed { - buffer.appendInt32(1232025500) + buffer.appendInt32(-1660637285) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) serializeInt64(userId, buffer: buffer, boxed: false) - serializeString(query, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {geo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {peerType!.serialize(buffer, true)} - serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(messages, buffer: buffer, boxed: false) + serializeInt32(avgChars, buffer: buffer, boxed: false) break - case .updateBotInlineSend(let flags, let userId, let query, let geo, let id, let msgId): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .statsGroupTopPoster(let userId, let messages, let avgChars): + return ("statsGroupTopPoster", [("userId", userId as Any), ("messages", messages as Any), ("avgChars", avgChars as Any)]) + } + } + + public static func parse_statsGroupTopPoster(_ reader: BufferReader) -> StatsGroupTopPoster? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.StatsGroupTopPoster.statsGroupTopPoster(userId: _1!, messages: _2!, avgChars: _3!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum StatsPercentValue: TypeConstructorDescription { + case statsPercentValue(part: Double, total: Double) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsPercentValue(let part, let total): if boxed { - buffer.appendInt32(317794823) + buffer.appendInt32(-875679776) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeString(query, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {geo!.serialize(buffer, true)} - serializeString(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {msgId!.serialize(buffer, true)} + serializeDouble(part, buffer: buffer, boxed: false) + serializeDouble(total, buffer: buffer, boxed: false) break - case .updateBotMenuButton(let botId, let button): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .statsPercentValue(let part, let total): + return ("statsPercentValue", [("part", part as Any), ("total", total as Any)]) + } + } + + public static func parse_statsPercentValue(_ reader: BufferReader) -> StatsPercentValue? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StatsPercentValue.statsPercentValue(part: _1!, total: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum StatsURL: TypeConstructorDescription { + case statsURL(url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsURL(let url): if boxed { - buffer.appendInt32(347625491) + buffer.appendInt32(1202287072) } - serializeInt64(botId, buffer: buffer, boxed: false) - button.serialize(buffer, true) + serializeString(url, buffer: buffer, boxed: false) break - case .updateBotMessageReaction(let peer, let msgId, let date, let actor, let oldReactions, let newReactions, let qts): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .statsURL(let url): + return ("statsURL", [("url", url as Any)]) + } + } + + public static func parse_statsURL(_ reader: BufferReader) -> StatsURL? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.StatsURL.statsURL(url: _1!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum StickerKeyword: TypeConstructorDescription { + case stickerKeyword(documentId: Int64, keyword: [String]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerKeyword(let documentId, let keyword): if boxed { - buffer.appendInt32(-1407069234) - } - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - actor.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(oldReactions.count)) - for item in oldReactions { - item.serialize(buffer, true) + buffer.appendInt32(-50416996) } + serializeInt64(documentId, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(newReactions.count)) - for item in newReactions { - item.serialize(buffer, true) + buffer.appendInt32(Int32(keyword.count)) + for item in keyword { + serializeString(item, buffer: buffer, boxed: false) } - serializeInt32(qts, buffer: buffer, boxed: false) break - case .updateBotMessageReactions(let peer, let msgId, let date, let reactions, let qts): - if boxed { - buffer.appendInt32(164329305) - } - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reactions.count)) - for item in reactions { - item.serialize(buffer, true) - } - serializeInt32(qts, buffer: buffer, boxed: false) - break - case .updateBotNewBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): - if boxed { - buffer.appendInt32(-1646578564) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(connectionId, buffer: buffer, boxed: false) - message.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {replyToMessage!.serialize(buffer, true)} - serializeInt32(qts, buffer: buffer, boxed: false) - break - case .updateBotPrecheckoutQuery(let flags, let queryId, let userId, let payload, let info, let shippingOptionId, let currency, let totalAmount): - if boxed { - buffer.appendInt32(-1934976362) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeBytes(payload, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(shippingOptionId!, buffer: buffer, boxed: false)} - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(totalAmount, buffer: buffer, boxed: false) - break - case .updateBotShippingQuery(let queryId, let userId, let payload, let shippingAddress): - if boxed { - buffer.appendInt32(-1246823043) - } - serializeInt64(queryId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeBytes(payload, buffer: buffer, boxed: false) - shippingAddress.serialize(buffer, true) - break - case .updateBotStopped(let userId, let date, let stopped, let qts): - if boxed { - buffer.appendInt32(-997782967) - } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - stopped.serialize(buffer, true) - serializeInt32(qts, buffer: buffer, boxed: false) - break - case .updateBotWebhookJSON(let data): - if boxed { - buffer.appendInt32(-2095595325) - } - data.serialize(buffer, true) - break - case .updateBotWebhookJSONQuery(let queryId, let data, let timeout): - if boxed { - buffer.appendInt32(-1684914010) - } - serializeInt64(queryId, buffer: buffer, boxed: false) - data.serialize(buffer, true) - serializeInt32(timeout, buffer: buffer, boxed: false) - break - case .updateBroadcastRevenueTransactions(let balances): - if boxed { - buffer.appendInt32(1550177112) - } - balances.serialize(buffer, true) - break - case .updateChannel(let channelId): - if boxed { - buffer.appendInt32(1666927625) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - break - case .updateChannelAvailableMessages(let channelId, let availableMinId): - if boxed { - buffer.appendInt32(-1304443240) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(availableMinId, buffer: buffer, boxed: false) - break - case .updateChannelMessageForwards(let channelId, let id, let forwards): - if boxed { - buffer.appendInt32(-761649164) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(forwards, buffer: buffer, boxed: false) - break - case .updateChannelMessageViews(let channelId, let id, let views): - if boxed { - buffer.appendInt32(-232346616) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(views, buffer: buffer, boxed: false) - break - case .updateChannelParticipant(let flags, let channelId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): - if boxed { - buffer.appendInt32(-1738720581) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(actorId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {prevParticipant!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {newParticipant!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {invite!.serialize(buffer, true)} - serializeInt32(qts, buffer: buffer, boxed: false) - break - case .updateChannelPinnedTopic(let flags, let channelId, let topicId): - if boxed { - buffer.appendInt32(422509539) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(topicId, buffer: buffer, boxed: false) - break - case .updateChannelPinnedTopics(let flags, let channelId, let order): - if boxed { - buffer.appendInt32(-31881726) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order!.count)) - for item in order! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - break - case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages): - if boxed { - buffer.appendInt32(-366410403) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .updateChannelTooLong(let flags, let channelId, let pts): - if boxed { - buffer.appendInt32(277713951) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(pts!, buffer: buffer, boxed: false)} - break - case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let fromId, let action): - if boxed { - buffer.appendInt32(-1937192669) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - fromId.serialize(buffer, true) - action.serialize(buffer, true) - break - case .updateChannelViewForumAsMessages(let channelId, let enabled): - if boxed { - buffer.appendInt32(129403168) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - enabled.serialize(buffer, true) - break - case .updateChannelWebPage(let channelId, let webpage, let pts, let ptsCount): - if boxed { - buffer.appendInt32(791390623) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - webpage.serialize(buffer, true) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateChat(let chatId): - if boxed { - buffer.appendInt32(-124097970) - } - serializeInt64(chatId, buffer: buffer, boxed: false) - break - case .updateChatDefaultBannedRights(let peer, let defaultBannedRights, let version): - if boxed { - buffer.appendInt32(1421875280) - } - peer.serialize(buffer, true) - defaultBannedRights.serialize(buffer, true) - serializeInt32(version, buffer: buffer, boxed: false) - break - case .updateChatParticipant(let flags, let chatId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): - if boxed { - buffer.appendInt32(-796432838) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(actorId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {prevParticipant!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {newParticipant!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {invite!.serialize(buffer, true)} - serializeInt32(qts, buffer: buffer, boxed: false) - break - case .updateChatParticipantAdd(let chatId, let userId, let inviterId, let date, let version): - if boxed { - buffer.appendInt32(1037718609) - } - serializeInt64(chatId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt64(inviterId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(version, buffer: buffer, boxed: false) - break - case .updateChatParticipantAdmin(let chatId, let userId, let isAdmin, let version): - if boxed { - buffer.appendInt32(-674602590) - } - serializeInt64(chatId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - isAdmin.serialize(buffer, true) - serializeInt32(version, buffer: buffer, boxed: false) - break - case .updateChatParticipantDelete(let chatId, let userId, let version): - if boxed { - buffer.appendInt32(-483443337) - } - serializeInt64(chatId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt32(version, buffer: buffer, boxed: false) - break - case .updateChatParticipants(let participants): - if boxed { - buffer.appendInt32(125178264) - } - participants.serialize(buffer, true) - break - case .updateChatUserTyping(let chatId, let fromId, let action): - if boxed { - buffer.appendInt32(-2092401936) - } - serializeInt64(chatId, buffer: buffer, boxed: false) - fromId.serialize(buffer, true) - action.serialize(buffer, true) - break - case .updateConfig: - if boxed { - buffer.appendInt32(-1574314746) - } - - break - case .updateContactsReset: - if boxed { - buffer.appendInt32(1887741886) - } - - break - case .updateDcOptions(let dcOptions): - if boxed { - buffer.appendInt32(-1906403213) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dcOptions.count)) - for item in dcOptions { - item.serialize(buffer, true) - } - break - case .updateDeleteChannelMessages(let channelId, let messages, let pts, let ptsCount): - if boxed { - buffer.appendInt32(-1020437742) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateDeleteMessages(let messages, let pts, let ptsCount): - if boxed { - buffer.appendInt32(-1576161051) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateDeleteQuickReply(let shortcutId): - if boxed { - buffer.appendInt32(1407644140) - } - serializeInt32(shortcutId, buffer: buffer, boxed: false) - break - case .updateDeleteQuickReplyMessages(let shortcutId, let messages): - if boxed { - buffer.appendInt32(1450174413) - } - serializeInt32(shortcutId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .updateDeleteScheduledMessages(let peer, let messages): - if boxed { - buffer.appendInt32(-1870238482) - } - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .updateDialogFilter(let flags, let id, let filter): - if boxed { - buffer.appendInt32(654302845) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {filter!.serialize(buffer, true)} - break - case .updateDialogFilterOrder(let order): - if boxed { - buffer.appendInt32(-1512627963) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .updateDialogFilters: - if boxed { - buffer.appendInt32(889491791) - } - - break - case .updateDialogPinned(let flags, let folderId, let peer): - if boxed { - buffer.appendInt32(1852826908) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - peer.serialize(buffer, true) - break - case .updateDialogUnreadMark(let flags, let peer): - if boxed { - buffer.appendInt32(-513517117) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - break - case .updateDraftMessage(let flags, let peer, let topMsgId, let draft): - if boxed { - buffer.appendInt32(457829485) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - draft.serialize(buffer, true) - break - case .updateEditChannelMessage(let message, let pts, let ptsCount): - if boxed { - buffer.appendInt32(457133559) - } - message.serialize(buffer, true) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateEditMessage(let message, let pts, let ptsCount): - if boxed { - buffer.appendInt32(-469536605) - } - message.serialize(buffer, true) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateEncryptedChatTyping(let chatId): - if boxed { - buffer.appendInt32(386986326) - } - serializeInt32(chatId, buffer: buffer, boxed: false) - break - case .updateEncryptedMessagesRead(let chatId, let maxDate, let date): - if boxed { - buffer.appendInt32(956179895) - } - serializeInt32(chatId, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - break - case .updateEncryption(let chat, let date): - if boxed { - buffer.appendInt32(-1264392051) - } - chat.serialize(buffer, true) - serializeInt32(date, buffer: buffer, boxed: false) - break - case .updateFavedStickers: - if boxed { - buffer.appendInt32(-451831443) - } - - break - case .updateFolderPeers(let folderPeers, let pts, let ptsCount): - if boxed { - buffer.appendInt32(422972864) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(folderPeers.count)) - for item in folderPeers { - item.serialize(buffer, true) - } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateGeoLiveViewed(let peer, let msgId): - if boxed { - buffer.appendInt32(-2027964103) - } - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - break - case .updateGroupCall(let chatId, let call): - if boxed { - buffer.appendInt32(347227392) - } - serializeInt64(chatId, buffer: buffer, boxed: false) - call.serialize(buffer, true) - break - case .updateGroupCallConnection(let flags, let params): - if boxed { - buffer.appendInt32(192428418) - } - serializeInt32(flags, buffer: buffer, boxed: false) - params.serialize(buffer, true) - break - case .updateGroupCallParticipants(let call, let participants, let version): - if boxed { - buffer.appendInt32(-219423922) - } - call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(participants.count)) - for item in participants { - item.serialize(buffer, true) - } - serializeInt32(version, buffer: buffer, boxed: false) - break - case .updateInlineBotCallbackQuery(let flags, let queryId, let userId, let msgId, let chatInstance, let data, let gameShortName): - if boxed { - buffer.appendInt32(1763610706) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - msgId.serialize(buffer, true) - serializeInt64(chatInstance, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(gameShortName!, buffer: buffer, boxed: false)} - break - case .updateLangPack(let difference): - if boxed { - buffer.appendInt32(1442983757) - } - difference.serialize(buffer, true) - break - case .updateLangPackTooLong(let langCode): - if boxed { - buffer.appendInt32(1180041828) - } - serializeString(langCode, buffer: buffer, boxed: false) - break - case .updateLoginToken: - if boxed { - buffer.appendInt32(1448076945) - } - - break - case .updateMessageExtendedMedia(let peer, let msgId, let extendedMedia): - if boxed { - buffer.appendInt32(1517529484) - } - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - extendedMedia.serialize(buffer, true) - break - case .updateMessageID(let id, let randomId): - if boxed { - buffer.appendInt32(1318109142) - } - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - break - case .updateMessagePoll(let flags, let pollId, let poll, let results): - if boxed { - buffer.appendInt32(-1398708869) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(pollId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {poll!.serialize(buffer, true)} - results.serialize(buffer, true) - break - case .updateMessagePollVote(let pollId, let peer, let options, let qts): - if boxed { - buffer.appendInt32(619974263) - } - serializeInt64(pollId, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(options.count)) - for item in options { - serializeBytes(item, buffer: buffer, boxed: false) - } - serializeInt32(qts, buffer: buffer, boxed: false) - break - case .updateMessageReactions(let flags, let peer, let msgId, let topMsgId, let reactions): - if boxed { - buffer.appendInt32(1578843320) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - reactions.serialize(buffer, true) - break - case .updateMoveStickerSetToTop(let flags, let stickerset): - if boxed { - buffer.appendInt32(-2030252155) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(stickerset, buffer: buffer, boxed: false) - break - case .updateNewAuthorization(let flags, let hash, let date, let device, let location): - if boxed { - buffer.appendInt32(-1991136273) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(date!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeString(device!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeString(location!, buffer: buffer, boxed: false)} - break - case .updateNewChannelMessage(let message, let pts, let ptsCount): - if boxed { - buffer.appendInt32(1656358105) - } - message.serialize(buffer, true) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateNewEncryptedMessage(let message, let qts): - if boxed { - buffer.appendInt32(314359194) - } - message.serialize(buffer, true) - serializeInt32(qts, buffer: buffer, boxed: false) - break - case .updateNewMessage(let message, let pts, let ptsCount): - if boxed { - buffer.appendInt32(522914557) - } - message.serialize(buffer, true) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateNewQuickReply(let quickReply): - if boxed { - buffer.appendInt32(-180508905) - } - quickReply.serialize(buffer, true) - break - case .updateNewScheduledMessage(let message): - if boxed { - buffer.appendInt32(967122427) - } - message.serialize(buffer, true) - break - case .updateNewStickerSet(let stickerset): - if boxed { - buffer.appendInt32(1753886890) - } - stickerset.serialize(buffer, true) - break - case .updateNewStoryReaction(let storyId, let peer, let reaction): - if boxed { - buffer.appendInt32(405070859) - } - serializeInt32(storyId, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - reaction.serialize(buffer, true) - break - case .updateNotifySettings(let peer, let notifySettings): - if boxed { - buffer.appendInt32(-1094555409) - } - peer.serialize(buffer, true) - notifySettings.serialize(buffer, true) - break - case .updatePeerBlocked(let flags, let peerId): - if boxed { - buffer.appendInt32(-337610926) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peerId.serialize(buffer, true) - break - case .updatePeerHistoryTTL(let flags, let peer, let ttlPeriod): - if boxed { - buffer.appendInt32(-1147422299) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} - break - case .updatePeerLocated(let peers): - if boxed { - buffer.appendInt32(-1263546448) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - break - case .updatePeerSettings(let peer, let settings): - if boxed { - buffer.appendInt32(1786671974) - } - peer.serialize(buffer, true) - settings.serialize(buffer, true) - break - case .updatePeerWallpaper(let flags, let peer, let wallpaper): - if boxed { - buffer.appendInt32(-1371598819) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {wallpaper!.serialize(buffer, true)} - break - case .updatePendingJoinRequests(let peer, let requestsPending, let recentRequesters): - if boxed { - buffer.appendInt32(1885586395) - } - peer.serialize(buffer, true) - serializeInt32(requestsPending, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentRequesters.count)) - for item in recentRequesters { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .updatePhoneCall(let phoneCall): - if boxed { - buffer.appendInt32(-1425052898) - } - phoneCall.serialize(buffer, true) - break - case .updatePhoneCallSignalingData(let phoneCallId, let data): - if boxed { - buffer.appendInt32(643940105) - } - serializeInt64(phoneCallId, buffer: buffer, boxed: false) - serializeBytes(data, buffer: buffer, boxed: false) - break - case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount): - if boxed { - buffer.appendInt32(1538885128) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updatePinnedDialogs(let flags, let folderId, let order): - if boxed { - buffer.appendInt32(-99664734) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order!.count)) - for item in order! { - item.serialize(buffer, true) - }} - break - case .updatePinnedMessages(let flags, let peer, let messages, let pts, let ptsCount): - if boxed { - buffer.appendInt32(-309990731) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updatePinnedSavedDialogs(let flags, let order): - if boxed { - buffer.appendInt32(1751942566) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order!.count)) - for item in order! { - item.serialize(buffer, true) - }} - break - case .updatePrivacy(let key, let rules): - if boxed { - buffer.appendInt32(-298113238) - } - key.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(rules.count)) - for item in rules { - item.serialize(buffer, true) - } - break - case .updatePtsChanged: - if boxed { - buffer.appendInt32(861169551) - } - - break - case .updateQuickReplies(let quickReplies): - if boxed { - buffer.appendInt32(-112784718) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(quickReplies.count)) - for item in quickReplies { - item.serialize(buffer, true) - } - break - case .updateQuickReplyMessage(let message): - if boxed { - buffer.appendInt32(1040518415) - } - message.serialize(buffer, true) - break - case .updateReadChannelDiscussionInbox(let flags, let channelId, let topMsgId, let readMaxId, let broadcastId, let broadcastPost): - if boxed { - buffer.appendInt32(-693004986) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(topMsgId, buffer: buffer, boxed: false) - serializeInt32(readMaxId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(broadcastId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(broadcastPost!, buffer: buffer, boxed: false)} - break - case .updateReadChannelDiscussionOutbox(let channelId, let topMsgId, let readMaxId): - if boxed { - buffer.appendInt32(1767677564) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(topMsgId, buffer: buffer, boxed: false) - serializeInt32(readMaxId, buffer: buffer, boxed: false) - break - case .updateReadChannelInbox(let flags, let folderId, let channelId, let maxId, let stillUnreadCount, let pts): - if boxed { - buffer.appendInt32(-1842450928) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(stillUnreadCount, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - break - case .updateReadChannelOutbox(let channelId, let maxId): - if boxed { - buffer.appendInt32(-1218471511) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - break - case .updateReadFeaturedEmojiStickers: - if boxed { - buffer.appendInt32(-78886548) - } - - break - case .updateReadFeaturedStickers: - if boxed { - buffer.appendInt32(1461528386) - } - - break - case .updateReadHistoryInbox(let flags, let folderId, let peer, let maxId, let stillUnreadCount, let pts, let ptsCount): - if boxed { - buffer.appendInt32(-1667805217) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(stillUnreadCount, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateReadHistoryOutbox(let peer, let maxId, let pts, let ptsCount): - if boxed { - buffer.appendInt32(791617983) - } - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateReadMessagesContents(let flags, let messages, let pts, let ptsCount, let date): - if boxed { - buffer.appendInt32(-131960447) - } - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(date!, buffer: buffer, boxed: false)} - break - case .updateReadStories(let peer, let maxId): - if boxed { - buffer.appendInt32(-145845461) - } - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - break - case .updateRecentEmojiStatuses: - if boxed { - buffer.appendInt32(821314523) - } - - break - case .updateRecentReactions: - if boxed { - buffer.appendInt32(1870160884) - } - - break - case .updateRecentStickers: - if boxed { - buffer.appendInt32(-1706939360) - } - - break - case .updateSavedDialogPinned(let flags, let peer): - if boxed { - buffer.appendInt32(-1364222348) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - break - case .updateSavedGifs: - if boxed { - buffer.appendInt32(-1821035490) - } - - break - case .updateSavedReactionTags: - if boxed { - buffer.appendInt32(969307186) - } - - break - case .updateSavedRingtones: - if boxed { - buffer.appendInt32(1960361625) - } - - break - case .updateSentStoryReaction(let peer, let storyId, let reaction): - if boxed { - buffer.appendInt32(2103604867) - } - peer.serialize(buffer, true) - serializeInt32(storyId, buffer: buffer, boxed: false) - reaction.serialize(buffer, true) - break - case .updateServiceNotification(let flags, let inboxDate, let type, let message, let media, let entities): - if boxed { - buffer.appendInt32(-337352679) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(inboxDate!, buffer: buffer, boxed: false)} - serializeString(type, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - media.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities.count)) - for item in entities { - item.serialize(buffer, true) - } - break - case .updateSmsJob(let jobId): - if boxed { - buffer.appendInt32(-245208620) - } - serializeString(jobId, buffer: buffer, boxed: false) - break - case .updateStickerSets(let flags): - if boxed { - buffer.appendInt32(834816008) - } - serializeInt32(flags, buffer: buffer, boxed: false) - break - case .updateStickerSetsOrder(let flags, let order): - if boxed { - buffer.appendInt32(196268545) - } - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .updateStoriesStealthMode(let stealthMode): - if boxed { - buffer.appendInt32(738741697) - } - stealthMode.serialize(buffer, true) - break - case .updateStory(let peer, let story): - if boxed { - buffer.appendInt32(1974712216) - } - peer.serialize(buffer, true) - story.serialize(buffer, true) - break - case .updateStoryID(let id, let randomId): - if boxed { - buffer.appendInt32(468923833) - } - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - break - case .updateTheme(let theme): - if boxed { - buffer.appendInt32(-2112423005) - } - theme.serialize(buffer, true) - break - case .updateTranscribedAudio(let flags, let peer, let msgId, let transcriptionId, let text): - if boxed { - buffer.appendInt32(8703322) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(transcriptionId, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - break - case .updateUser(let userId): - if boxed { - buffer.appendInt32(542282808) - } - serializeInt64(userId, buffer: buffer, boxed: false) - break - case .updateUserEmojiStatus(let userId, let emojiStatus): - if boxed { - buffer.appendInt32(674706841) - } - serializeInt64(userId, buffer: buffer, boxed: false) - emojiStatus.serialize(buffer, true) - break - case .updateUserName(let userId, let firstName, let lastName, let usernames): - if boxed { - buffer.appendInt32(-1484486364) - } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(usernames.count)) - for item in usernames { - item.serialize(buffer, true) - } - break - case .updateUserPhone(let userId, let phone): - if boxed { - buffer.appendInt32(88680979) - } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeString(phone, buffer: buffer, boxed: false) - break - case .updateUserStatus(let userId, let status): - if boxed { - buffer.appendInt32(-440534818) - } - serializeInt64(userId, buffer: buffer, boxed: false) - status.serialize(buffer, true) - break - case .updateUserTyping(let userId, let action): - if boxed { - buffer.appendInt32(-1071741569) - } - serializeInt64(userId, buffer: buffer, boxed: false) - action.serialize(buffer, true) - break - case .updateWebPage(let webpage, let pts, let ptsCount): - if boxed { - buffer.appendInt32(2139689491) - } - webpage.serialize(buffer, true) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - case .updateWebViewResultSent(let queryId): - if boxed { - buffer.appendInt32(361936797) - } - serializeInt64(queryId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .updateAttachMenuBots: - return ("updateAttachMenuBots", []) - case .updateAutoSaveSettings: - return ("updateAutoSaveSettings", []) - case .updateBotBusinessConnect(let connection, let qts): - return ("updateBotBusinessConnect", [("connection", connection as Any), ("qts", qts as Any)]) - case .updateBotCallbackQuery(let flags, let queryId, let userId, let peer, let msgId, let chatInstance, let data, let gameShortName): - return ("updateBotCallbackQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("peer", peer as Any), ("msgId", msgId as Any), ("chatInstance", chatInstance as Any), ("data", data as Any), ("gameShortName", gameShortName as Any)]) - case .updateBotChatBoost(let peer, let boost, let qts): - return ("updateBotChatBoost", [("peer", peer as Any), ("boost", boost as Any), ("qts", qts as Any)]) - case .updateBotChatInviteRequester(let peer, let date, let userId, let about, let invite, let qts): - return ("updateBotChatInviteRequester", [("peer", peer as Any), ("date", date as Any), ("userId", userId as Any), ("about", about as Any), ("invite", invite as Any), ("qts", qts as Any)]) - case .updateBotCommands(let peer, let botId, let commands): - return ("updateBotCommands", [("peer", peer as Any), ("botId", botId as Any), ("commands", commands as Any)]) - case .updateBotDeleteBusinessMessage(let connectionId, let peer, let messages, let qts): - return ("updateBotDeleteBusinessMessage", [("connectionId", connectionId as Any), ("peer", peer as Any), ("messages", messages as Any), ("qts", qts as Any)]) - case .updateBotEditBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): - return ("updateBotEditBusinessMessage", [("flags", flags as Any), ("connectionId", connectionId as Any), ("message", message as Any), ("replyToMessage", replyToMessage as Any), ("qts", qts as Any)]) - case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset): - return ("updateBotInlineQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("query", query as Any), ("geo", geo as Any), ("peerType", peerType as Any), ("offset", offset as Any)]) - case .updateBotInlineSend(let flags, let userId, let query, let geo, let id, let msgId): - return ("updateBotInlineSend", [("flags", flags as Any), ("userId", userId as Any), ("query", query as Any), ("geo", geo as Any), ("id", id as Any), ("msgId", msgId as Any)]) - case .updateBotMenuButton(let botId, let button): - return ("updateBotMenuButton", [("botId", botId as Any), ("button", button as Any)]) - case .updateBotMessageReaction(let peer, let msgId, let date, let actor, let oldReactions, let newReactions, let qts): - return ("updateBotMessageReaction", [("peer", peer as Any), ("msgId", msgId as Any), ("date", date as Any), ("actor", actor as Any), ("oldReactions", oldReactions as Any), ("newReactions", newReactions as Any), ("qts", qts as Any)]) - case .updateBotMessageReactions(let peer, let msgId, let date, let reactions, let qts): - return ("updateBotMessageReactions", [("peer", peer as Any), ("msgId", msgId as Any), ("date", date as Any), ("reactions", reactions as Any), ("qts", qts as Any)]) - case .updateBotNewBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): - return ("updateBotNewBusinessMessage", [("flags", flags as Any), ("connectionId", connectionId as Any), ("message", message as Any), ("replyToMessage", replyToMessage as Any), ("qts", qts as Any)]) - case .updateBotPrecheckoutQuery(let flags, let queryId, let userId, let payload, let info, let shippingOptionId, let currency, let totalAmount): - return ("updateBotPrecheckoutQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("payload", payload as Any), ("info", info as Any), ("shippingOptionId", shippingOptionId as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any)]) - case .updateBotShippingQuery(let queryId, let userId, let payload, let shippingAddress): - return ("updateBotShippingQuery", [("queryId", queryId as Any), ("userId", userId as Any), ("payload", payload as Any), ("shippingAddress", shippingAddress as Any)]) - case .updateBotStopped(let userId, let date, let stopped, let qts): - return ("updateBotStopped", [("userId", userId as Any), ("date", date as Any), ("stopped", stopped as Any), ("qts", qts as Any)]) - case .updateBotWebhookJSON(let data): - return ("updateBotWebhookJSON", [("data", data as Any)]) - case .updateBotWebhookJSONQuery(let queryId, let data, let timeout): - return ("updateBotWebhookJSONQuery", [("queryId", queryId as Any), ("data", data as Any), ("timeout", timeout as Any)]) - case .updateBroadcastRevenueTransactions(let balances): - return ("updateBroadcastRevenueTransactions", [("balances", balances as Any)]) - case .updateChannel(let channelId): - return ("updateChannel", [("channelId", channelId as Any)]) - case .updateChannelAvailableMessages(let channelId, let availableMinId): - return ("updateChannelAvailableMessages", [("channelId", channelId as Any), ("availableMinId", availableMinId as Any)]) - case .updateChannelMessageForwards(let channelId, let id, let forwards): - return ("updateChannelMessageForwards", [("channelId", channelId as Any), ("id", id as Any), ("forwards", forwards as Any)]) - case .updateChannelMessageViews(let channelId, let id, let views): - return ("updateChannelMessageViews", [("channelId", channelId as Any), ("id", id as Any), ("views", views as Any)]) - case .updateChannelParticipant(let flags, let channelId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): - return ("updateChannelParticipant", [("flags", flags as Any), ("channelId", channelId as Any), ("date", date as Any), ("actorId", actorId as Any), ("userId", userId as Any), ("prevParticipant", prevParticipant as Any), ("newParticipant", newParticipant as Any), ("invite", invite as Any), ("qts", qts as Any)]) - case .updateChannelPinnedTopic(let flags, let channelId, let topicId): - return ("updateChannelPinnedTopic", [("flags", flags as Any), ("channelId", channelId as Any), ("topicId", topicId as Any)]) - case .updateChannelPinnedTopics(let flags, let channelId, let order): - return ("updateChannelPinnedTopics", [("flags", flags as Any), ("channelId", channelId as Any), ("order", order as Any)]) - case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages): - return ("updateChannelReadMessagesContents", [("flags", flags as Any), ("channelId", channelId as Any), ("topMsgId", topMsgId as Any), ("messages", messages as Any)]) - case .updateChannelTooLong(let flags, let channelId, let pts): - return ("updateChannelTooLong", [("flags", flags as Any), ("channelId", channelId as Any), ("pts", pts as Any)]) - case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let fromId, let action): - return ("updateChannelUserTyping", [("flags", flags as Any), ("channelId", channelId as Any), ("topMsgId", topMsgId as Any), ("fromId", fromId as Any), ("action", action as Any)]) - case .updateChannelViewForumAsMessages(let channelId, let enabled): - return ("updateChannelViewForumAsMessages", [("channelId", channelId as Any), ("enabled", enabled as Any)]) - case .updateChannelWebPage(let channelId, let webpage, let pts, let ptsCount): - return ("updateChannelWebPage", [("channelId", channelId as Any), ("webpage", webpage as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateChat(let chatId): - return ("updateChat", [("chatId", chatId as Any)]) - case .updateChatDefaultBannedRights(let peer, let defaultBannedRights, let version): - return ("updateChatDefaultBannedRights", [("peer", peer as Any), ("defaultBannedRights", defaultBannedRights as Any), ("version", version as Any)]) - case .updateChatParticipant(let flags, let chatId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): - return ("updateChatParticipant", [("flags", flags as Any), ("chatId", chatId as Any), ("date", date as Any), ("actorId", actorId as Any), ("userId", userId as Any), ("prevParticipant", prevParticipant as Any), ("newParticipant", newParticipant as Any), ("invite", invite as Any), ("qts", qts as Any)]) - case .updateChatParticipantAdd(let chatId, let userId, let inviterId, let date, let version): - return ("updateChatParticipantAdd", [("chatId", chatId as Any), ("userId", userId as Any), ("inviterId", inviterId as Any), ("date", date as Any), ("version", version as Any)]) - case .updateChatParticipantAdmin(let chatId, let userId, let isAdmin, let version): - return ("updateChatParticipantAdmin", [("chatId", chatId as Any), ("userId", userId as Any), ("isAdmin", isAdmin as Any), ("version", version as Any)]) - case .updateChatParticipantDelete(let chatId, let userId, let version): - return ("updateChatParticipantDelete", [("chatId", chatId as Any), ("userId", userId as Any), ("version", version as Any)]) - case .updateChatParticipants(let participants): - return ("updateChatParticipants", [("participants", participants as Any)]) - case .updateChatUserTyping(let chatId, let fromId, let action): - return ("updateChatUserTyping", [("chatId", chatId as Any), ("fromId", fromId as Any), ("action", action as Any)]) - case .updateConfig: - return ("updateConfig", []) - case .updateContactsReset: - return ("updateContactsReset", []) - case .updateDcOptions(let dcOptions): - return ("updateDcOptions", [("dcOptions", dcOptions as Any)]) - case .updateDeleteChannelMessages(let channelId, let messages, let pts, let ptsCount): - return ("updateDeleteChannelMessages", [("channelId", channelId as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateDeleteMessages(let messages, let pts, let ptsCount): - return ("updateDeleteMessages", [("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateDeleteQuickReply(let shortcutId): - return ("updateDeleteQuickReply", [("shortcutId", shortcutId as Any)]) - case .updateDeleteQuickReplyMessages(let shortcutId, let messages): - return ("updateDeleteQuickReplyMessages", [("shortcutId", shortcutId as Any), ("messages", messages as Any)]) - case .updateDeleteScheduledMessages(let peer, let messages): - return ("updateDeleteScheduledMessages", [("peer", peer as Any), ("messages", messages as Any)]) - case .updateDialogFilter(let flags, let id, let filter): - return ("updateDialogFilter", [("flags", flags as Any), ("id", id as Any), ("filter", filter as Any)]) - case .updateDialogFilterOrder(let order): - return ("updateDialogFilterOrder", [("order", order as Any)]) - case .updateDialogFilters: - return ("updateDialogFilters", []) - case .updateDialogPinned(let flags, let folderId, let peer): - return ("updateDialogPinned", [("flags", flags as Any), ("folderId", folderId as Any), ("peer", peer as Any)]) - case .updateDialogUnreadMark(let flags, let peer): - return ("updateDialogUnreadMark", [("flags", flags as Any), ("peer", peer as Any)]) - case .updateDraftMessage(let flags, let peer, let topMsgId, let draft): - return ("updateDraftMessage", [("flags", flags as Any), ("peer", peer as Any), ("topMsgId", topMsgId as Any), ("draft", draft as Any)]) - case .updateEditChannelMessage(let message, let pts, let ptsCount): - return ("updateEditChannelMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateEditMessage(let message, let pts, let ptsCount): - return ("updateEditMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateEncryptedChatTyping(let chatId): - return ("updateEncryptedChatTyping", [("chatId", chatId as Any)]) - case .updateEncryptedMessagesRead(let chatId, let maxDate, let date): - return ("updateEncryptedMessagesRead", [("chatId", chatId as Any), ("maxDate", maxDate as Any), ("date", date as Any)]) - case .updateEncryption(let chat, let date): - return ("updateEncryption", [("chat", chat as Any), ("date", date as Any)]) - case .updateFavedStickers: - return ("updateFavedStickers", []) - case .updateFolderPeers(let folderPeers, let pts, let ptsCount): - return ("updateFolderPeers", [("folderPeers", folderPeers as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateGeoLiveViewed(let peer, let msgId): - return ("updateGeoLiveViewed", [("peer", peer as Any), ("msgId", msgId as Any)]) - case .updateGroupCall(let chatId, let call): - return ("updateGroupCall", [("chatId", chatId as Any), ("call", call as Any)]) - case .updateGroupCallConnection(let flags, let params): - return ("updateGroupCallConnection", [("flags", flags as Any), ("params", params as Any)]) - case .updateGroupCallParticipants(let call, let participants, let version): - return ("updateGroupCallParticipants", [("call", call as Any), ("participants", participants as Any), ("version", version as Any)]) - case .updateInlineBotCallbackQuery(let flags, let queryId, let userId, let msgId, let chatInstance, let data, let gameShortName): - return ("updateInlineBotCallbackQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("msgId", msgId as Any), ("chatInstance", chatInstance as Any), ("data", data as Any), ("gameShortName", gameShortName as Any)]) - case .updateLangPack(let difference): - return ("updateLangPack", [("difference", difference as Any)]) - case .updateLangPackTooLong(let langCode): - return ("updateLangPackTooLong", [("langCode", langCode as Any)]) - case .updateLoginToken: - return ("updateLoginToken", []) - case .updateMessageExtendedMedia(let peer, let msgId, let extendedMedia): - return ("updateMessageExtendedMedia", [("peer", peer as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any)]) - case .updateMessageID(let id, let randomId): - return ("updateMessageID", [("id", id as Any), ("randomId", randomId as Any)]) - case .updateMessagePoll(let flags, let pollId, let poll, let results): - return ("updateMessagePoll", [("flags", flags as Any), ("pollId", pollId as Any), ("poll", poll as Any), ("results", results as Any)]) - case .updateMessagePollVote(let pollId, let peer, let options, let qts): - return ("updateMessagePollVote", [("pollId", pollId as Any), ("peer", peer as Any), ("options", options as Any), ("qts", qts as Any)]) - case .updateMessageReactions(let flags, let peer, let msgId, let topMsgId, let reactions): - return ("updateMessageReactions", [("flags", flags as Any), ("peer", peer as Any), ("msgId", msgId as Any), ("topMsgId", topMsgId as Any), ("reactions", reactions as Any)]) - case .updateMoveStickerSetToTop(let flags, let stickerset): - return ("updateMoveStickerSetToTop", [("flags", flags as Any), ("stickerset", stickerset as Any)]) - case .updateNewAuthorization(let flags, let hash, let date, let device, let location): - return ("updateNewAuthorization", [("flags", flags as Any), ("hash", hash as Any), ("date", date as Any), ("device", device as Any), ("location", location as Any)]) - case .updateNewChannelMessage(let message, let pts, let ptsCount): - return ("updateNewChannelMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateNewEncryptedMessage(let message, let qts): - return ("updateNewEncryptedMessage", [("message", message as Any), ("qts", qts as Any)]) - case .updateNewMessage(let message, let pts, let ptsCount): - return ("updateNewMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateNewQuickReply(let quickReply): - return ("updateNewQuickReply", [("quickReply", quickReply as Any)]) - case .updateNewScheduledMessage(let message): - return ("updateNewScheduledMessage", [("message", message as Any)]) - case .updateNewStickerSet(let stickerset): - return ("updateNewStickerSet", [("stickerset", stickerset as Any)]) - case .updateNewStoryReaction(let storyId, let peer, let reaction): - return ("updateNewStoryReaction", [("storyId", storyId as Any), ("peer", peer as Any), ("reaction", reaction as Any)]) - case .updateNotifySettings(let peer, let notifySettings): - return ("updateNotifySettings", [("peer", peer as Any), ("notifySettings", notifySettings as Any)]) - case .updatePeerBlocked(let flags, let peerId): - return ("updatePeerBlocked", [("flags", flags as Any), ("peerId", peerId as Any)]) - case .updatePeerHistoryTTL(let flags, let peer, let ttlPeriod): - return ("updatePeerHistoryTTL", [("flags", flags as Any), ("peer", peer as Any), ("ttlPeriod", ttlPeriod as Any)]) - case .updatePeerLocated(let peers): - return ("updatePeerLocated", [("peers", peers as Any)]) - case .updatePeerSettings(let peer, let settings): - return ("updatePeerSettings", [("peer", peer as Any), ("settings", settings as Any)]) - case .updatePeerWallpaper(let flags, let peer, let wallpaper): - return ("updatePeerWallpaper", [("flags", flags as Any), ("peer", peer as Any), ("wallpaper", wallpaper as Any)]) - case .updatePendingJoinRequests(let peer, let requestsPending, let recentRequesters): - return ("updatePendingJoinRequests", [("peer", peer as Any), ("requestsPending", requestsPending as Any), ("recentRequesters", recentRequesters as Any)]) - case .updatePhoneCall(let phoneCall): - return ("updatePhoneCall", [("phoneCall", phoneCall as Any)]) - case .updatePhoneCallSignalingData(let phoneCallId, let data): - return ("updatePhoneCallSignalingData", [("phoneCallId", phoneCallId as Any), ("data", data as Any)]) - case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount): - return ("updatePinnedChannelMessages", [("flags", flags as Any), ("channelId", channelId as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updatePinnedDialogs(let flags, let folderId, let order): - return ("updatePinnedDialogs", [("flags", flags as Any), ("folderId", folderId as Any), ("order", order as Any)]) - case .updatePinnedMessages(let flags, let peer, let messages, let pts, let ptsCount): - return ("updatePinnedMessages", [("flags", flags as Any), ("peer", peer as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updatePinnedSavedDialogs(let flags, let order): - return ("updatePinnedSavedDialogs", [("flags", flags as Any), ("order", order as Any)]) - case .updatePrivacy(let key, let rules): - return ("updatePrivacy", [("key", key as Any), ("rules", rules as Any)]) - case .updatePtsChanged: - return ("updatePtsChanged", []) - case .updateQuickReplies(let quickReplies): - return ("updateQuickReplies", [("quickReplies", quickReplies as Any)]) - case .updateQuickReplyMessage(let message): - return ("updateQuickReplyMessage", [("message", message as Any)]) - case .updateReadChannelDiscussionInbox(let flags, let channelId, let topMsgId, let readMaxId, let broadcastId, let broadcastPost): - return ("updateReadChannelDiscussionInbox", [("flags", flags as Any), ("channelId", channelId as Any), ("topMsgId", topMsgId as Any), ("readMaxId", readMaxId as Any), ("broadcastId", broadcastId as Any), ("broadcastPost", broadcastPost as Any)]) - case .updateReadChannelDiscussionOutbox(let channelId, let topMsgId, let readMaxId): - return ("updateReadChannelDiscussionOutbox", [("channelId", channelId as Any), ("topMsgId", topMsgId as Any), ("readMaxId", readMaxId as Any)]) - case .updateReadChannelInbox(let flags, let folderId, let channelId, let maxId, let stillUnreadCount, let pts): - return ("updateReadChannelInbox", [("flags", flags as Any), ("folderId", folderId as Any), ("channelId", channelId as Any), ("maxId", maxId as Any), ("stillUnreadCount", stillUnreadCount as Any), ("pts", pts as Any)]) - case .updateReadChannelOutbox(let channelId, let maxId): - return ("updateReadChannelOutbox", [("channelId", channelId as Any), ("maxId", maxId as Any)]) - case .updateReadFeaturedEmojiStickers: - return ("updateReadFeaturedEmojiStickers", []) - case .updateReadFeaturedStickers: - return ("updateReadFeaturedStickers", []) - case .updateReadHistoryInbox(let flags, let folderId, let peer, let maxId, let stillUnreadCount, let pts, let ptsCount): - return ("updateReadHistoryInbox", [("flags", flags as Any), ("folderId", folderId as Any), ("peer", peer as Any), ("maxId", maxId as Any), ("stillUnreadCount", stillUnreadCount as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateReadHistoryOutbox(let peer, let maxId, let pts, let ptsCount): - return ("updateReadHistoryOutbox", [("peer", peer as Any), ("maxId", maxId as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateReadMessagesContents(let flags, let messages, let pts, let ptsCount, let date): - return ("updateReadMessagesContents", [("flags", flags as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any), ("date", date as Any)]) - case .updateReadStories(let peer, let maxId): - return ("updateReadStories", [("peer", peer as Any), ("maxId", maxId as Any)]) - case .updateRecentEmojiStatuses: - return ("updateRecentEmojiStatuses", []) - case .updateRecentReactions: - return ("updateRecentReactions", []) - case .updateRecentStickers: - return ("updateRecentStickers", []) - case .updateSavedDialogPinned(let flags, let peer): - return ("updateSavedDialogPinned", [("flags", flags as Any), ("peer", peer as Any)]) - case .updateSavedGifs: - return ("updateSavedGifs", []) - case .updateSavedReactionTags: - return ("updateSavedReactionTags", []) - case .updateSavedRingtones: - return ("updateSavedRingtones", []) - case .updateSentStoryReaction(let peer, let storyId, let reaction): - return ("updateSentStoryReaction", [("peer", peer as Any), ("storyId", storyId as Any), ("reaction", reaction as Any)]) - case .updateServiceNotification(let flags, let inboxDate, let type, let message, let media, let entities): - return ("updateServiceNotification", [("flags", flags as Any), ("inboxDate", inboxDate as Any), ("type", type as Any), ("message", message as Any), ("media", media as Any), ("entities", entities as Any)]) - case .updateSmsJob(let jobId): - return ("updateSmsJob", [("jobId", jobId as Any)]) - case .updateStickerSets(let flags): - return ("updateStickerSets", [("flags", flags as Any)]) - case .updateStickerSetsOrder(let flags, let order): - return ("updateStickerSetsOrder", [("flags", flags as Any), ("order", order as Any)]) - case .updateStoriesStealthMode(let stealthMode): - return ("updateStoriesStealthMode", [("stealthMode", stealthMode as Any)]) - case .updateStory(let peer, let story): - return ("updateStory", [("peer", peer as Any), ("story", story as Any)]) - case .updateStoryID(let id, let randomId): - return ("updateStoryID", [("id", id as Any), ("randomId", randomId as Any)]) - case .updateTheme(let theme): - return ("updateTheme", [("theme", theme as Any)]) - case .updateTranscribedAudio(let flags, let peer, let msgId, let transcriptionId, let text): - return ("updateTranscribedAudio", [("flags", flags as Any), ("peer", peer as Any), ("msgId", msgId as Any), ("transcriptionId", transcriptionId as Any), ("text", text as Any)]) - case .updateUser(let userId): - return ("updateUser", [("userId", userId as Any)]) - case .updateUserEmojiStatus(let userId, let emojiStatus): - return ("updateUserEmojiStatus", [("userId", userId as Any), ("emojiStatus", emojiStatus as Any)]) - case .updateUserName(let userId, let firstName, let lastName, let usernames): - return ("updateUserName", [("userId", userId as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("usernames", usernames as Any)]) - case .updateUserPhone(let userId, let phone): - return ("updateUserPhone", [("userId", userId as Any), ("phone", phone as Any)]) - case .updateUserStatus(let userId, let status): - return ("updateUserStatus", [("userId", userId as Any), ("status", status as Any)]) - case .updateUserTyping(let userId, let action): - return ("updateUserTyping", [("userId", userId as Any), ("action", action as Any)]) - case .updateWebPage(let webpage, let pts, let ptsCount): - return ("updateWebPage", [("webpage", webpage as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - case .updateWebViewResultSent(let queryId): - return ("updateWebViewResultSent", [("queryId", queryId as Any)]) - } - } - - public static func parse_updateAttachMenuBots(_ reader: BufferReader) -> Update? { - return Api.Update.updateAttachMenuBots - } - public static func parse_updateAutoSaveSettings(_ reader: BufferReader) -> Update? { - return Api.Update.updateAutoSaveSettings - } - public static func parse_updateBotBusinessConnect(_ reader: BufferReader) -> Update? { - var _1: Api.BotBusinessConnection? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.BotBusinessConnection - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateBotBusinessConnect(connection: _1!, qts: _2!) - } - else { - return nil - } - } - public static func parse_updateBotCallbackQuery(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _5: Int32? - _5 = reader.readInt32() - var _6: Int64? - _6 = reader.readInt64() - var _7: Buffer? - if Int(_1!) & Int(1 << 0) != 0 {_7 = parseBytes(reader) } - var _8: String? - if Int(_1!) & Int(1 << 1) != 0 {_8 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Update.updateBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, peer: _4!, msgId: _5!, chatInstance: _6!, data: _7, gameShortName: _8) - } - else { - return nil - } - } - public static func parse_updateBotChatBoost(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.Boost? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Boost - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotChatBoost(peer: _1!, boost: _2!, qts: _3!) - } - else { - return nil - } - } - public static func parse_updateBotChatInviteRequester(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Api.ExportedChatInvite? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateBotChatInviteRequester(peer: _1!, date: _2!, userId: _3!, about: _4!, invite: _5!, qts: _6!) - } - else { - return nil - } - } - public static func parse_updateBotCommands(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int64? - _2 = reader.readInt64() - var _3: [Api.BotCommand]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotCommands(peer: _1!, botId: _2!, commands: _3!) - } - else { - return nil - } - } - public static func parse_updateBotDeleteBusinessMessage(_ reader: BufferReader) -> Update? { - var _1: String? - _1 = parseString(reader) - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: [Int32]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateBotDeleteBusinessMessage(connectionId: _1!, peer: _2!, messages: _3!, qts: _4!) - } - else { - return nil - } - } - public static func parse_updateBotEditBusinessMessage(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.Message? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Message - } - var _4: Api.Message? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Message - } } - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateBotEditBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!) - } - else { - return nil - } - } - public static func parse_updateBotInlineQuery(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Api.GeoPoint? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.GeoPoint - } } - var _6: Api.InlineQueryPeerType? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.InlineQueryPeerType - } } - var _7: String? - _7 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateBotInlineQuery(flags: _1!, queryId: _2!, userId: _3!, query: _4!, geo: _5, peerType: _6, offset: _7!) - } - else { - return nil - } - } - public static func parse_updateBotInlineSend(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: Api.GeoPoint? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.GeoPoint - } } - var _5: String? - _5 = parseString(reader) - var _6: Api.InputBotInlineMessageID? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateBotInlineSend(flags: _1!, userId: _2!, query: _3!, geo: _4, id: _5!, msgId: _6) - } - else { - return nil - } - } - public static func parse_updateBotMenuButton(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.BotMenuButton? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BotMenuButton - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateBotMenuButton(botId: _1!, button: _2!) - } - else { - return nil - } - } - public static func parse_updateBotMessageReaction(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _5: [Api.Reaction]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) - } - var _6: [Api.Reaction]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) - } - var _7: Int32? - _7 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateBotMessageReaction(peer: _1!, msgId: _2!, date: _3!, actor: _4!, oldReactions: _5!, newReactions: _6!, qts: _7!) - } - else { - return nil - } - } - public static func parse_updateBotMessageReactions(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Api.ReactionCount]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) - } - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateBotMessageReactions(peer: _1!, msgId: _2!, date: _3!, reactions: _4!, qts: _5!) - } - else { - return nil - } - } - public static func parse_updateBotNewBusinessMessage(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.Message? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Message - } - var _4: Api.Message? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Message - } } - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateBotNewBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!) - } - else { - return nil - } - } - public static func parse_updateBotPrecheckoutQuery(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Buffer? - _4 = parseBytes(reader) - var _5: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } } - var _6: String? - if Int(_1!) & Int(1 << 1) != 0 {_6 = parseString(reader) } - var _7: String? - _7 = parseString(reader) - var _8: Int64? - _8 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Update.updateBotPrecheckoutQuery(flags: _1!, queryId: _2!, userId: _3!, payload: _4!, info: _5, shippingOptionId: _6, currency: _7!, totalAmount: _8!) - } - else { - return nil - } - } - public static func parse_updateBotShippingQuery(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Buffer? - _3 = parseBytes(reader) - var _4: Api.PostAddress? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.PostAddress - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateBotShippingQuery(queryId: _1!, userId: _2!, payload: _3!, shippingAddress: _4!) - } - else { - return nil - } - } - public static func parse_updateBotStopped(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Bool? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateBotStopped(userId: _1!, date: _2!, stopped: _3!, qts: _4!) - } - else { - return nil - } - } - public static func parse_updateBotWebhookJSON(_ reader: BufferReader) -> Update? { - var _1: Api.DataJSON? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateBotWebhookJSON(data: _1!) - } - else { - return nil - } - } - public static func parse_updateBotWebhookJSONQuery(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.DataJSON? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotWebhookJSONQuery(queryId: _1!, data: _2!, timeout: _3!) - } - else { - return nil - } - } - public static func parse_updateBroadcastRevenueTransactions(_ reader: BufferReader) -> Update? { - var _1: Api.BroadcastRevenueBalances? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.BroadcastRevenueBalances - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateBroadcastRevenueTransactions(balances: _1!) - } - else { - return nil - } - } - public static func parse_updateChannel(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateChannel(channelId: _1!) - } - else { - return nil - } - } - public static func parse_updateChannelAvailableMessages(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateChannelAvailableMessages(channelId: _1!, availableMinId: _2!) - } - else { - return nil - } - } - public static func parse_updateChannelMessageForwards(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelMessageForwards(channelId: _1!, id: _2!, forwards: _3!) - } - else { - return nil - } - } - public static func parse_updateChannelMessageViews(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelMessageViews(channelId: _1!, id: _2!, views: _3!) - } - else { - return nil - } - } - public static func parse_updateChannelParticipant(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: Int64? - _5 = reader.readInt64() - var _6: Api.ChannelParticipant? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant - } } - var _7: Api.ChannelParticipant? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant - } } - var _8: Api.ExportedChatInvite? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } } - var _9: Int32? - _9 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Update.updateChannelParticipant(flags: _1!, channelId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!) - } - else { - return nil - } - } - public static func parse_updateChannelPinnedTopic(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelPinnedTopic(flags: _1!, channelId: _2!, topicId: _3!) - } - else { - return nil - } - } - public static func parse_updateChannelPinnedTopics(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelPinnedTopics(flags: _1!, channelId: _2!, order: _3) - } - else { - return nil - } - } - public static func parse_updateChannelReadMessagesContents(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: [Int32]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateChannelReadMessagesContents(flags: _1!, channelId: _2!, topMsgId: _3, messages: _4!) - } - else { - return nil - } - } - public static func parse_updateChannelTooLong(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelTooLong(flags: _1!, channelId: _2!, pts: _3) - } - else { - return nil - } - } - public static func parse_updateChannelUserTyping(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _5: Api.SendMessageAction? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.SendMessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateChannelUserTyping(flags: _1!, channelId: _2!, topMsgId: _3, fromId: _4!, action: _5!) - } - else { - return nil - } - } - public static func parse_updateChannelViewForumAsMessages(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.Bool? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateChannelViewForumAsMessages(channelId: _1!, enabled: _2!) - } - else { - return nil - } - } - public static func parse_updateChannelWebPage(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.WebPage? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.WebPage - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateChannelWebPage(channelId: _1!, webpage: _2!, pts: _3!, ptsCount: _4!) - } - else { - return nil - } - } - public static func parse_updateChat(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateChat(chatId: _1!) - } - else { - return nil - } - } - public static func parse_updateChatDefaultBannedRights(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.ChatBannedRights? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChatDefaultBannedRights(peer: _1!, defaultBannedRights: _2!, version: _3!) - } - else { - return nil - } - } - public static func parse_updateChatParticipant(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: Int64? - _5 = reader.readInt64() - var _6: Api.ChatParticipant? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.ChatParticipant - } } - var _7: Api.ChatParticipant? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.ChatParticipant - } } - var _8: Api.ExportedChatInvite? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } } - var _9: Int32? - _9 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Update.updateChatParticipant(flags: _1!, chatId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!) - } - else { - return nil - } - } - public static func parse_updateChatParticipantAdd(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateChatParticipantAdd(chatId: _1!, userId: _2!, inviterId: _3!, date: _4!, version: _5!) - } - else { - return nil - } - } - public static func parse_updateChatParticipantAdmin(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.Bool? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateChatParticipantAdmin(chatId: _1!, userId: _2!, isAdmin: _3!, version: _4!) - } - else { - return nil - } - } - public static func parse_updateChatParticipantDelete(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChatParticipantDelete(chatId: _1!, userId: _2!, version: _3!) - } - else { - return nil - } - } - public static func parse_updateChatParticipants(_ reader: BufferReader) -> Update? { - var _1: Api.ChatParticipants? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ChatParticipants - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateChatParticipants(participants: _1!) - } - else { - return nil - } - } - public static func parse_updateChatUserTyping(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Api.SendMessageAction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.SendMessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChatUserTyping(chatId: _1!, fromId: _2!, action: _3!) - } - else { - return nil - } - } - public static func parse_updateConfig(_ reader: BufferReader) -> Update? { - return Api.Update.updateConfig - } - public static func parse_updateContactsReset(_ reader: BufferReader) -> Update? { - return Api.Update.updateContactsReset - } - public static func parse_updateDcOptions(_ reader: BufferReader) -> Update? { - var _1: [Api.DcOption]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DcOption.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateDcOptions(dcOptions: _1!) - } - else { - return nil - } - } - public static func parse_updateDeleteChannelMessages(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Int32]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateDeleteChannelMessages(channelId: _1!, messages: _2!, pts: _3!, ptsCount: _4!) - } - else { - return nil - } - } - public static func parse_updateDeleteMessages(_ reader: BufferReader) -> Update? { - var _1: [Int32]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDeleteMessages(messages: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } - } - public static func parse_updateDeleteQuickReply(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateDeleteQuickReply(shortcutId: _1!) - } - else { - return nil - } - } - public static func parse_updateDeleteQuickReplyMessages(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Int32]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateDeleteQuickReplyMessages(shortcutId: _1!, messages: _2!) - } - else { - return nil - } - } - public static func parse_updateDeleteScheduledMessages(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: [Int32]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateDeleteScheduledMessages(peer: _1!, messages: _2!) - } - else { - return nil - } - } - public static func parse_updateDialogFilter(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.DialogFilter? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.DialogFilter - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDialogFilter(flags: _1!, id: _2!, filter: _3) - } - else { - return nil - } - } - public static func parse_updateDialogFilterOrder(_ reader: BufferReader) -> Update? { - var _1: [Int32]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateDialogFilterOrder(order: _1!) - } - else { - return nil - } - } - public static func parse_updateDialogFilters(_ reader: BufferReader) -> Update? { - return Api.Update.updateDialogFilters - } - public static func parse_updateDialogPinned(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } - var _3: Api.DialogPeer? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.DialogPeer - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDialogPinned(flags: _1!, folderId: _2, peer: _3!) - } - else { - return nil - } - } - public static func parse_updateDialogUnreadMark(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.DialogPeer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DialogPeer - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateDialogUnreadMark(flags: _1!, peer: _2!) - } - else { - return nil - } - } - public static func parse_updateDraftMessage(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: Api.DraftMessage? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.DraftMessage - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateDraftMessage(flags: _1!, peer: _2!, topMsgId: _3, draft: _4!) - } - else { - return nil - } - } - public static func parse_updateEditChannelMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateEditChannelMessage(message: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } - } - public static func parse_updateEditMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateEditMessage(message: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } - } - public static func parse_updateEncryptedChatTyping(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateEncryptedChatTyping(chatId: _1!) - } - else { - return nil - } - } - public static func parse_updateEncryptedMessagesRead(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateEncryptedMessagesRead(chatId: _1!, maxDate: _2!, date: _3!) - } - else { - return nil - } - } - public static func parse_updateEncryption(_ reader: BufferReader) -> Update? { - var _1: Api.EncryptedChat? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.EncryptedChat - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateEncryption(chat: _1!, date: _2!) - } - else { - return nil - } - } - public static func parse_updateFavedStickers(_ reader: BufferReader) -> Update? { - return Api.Update.updateFavedStickers - } - public static func parse_updateFolderPeers(_ reader: BufferReader) -> Update? { - var _1: [Api.FolderPeer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FolderPeer.self) - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateFolderPeers(folderPeers: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } - } - public static func parse_updateGeoLiveViewed(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateGeoLiveViewed(peer: _1!, msgId: _2!) - } - else { - return nil - } - } - public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.GroupCall? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.GroupCall - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateGroupCall(chatId: _1!, call: _2!) - } - else { - return nil - } - } - public static func parse_updateGroupCallConnection(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.DataJSON? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateGroupCallConnection(flags: _1!, params: _2!) - } - else { - return nil - } - } - public static func parse_updateGroupCallParticipants(_ reader: BufferReader) -> Update? { - var _1: Api.InputGroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _2: [Api.GroupCallParticipant]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateGroupCallParticipants(call: _1!, participants: _2!, version: _3!) - } - else { - return nil - } - } - public static func parse_updateInlineBotCallbackQuery(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Api.InputBotInlineMessageID? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID - } - var _5: Int64? - _5 = reader.readInt64() - var _6: Buffer? - if Int(_1!) & Int(1 << 0) != 0 {_6 = parseBytes(reader) } - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateInlineBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, msgId: _4!, chatInstance: _5!, data: _6, gameShortName: _7) - } - else { - return nil - } - } - public static func parse_updateLangPack(_ reader: BufferReader) -> Update? { - var _1: Api.LangPackDifference? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.LangPackDifference - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateLangPack(difference: _1!) - } - else { - return nil - } - } - public static func parse_updateLangPackTooLong(_ reader: BufferReader) -> Update? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateLangPackTooLong(langCode: _1!) - } - else { - return nil - } - } - public static func parse_updateLoginToken(_ reader: BufferReader) -> Update? { - return Api.Update.updateLoginToken - } - public static func parse_updateMessageExtendedMedia(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.MessageExtendedMedia? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.MessageExtendedMedia - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateMessageExtendedMedia(peer: _1!, msgId: _2!, extendedMedia: _3!) - } - else { - return nil - } - } - public static func parse_updateMessageID(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateMessageID(id: _1!, randomId: _2!) - } - else { - return nil - } - } - public static func parse_updateMessagePoll(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.Poll? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Poll - } } - var _4: Api.PollResults? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.PollResults - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateMessagePoll(flags: _1!, pollId: _2!, poll: _3, results: _4!) - } - else { - return nil - } - } - public static func parse_updateMessagePollVote(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: [Buffer]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateMessagePollVote(pollId: _1!, peer: _2!, options: _3!, qts: _4!) - } - else { - return nil - } - } - public static func parse_updateMessageReactions(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } - var _5: Api.MessageReactions? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.MessageReactions - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateMessageReactions(flags: _1!, peer: _2!, msgId: _3!, topMsgId: _4, reactions: _5!) - } - else { - return nil - } - } - public static func parse_updateMoveStickerSetToTop(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateMoveStickerSetToTop(flags: _1!, stickerset: _2!) - } - else { - return nil - } - } - public static func parse_updateNewAuthorization(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } - var _5: String? - if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateNewAuthorization(flags: _1!, hash: _2!, date: _3, device: _4, location: _5) - } - else { - return nil - } - } - public static func parse_updateNewChannelMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateNewChannelMessage(message: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } - } - public static func parse_updateNewEncryptedMessage(_ reader: BufferReader) -> Update? { - var _1: Api.EncryptedMessage? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.EncryptedMessage - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateNewEncryptedMessage(message: _1!, qts: _2!) - } - else { - return nil - } - } - public static func parse_updateNewMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateNewMessage(message: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } - } - public static func parse_updateNewQuickReply(_ reader: BufferReader) -> Update? { - var _1: Api.QuickReply? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.QuickReply - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateNewQuickReply(quickReply: _1!) - } - else { - return nil - } - } - public static func parse_updateNewScheduledMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateNewScheduledMessage(message: _1!) - } - else { - return nil - } - } - public static func parse_updateNewStickerSet(_ reader: BufferReader) -> Update? { - var _1: Api.messages.StickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateNewStickerSet(stickerset: _1!) - } - else { - return nil - } - } - public static func parse_updateNewStoryReaction(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Api.Reaction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Reaction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateNewStoryReaction(storyId: _1!, peer: _2!, reaction: _3!) - } - else { - return nil - } - } - public static func parse_updateNotifySettings(_ reader: BufferReader) -> Update? { - var _1: Api.NotifyPeer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.NotifyPeer - } - var _2: Api.PeerNotifySettings? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateNotifySettings(peer: _1!, notifySettings: _2!) - } - else { - return nil - } - } - public static func parse_updatePeerBlocked(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePeerBlocked(flags: _1!, peerId: _2!) - } - else { - return nil - } - } - public static func parse_updatePeerHistoryTTL(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePeerHistoryTTL(flags: _1!, peer: _2!, ttlPeriod: _3) - } - else { - return nil - } - } - public static func parse_updatePeerLocated(_ reader: BufferReader) -> Update? { - var _1: [Api.PeerLocated]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerLocated.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updatePeerLocated(peers: _1!) - } - else { - return nil - } - } - public static func parse_updatePeerSettings(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.PeerSettings? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PeerSettings - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePeerSettings(peer: _1!, settings: _2!) - } - else { - return nil - } - } - public static func parse_updatePeerWallpaper(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Api.WallPaper? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.WallPaper - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePeerWallpaper(flags: _1!, peer: _2!, wallpaper: _3) - } - else { - return nil - } - } - public static func parse_updatePendingJoinRequests(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: [Int64]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePendingJoinRequests(peer: _1!, requestsPending: _2!, recentRequesters: _3!) - } - else { - return nil - } - } - public static func parse_updatePhoneCall(_ reader: BufferReader) -> Update? { - var _1: Api.PhoneCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updatePhoneCall(phoneCall: _1!) - } - else { - return nil - } - } - public static func parse_updatePhoneCallSignalingData(_ reader: BufferReader) -> Update? { + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .stickerKeyword(let documentId, let keyword): + return ("stickerKeyword", [("documentId", documentId as Any), ("keyword", keyword as Any)]) + } + } + + public static func parse_stickerKeyword(_ reader: BufferReader) -> StickerKeyword? { var _1: Int64? _1 = reader.readInt64() - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePhoneCallSignalingData(phoneCallId: _1!, data: _2!) - } - else { - return nil - } - } - public static func parse_updatePinnedChannelMessages(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: [Int32]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updatePinnedChannelMessages(flags: _1!, channelId: _2!, messages: _3!, pts: _4!, ptsCount: _5!) - } - else { - return nil - } - } - public static func parse_updatePinnedDialogs(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } - var _3: [Api.DialogPeer]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) - } } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePinnedDialogs(flags: _1!, folderId: _2, order: _3) - } - else { - return nil - } - } - public static func parse_updatePinnedMessages(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: [Int32]? + var _2: [String]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updatePinnedMessages(flags: _1!, peer: _2!, messages: _3!, pts: _4!, ptsCount: _5!) - } - else { - return nil - } - } - public static func parse_updatePinnedSavedDialogs(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.DialogPeer]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) - } } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil if _c1 && _c2 { - return Api.Update.updatePinnedSavedDialogs(flags: _1!, order: _2) + return Api.StickerKeyword.stickerKeyword(documentId: _1!, keyword: _2!) } else { return nil } } - public static func parse_updatePrivacy(_ reader: BufferReader) -> Update? { - var _1: Api.PrivacyKey? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PrivacyKey - } - var _2: [Api.PrivacyRule]? + + } +} +public extension Api { + enum StickerPack: TypeConstructorDescription { + case stickerPack(emoticon: String, documents: [Int64]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerPack(let emoticon, let documents): + if boxed { + buffer.appendInt32(313694676) + } + serializeString(emoticon, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(documents.count)) + for item in documents { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .stickerPack(let emoticon, let documents): + return ("stickerPack", [("emoticon", emoticon as Any), ("documents", documents as Any)]) + } + } + + public static func parse_stickerPack(_ reader: BufferReader) -> StickerPack? { + var _1: String? + _1 = parseString(reader) + var _2: [Int64]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) + _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.Update.updatePrivacy(key: _1!, rules: _2!) - } - else { - return nil - } - } - public static func parse_updatePtsChanged(_ reader: BufferReader) -> Update? { - return Api.Update.updatePtsChanged - } - public static func parse_updateQuickReplies(_ reader: BufferReader) -> Update? { - var _1: [Api.QuickReply]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateQuickReplies(quickReplies: _1!) - } - else { - return nil - } - } - public static func parse_updateQuickReplyMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateQuickReplyMessage(message: _1!) - } - else { - return nil - } - } - public static func parse_updateReadChannelDiscussionInbox(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } - var _6: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateReadChannelDiscussionInbox(flags: _1!, channelId: _2!, topMsgId: _3!, readMaxId: _4!, broadcastId: _5, broadcastPost: _6) - } - else { - return nil - } - } - public static func parse_updateReadChannelDiscussionOutbox(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateReadChannelDiscussionOutbox(channelId: _1!, topMsgId: _2!, readMaxId: _3!) + return Api.StickerPack.stickerPack(emoticon: _1!, documents: _2!) } else { return nil } } - public static func parse_updateReadChannelInbox(_ reader: BufferReader) -> Update? { + + } +} +public extension Api { + enum StickerSet: TypeConstructorDescription { + case stickerSet(flags: Int32, installedDate: Int32?, id: Int64, accessHash: Int64, title: String, shortName: String, thumbs: [Api.PhotoSize]?, thumbDcId: Int32?, thumbVersion: Int32?, thumbDocumentId: Int64?, count: Int32, hash: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerSet(let flags, let installedDate, let id, let accessHash, let title, let shortName, let thumbs, let thumbDcId, let thumbVersion, let thumbDocumentId, let count, let hash): + if boxed { + buffer.appendInt32(768691932) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(installedDate!, buffer: buffer, boxed: false)} + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(shortName, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(thumbs!.count)) + for item in thumbs! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(thumbDcId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(thumbVersion!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 8) != 0 {serializeInt64(thumbDocumentId!, buffer: buffer, boxed: false)} + serializeInt32(count, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .stickerSet(let flags, let installedDate, let id, let accessHash, let title, let shortName, let thumbs, let thumbDcId, let thumbVersion, let thumbDocumentId, let count, let hash): + return ("stickerSet", [("flags", flags as Any), ("installedDate", installedDate as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("title", title as Any), ("shortName", shortName as Any), ("thumbs", thumbs as Any), ("thumbDcId", thumbDcId as Any), ("thumbVersion", thumbVersion as Any), ("thumbDocumentId", thumbDocumentId as Any), ("count", count as Any), ("hash", hash as Any)]) + } + } + + public static func parse_stickerSet(_ reader: BufferReader) -> StickerSet? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } var _3: Int64? _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + var _7: [Api.PhotoSize]? + if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) + } } + var _8: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() } + var _9: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_9 = reader.readInt32() } + var _10: Int64? + if Int(_1!) & Int(1 << 8) != 0 {_10 = reader.readInt64() } + var _11: Int32? + _11 = reader.readInt32() + var _12: Int32? + _12 = reader.readInt32() let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateReadChannelInbox(flags: _1!, folderId: _2, channelId: _3!, maxId: _4!, stillUnreadCount: _5!, pts: _6!) + let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 8) == 0) || _10 != nil + let _c11 = _11 != nil + let _c12 = _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.StickerSet.stickerSet(flags: _1!, installedDate: _2, id: _3!, accessHash: _4!, title: _5!, shortName: _6!, thumbs: _7, thumbDcId: _8, thumbVersion: _9, thumbDocumentId: _10, count: _11!, hash: _12!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum StickerSetCovered: TypeConstructorDescription { + case stickerSetCovered(set: Api.StickerSet, cover: Api.Document) + case stickerSetFullCovered(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) + case stickerSetMultiCovered(set: Api.StickerSet, covers: [Api.Document]) + case stickerSetNoCovered(set: Api.StickerSet) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerSetCovered(let set, let cover): + if boxed { + buffer.appendInt32(1678812626) + } + set.serialize(buffer, true) + cover.serialize(buffer, true) + break + case .stickerSetFullCovered(let set, let packs, let keywords, let documents): + if boxed { + buffer.appendInt32(1087454222) + } + set.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(packs.count)) + for item in packs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(keywords.count)) + for item in keywords { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(documents.count)) + for item in documents { + item.serialize(buffer, true) + } + break + case .stickerSetMultiCovered(let set, let covers): + if boxed { + buffer.appendInt32(872932635) + } + set.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(covers.count)) + for item in covers { + item.serialize(buffer, true) + } + break + case .stickerSetNoCovered(let set): + if boxed { + buffer.appendInt32(2008112412) + } + set.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .stickerSetCovered(let set, let cover): + return ("stickerSetCovered", [("set", set as Any), ("cover", cover as Any)]) + case .stickerSetFullCovered(let set, let packs, let keywords, let documents): + return ("stickerSetFullCovered", [("set", set as Any), ("packs", packs as Any), ("keywords", keywords as Any), ("documents", documents as Any)]) + case .stickerSetMultiCovered(let set, let covers): + return ("stickerSetMultiCovered", [("set", set as Any), ("covers", covers as Any)]) + case .stickerSetNoCovered(let set): + return ("stickerSetNoCovered", [("set", set as Any)]) + } + } + + public static func parse_stickerSetCovered(_ reader: BufferReader) -> StickerSetCovered? { + var _1: Api.StickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StickerSet } - else { - return nil + var _2: Api.Document? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Document } - } - public static func parse_updateReadChannelOutbox(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.Update.updateReadChannelOutbox(channelId: _1!, maxId: _2!) + return Api.StickerSetCovered.stickerSetCovered(set: _1!, cover: _2!) } else { return nil } } - public static func parse_updateReadFeaturedEmojiStickers(_ reader: BufferReader) -> Update? { - return Api.Update.updateReadFeaturedEmojiStickers - } - public static func parse_updateReadFeaturedStickers(_ reader: BufferReader) -> Update? { - return Api.Update.updateReadFeaturedStickers - } - public static func parse_updateReadHistoryInbox(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: Api.Peer? + public static func parse_stickerSetFullCovered(_ reader: BufferReader) -> StickerSetCovered? { + var _1: Api.StickerSet? if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer + _1 = Api.parse(reader, signature: signature) as? Api.StickerSet } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateReadHistoryInbox(flags: _1!, folderId: _2, peer: _3!, maxId: _4!, stillUnreadCount: _5!, pts: _6!, ptsCount: _7!) + var _2: [Api.StickerPack]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) } - else { - return nil + var _3: [Api.StickerKeyword]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self) } - } - public static func parse_updateReadHistoryOutbox(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + var _4: [Api.Document]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateReadHistoryOutbox(peer: _1!, maxId: _2!, pts: _3!, ptsCount: _4!) + return Api.StickerSetCovered.stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!) } else { return nil } } - public static func parse_updateReadMessagesContents(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Int32]? + public static func parse_stickerSetMultiCovered(_ reader: BufferReader) -> StickerSetCovered? { + var _1: Api.StickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StickerSet + } + var _2: [Api.Document]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateReadMessagesContents(flags: _1!, messages: _2!, pts: _3!, ptsCount: _4!, date: _5) + if _c1 && _c2 { + return Api.StickerSetCovered.stickerSetMultiCovered(set: _1!, covers: _2!) } else { return nil } } - public static func parse_updateReadStories(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? + public static func parse_stickerSetNoCovered(_ reader: BufferReader) -> StickerSetCovered? { + var _1: Api.StickerSet? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + _1 = Api.parse(reader, signature: signature) as? Api.StickerSet } - var _2: Int32? - _2 = reader.readInt32() let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateReadStories(peer: _1!, maxId: _2!) + if _c1 { + return Api.StickerSetCovered.stickerSetNoCovered(set: _1!) } else { return nil } } - public static func parse_updateRecentEmojiStatuses(_ reader: BufferReader) -> Update? { - return Api.Update.updateRecentEmojiStatuses - } - public static func parse_updateRecentReactions(_ reader: BufferReader) -> Update? { - return Api.Update.updateRecentReactions - } - public static func parse_updateRecentStickers(_ reader: BufferReader) -> Update? { - return Api.Update.updateRecentStickers - } - public static func parse_updateSavedDialogPinned(_ reader: BufferReader) -> Update? { + + } +} +public extension Api { + enum StoriesStealthMode: TypeConstructorDescription { + case storiesStealthMode(flags: Int32, activeUntilDate: Int32?, cooldownUntilDate: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storiesStealthMode(let flags, let activeUntilDate, let cooldownUntilDate): + if boxed { + buffer.appendInt32(1898850301) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(activeUntilDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(cooldownUntilDate!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storiesStealthMode(let flags, let activeUntilDate, let cooldownUntilDate): + return ("storiesStealthMode", [("flags", flags as Any), ("activeUntilDate", activeUntilDate as Any), ("cooldownUntilDate", cooldownUntilDate as Any)]) + } + } + + public static func parse_storiesStealthMode(_ reader: BufferReader) -> StoriesStealthMode? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.DialogPeer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DialogPeer - } + var _2: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _3: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateSavedDialogPinned(flags: _1!, peer: _2!) + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.StoriesStealthMode.storiesStealthMode(flags: _1!, activeUntilDate: _2, cooldownUntilDate: _3) } else { return nil } } - public static func parse_updateSavedGifs(_ reader: BufferReader) -> Update? { - return Api.Update.updateSavedGifs - } - public static func parse_updateSavedReactionTags(_ reader: BufferReader) -> Update? { - return Api.Update.updateSavedReactionTags - } - public static func parse_updateSavedRingtones(_ reader: BufferReader) -> Update? { - return Api.Update.updateSavedRingtones - } - public static func parse_updateSentStoryReaction(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Reaction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Reaction - } + + } +} +public extension Api { + enum StoryFwdHeader: TypeConstructorDescription { + case storyFwdHeader(flags: Int32, from: Api.Peer?, fromName: String?, storyId: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyFwdHeader(let flags, let from, let fromName, let storyId): + if boxed { + buffer.appendInt32(-1205411504) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {from!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(fromName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(storyId!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyFwdHeader(let flags, let from, let fromName, let storyId): + return ("storyFwdHeader", [("flags", flags as Any), ("from", from as Any), ("fromName", fromName as Any), ("storyId", storyId as Any)]) + } + } + + public static func parse_storyFwdHeader(_ reader: BufferReader) -> StoryFwdHeader? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _3: String? + if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } + var _4: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateSentStoryReaction(peer: _1!, storyId: _2!, reaction: _3!) + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StoryFwdHeader.storyFwdHeader(flags: _1!, from: _2, fromName: _3, storyId: _4) } else { return nil } } - public static func parse_updateServiceNotification(_ reader: BufferReader) -> Update? { + + } +} +public extension Api { + indirect enum StoryItem: TypeConstructorDescription { + case storyItem(flags: Int32, id: Int32, date: Int32, fromId: Api.Peer?, fwdFrom: Api.StoryFwdHeader?, expireDate: Int32, caption: String?, entities: [Api.MessageEntity]?, media: Api.MessageMedia, mediaAreas: [Api.MediaArea]?, privacy: [Api.PrivacyRule]?, views: Api.StoryViews?, sentReaction: Api.Reaction?) + case storyItemDeleted(id: Int32) + case storyItemSkipped(flags: Int32, id: Int32, date: Int32, expireDate: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyItem(let flags, let id, let date, let fromId, let fwdFrom, let expireDate, let caption, let entities, let media, let mediaAreas, let privacy, let views, let sentReaction): + if boxed { + buffer.appendInt32(2041735716) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 18) != 0 {fromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 17) != 0 {fwdFrom!.serialize(buffer, true)} + serializeInt32(expireDate, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(caption!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + media.serialize(buffer, true) + if Int(flags) & Int(1 << 14) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(mediaAreas!.count)) + for item in mediaAreas! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(privacy!.count)) + for item in privacy! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 3) != 0 {views!.serialize(buffer, true)} + if Int(flags) & Int(1 << 15) != 0 {sentReaction!.serialize(buffer, true)} + break + case .storyItemDeleted(let id): + if boxed { + buffer.appendInt32(1374088783) + } + serializeInt32(id, buffer: buffer, boxed: false) + break + case .storyItemSkipped(let flags, let id, let date, let expireDate): + if boxed { + buffer.appendInt32(-5388013) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(expireDate, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyItem(let flags, let id, let date, let fromId, let fwdFrom, let expireDate, let caption, let entities, let media, let mediaAreas, let privacy, let views, let sentReaction): + return ("storyItem", [("flags", flags as Any), ("id", id as Any), ("date", date as Any), ("fromId", fromId as Any), ("fwdFrom", fwdFrom as Any), ("expireDate", expireDate as Any), ("caption", caption as Any), ("entities", entities as Any), ("media", media as Any), ("mediaAreas", mediaAreas as Any), ("privacy", privacy as Any), ("views", views as Any), ("sentReaction", sentReaction as Any)]) + case .storyItemDeleted(let id): + return ("storyItemDeleted", [("id", id as Any)]) + case .storyItemSkipped(let flags, let id, let date, let expireDate): + return ("storyItemSkipped", [("flags", flags as Any), ("id", id as Any), ("date", date as Any), ("expireDate", expireDate as Any)]) + } + } + + public static func parse_storyItem(_ reader: BufferReader) -> StoryItem? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Api.MessageMedia? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Peer? + if Int(_1!) & Int(1 << 18) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _5: Api.StoryFwdHeader? + if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.StoryFwdHeader + } } + var _6: Int32? + _6 = reader.readInt32() + var _7: String? + if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) } + var _8: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + var _9: Api.MessageMedia? if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - var _6: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _9 = Api.parse(reader, signature: signature) as? Api.MessageMedia } + var _10: [Api.MediaArea]? + if Int(_1!) & Int(1 << 14) != 0 {if let _ = reader.readInt32() { + _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MediaArea.self) + } } + var _11: [Api.PrivacyRule]? + if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() { + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) + } } + var _12: Api.StoryViews? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.StoryViews + } } + var _13: Api.Reaction? + if Int(_1!) & Int(1 << 15) != 0 {if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.Reaction + } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil + let _c4 = (Int(_1!) & Int(1 << 18) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 17) == 0) || _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateServiceNotification(flags: _1!, inboxDate: _2, type: _3!, message: _4!, media: _5!, entities: _6!) + let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil + let _c9 = _9 != nil + let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 2) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { + return Api.StoryItem.storyItem(flags: _1!, id: _2!, date: _3!, fromId: _4, fwdFrom: _5, expireDate: _6!, caption: _7, entities: _8, media: _9!, mediaAreas: _10, privacy: _11, views: _12, sentReaction: _13) } else { return nil } } - public static func parse_updateSmsJob(_ reader: BufferReader) -> Update? { - var _1: String? - _1 = parseString(reader) + public static func parse_storyItemDeleted(_ reader: BufferReader) -> StoryItem? { + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { - return Api.Update.updateSmsJob(jobId: _1!) + return Api.StoryItem.storyItemDeleted(id: _1!) } else { return nil } } - public static func parse_updateStickerSets(_ reader: BufferReader) -> Update? { + public static func parse_storyItemSkipped(_ reader: BufferReader) -> StoryItem? { var _1: Int32? _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.Update.updateStickerSets(flags: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StoryItem.storyItemSkipped(flags: _1!, id: _2!, date: _3!, expireDate: _4!) } else { return nil } } - public static func parse_updateStickerSetsOrder(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Int64]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + + } +} +public extension Api { + indirect enum StoryReaction: TypeConstructorDescription { + case storyReaction(peerId: Api.Peer, date: Int32, reaction: Api.Reaction) + case storyReactionPublicForward(message: Api.Message) + case storyReactionPublicRepost(peerId: Api.Peer, story: Api.StoryItem) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyReaction(let peerId, let date, let reaction): + if boxed { + buffer.appendInt32(1620104917) + } + peerId.serialize(buffer, true) + serializeInt32(date, buffer: buffer, boxed: false) + reaction.serialize(buffer, true) + break + case .storyReactionPublicForward(let message): + if boxed { + buffer.appendInt32(-1146411453) + } + message.serialize(buffer, true) + break + case .storyReactionPublicRepost(let peerId, let story): + if boxed { + buffer.appendInt32(-808644845) + } + peerId.serialize(buffer, true) + story.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyReaction(let peerId, let date, let reaction): + return ("storyReaction", [("peerId", peerId as Any), ("date", date as Any), ("reaction", reaction as Any)]) + case .storyReactionPublicForward(let message): + return ("storyReactionPublicForward", [("message", message as Any)]) + case .storyReactionPublicRepost(let peerId, let story): + return ("storyReactionPublicRepost", [("peerId", peerId as Any), ("story", story as Any)]) + } + } + + public static func parse_storyReaction(_ reader: BufferReader) -> StoryReaction? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Reaction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Reaction } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStickerSetsOrder(flags: _1!, order: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.StoryReaction.storyReaction(peerId: _1!, date: _2!, reaction: _3!) } else { return nil } } - public static func parse_updateStoriesStealthMode(_ reader: BufferReader) -> Update? { - var _1: Api.StoriesStealthMode? + public static func parse_storyReactionPublicForward(_ reader: BufferReader) -> StoryReaction? { + var _1: Api.Message? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode + _1 = Api.parse(reader, signature: signature) as? Api.Message } let _c1 = _1 != nil if _c1 { - return Api.Update.updateStoriesStealthMode(stealthMode: _1!) + return Api.StoryReaction.storyReactionPublicForward(message: _1!) } else { return nil } } - public static func parse_updateStory(_ reader: BufferReader) -> Update? { + public static func parse_storyReactionPublicRepost(_ reader: BufferReader) -> StoryReaction? { var _1: Api.Peer? if let signature = reader.readInt32() { _1 = Api.parse(reader, signature: signature) as? Api.Peer @@ -3838,184 +1047,371 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.Update.updateStory(peer: _1!, story: _2!) + return Api.StoryReaction.storyReactionPublicRepost(peerId: _1!, story: _2!) } else { return nil } } - public static func parse_updateStoryID(_ reader: BufferReader) -> Update? { + + } +} +public extension Api { + indirect enum StoryView: TypeConstructorDescription { + case storyView(flags: Int32, userId: Int64, date: Int32, reaction: Api.Reaction?) + case storyViewPublicForward(flags: Int32, message: Api.Message) + case storyViewPublicRepost(flags: Int32, peerId: Api.Peer, story: Api.StoryItem) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyView(let flags, let userId, let date, let reaction): + if boxed { + buffer.appendInt32(-1329730875) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {reaction!.serialize(buffer, true)} + break + case .storyViewPublicForward(let flags, let message): + if boxed { + buffer.appendInt32(-1870436597) + } + serializeInt32(flags, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + case .storyViewPublicRepost(let flags, let peerId, let story): + if boxed { + buffer.appendInt32(-1116418231) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peerId.serialize(buffer, true) + story.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyView(let flags, let userId, let date, let reaction): + return ("storyView", [("flags", flags as Any), ("userId", userId as Any), ("date", date as Any), ("reaction", reaction as Any)]) + case .storyViewPublicForward(let flags, let message): + return ("storyViewPublicForward", [("flags", flags as Any), ("message", message as Any)]) + case .storyViewPublicRepost(let flags, let peerId, let story): + return ("storyViewPublicRepost", [("flags", flags as Any), ("peerId", peerId as Any), ("story", story as Any)]) + } + } + + public static func parse_storyView(_ reader: BufferReader) -> StoryView? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Reaction? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Reaction + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStoryID(id: _1!, randomId: _2!) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StoryView.storyView(flags: _1!, userId: _2!, date: _3!, reaction: _4) } else { return nil } } - public static func parse_updateTheme(_ reader: BufferReader) -> Update? { - var _1: Api.Theme? + public static func parse_storyViewPublicForward(_ reader: BufferReader) -> StoryView? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Message? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Theme + _2 = Api.parse(reader, signature: signature) as? Api.Message } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateTheme(theme: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StoryView.storyViewPublicForward(flags: _1!, message: _2!) } else { return nil } } - public static func parse_updateTranscribedAudio(_ reader: BufferReader) -> Update? { + public static func parse_storyViewPublicRepost(_ reader: BufferReader) -> StoryView? { var _1: Int32? _1 = reader.readInt32() var _2: Api.Peer? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.Peer } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - _5 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateTranscribedAudio(flags: _1!, peer: _2!, msgId: _3!, transcriptionId: _4!, text: _5!) - } - else { - return nil - } - } - public static func parse_updateUser(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateUser(userId: _1!) - } - else { - return nil - } - } - public static func parse_updateUserEmojiStatus(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.EmojiStatus? + var _3: Api.StoryItem? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.EmojiStatus - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateUserEmojiStatus(userId: _1!, emojiStatus: _2!) - } - else { - return nil - } - } - public static func parse_updateUserName(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: [Api.Username]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self) + _3 = Api.parse(reader, signature: signature) as? Api.StoryItem } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateUserName(userId: _1!, firstName: _2!, lastName: _3!, usernames: _4!) + if _c1 && _c2 && _c3 { + return Api.StoryView.storyViewPublicRepost(flags: _1!, peerId: _2!, story: _3!) } else { return nil } } - public static func parse_updateUserPhone(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: String? - _2 = parseString(reader) + + } +} +public extension Api { + enum StoryViews: TypeConstructorDescription { + case storyViews(flags: Int32, viewsCount: Int32, forwardsCount: Int32?, reactions: [Api.ReactionCount]?, reactionsCount: Int32?, recentViewers: [Int64]?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): + if boxed { + buffer.appendInt32(-1923523370) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(viewsCount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(forwardsCount!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(reactions!.count)) + for item in reactions! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(reactionsCount!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentViewers!.count)) + for item in recentViewers! { + serializeInt64(item, buffer: buffer, boxed: false) + }} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): + return ("storyViews", [("flags", flags as Any), ("viewsCount", viewsCount as Any), ("forwardsCount", forwardsCount as Any), ("reactions", reactions as Any), ("reactionsCount", reactionsCount as Any), ("recentViewers", recentViewers as Any)]) + } + } + + public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } + var _4: [Api.ReactionCount]? + if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) + } } + var _5: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } + var _6: [Int64]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateUserPhone(userId: _1!, phone: _2!) + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.StoryViews.storyViews(flags: _1!, viewsCount: _2!, forwardsCount: _3, reactions: _4, reactionsCount: _5, recentViewers: _6) } else { return nil } } - public static func parse_updateUserStatus(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.UserStatus? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.UserStatus + + } +} +public extension Api { + enum TextWithEntities: TypeConstructorDescription { + case textWithEntities(text: String, entities: [Api.MessageEntity]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .textWithEntities(let text, let entities): + if boxed { + buffer.appendInt32(1964978502) + } + serializeString(text, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities.count)) + for item in entities { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .textWithEntities(let text, let entities): + return ("textWithEntities", [("text", text as Any), ("entities", entities as Any)]) + } + } + + public static func parse_textWithEntities(_ reader: BufferReader) -> TextWithEntities? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.Update.updateUserStatus(userId: _1!, status: _2!) + return Api.TextWithEntities.textWithEntities(text: _1!, entities: _2!) } else { return nil } } - public static func parse_updateUserTyping(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.SendMessageAction? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.SendMessageAction - } + + } +} +public extension Api { + enum Theme: TypeConstructorDescription { + case theme(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: [Api.ThemeSettings]?, emoticon: String?, installsCount: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let emoticon, let installsCount): + if boxed { + buffer.appendInt32(-1609668650) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeString(slug, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(settings!.count)) + for item in settings! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 6) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(installsCount!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let emoticon, let installsCount): + return ("theme", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("slug", slug as Any), ("title", title as Any), ("document", document as Any), ("settings", settings as Any), ("emoticon", emoticon as Any), ("installsCount", installsCount as Any)]) + } + } + + public static func parse_theme(_ reader: BufferReader) -> Theme? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.Document? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Document + } } + var _7: [Api.ThemeSettings]? + if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ThemeSettings.self) + } } + var _8: String? + if Int(_1!) & Int(1 << 6) != 0 {_8 = parseString(reader) } + var _9: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_9 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateUserTyping(userId: _1!, action: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, emoticon: _8, installsCount: _9) } else { return nil } } - public static func parse_updateWebPage(_ reader: BufferReader) -> Update? { - var _1: Api.WebPage? + + } +} +public extension Api { + enum ThemeSettings: TypeConstructorDescription { + case themeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, outboxAccentColor: Int32?, messageColors: [Int32]?, wallpaper: Api.WallPaper?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper): + if boxed { + buffer.appendInt32(-94849324) + } + serializeInt32(flags, buffer: buffer, boxed: false) + baseTheme.serialize(buffer, true) + serializeInt32(accentColor, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(outboxAccentColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messageColors!.count)) + for item in messageColors! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + if Int(flags) & Int(1 << 1) != 0 {wallpaper!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper): + return ("themeSettings", [("flags", flags as Any), ("baseTheme", baseTheme as Any), ("accentColor", accentColor as Any), ("outboxAccentColor", outboxAccentColor as Any), ("messageColors", messageColors as Any), ("wallpaper", wallpaper as Any)]) + } + } + + public static func parse_themeSettings(_ reader: BufferReader) -> ThemeSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.BaseTheme? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.WebPage + _2 = Api.parse(reader, signature: signature) as? Api.BaseTheme } - var _2: Int32? - _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() + var _4: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() } + var _5: [Int32]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + var _6: Api.WallPaper? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WallPaper + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateWebPage(webpage: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } - } - public static func parse_updateWebViewResultSent(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateWebViewResultSent(queryId: _1!) + let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.ThemeSettings.themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index e09e22c1644..ffad9c1ca72 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -1,322 +1,3645 @@ public extension Api { - indirect enum Updates: TypeConstructorDescription { - case updateShort(update: Api.Update, date: Int32) - case updateShortChatMessage(flags: Int32, id: Int32, fromId: Int64, chatId: Int64, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) - case updateShortMessage(flags: Int32, id: Int32, userId: Int64, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) - case updateShortSentMessage(flags: Int32, id: Int32, pts: Int32, ptsCount: Int32, date: Int32, media: Api.MessageMedia?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) - case updates(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seq: Int32) - case updatesCombined(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seqStart: Int32, seq: Int32) - case updatesTooLong + enum Timezone: TypeConstructorDescription { + case timezone(id: String, name: String, utcOffset: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .updateShort(let update, let date): + case .timezone(let id, let name, let utcOffset): if boxed { - buffer.appendInt32(2027216577) + buffer.appendInt32(-7173643) } - update.serialize(buffer, true) - serializeInt32(date, buffer: buffer, boxed: false) + serializeString(id, buffer: buffer, boxed: false) + serializeString(name, buffer: buffer, boxed: false) + serializeInt32(utcOffset, buffer: buffer, boxed: false) break - case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities, let ttlPeriod): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .timezone(let id, let name, let utcOffset): + return ("timezone", [("id", id as Any), ("name", name as Any), ("utcOffset", utcOffset as Any)]) + } + } + + public static func parse_timezone(_ reader: BufferReader) -> Timezone? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Timezone.timezone(id: _1!, name: _2!, utcOffset: _3!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum TopPeer: TypeConstructorDescription { + case topPeer(peer: Api.Peer, rating: Double) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .topPeer(let peer, let rating): if boxed { - buffer.appendInt32(1299050149) + buffer.appendInt32(-305282981) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt64(fromId, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {serializeInt64(viaBotId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} + peer.serialize(buffer, true) + serializeDouble(rating, buffer: buffer, boxed: false) break - case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities, let ttlPeriod): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .topPeer(let peer, let rating): + return ("topPeer", [("peer", peer as Any), ("rating", rating as Any)]) + } + } + + public static func parse_topPeer(_ reader: BufferReader) -> TopPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.TopPeer.topPeer(peer: _1!, rating: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum TopPeerCategory: TypeConstructorDescription { + case topPeerCategoryBotsInline + case topPeerCategoryBotsPM + case topPeerCategoryChannels + case topPeerCategoryCorrespondents + case topPeerCategoryForwardChats + case topPeerCategoryForwardUsers + case topPeerCategoryGroups + case topPeerCategoryPhoneCalls + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .topPeerCategoryBotsInline: if boxed { - buffer.appendInt32(826001400) + buffer.appendInt32(344356834) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {serializeInt64(viaBotId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} + break - case .updateShortSentMessage(let flags, let id, let pts, let ptsCount, let date, let media, let entities, let ttlPeriod): + case .topPeerCategoryBotsPM: if boxed { - buffer.appendInt32(-1877614335) + buffer.appendInt32(-1419371685) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} + break - case .updates(let updates, let users, let chats, let date, let seq): + case .topPeerCategoryChannels: if boxed { - buffer.appendInt32(1957577280) + buffer.appendInt32(371037736) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(updates.count)) - for item in updates { - item.serialize(buffer, true) + + break + case .topPeerCategoryCorrespondents: + if boxed { + buffer.appendInt32(104314861) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + + break + case .topPeerCategoryForwardChats: + if boxed { + buffer.appendInt32(-68239120) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + + break + case .topPeerCategoryForwardUsers: + if boxed { + buffer.appendInt32(-1472172887) } - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(seq, buffer: buffer, boxed: false) + break - case .updatesCombined(let updates, let users, let chats, let date, let seqStart, let seq): + case .topPeerCategoryGroups: if boxed { - buffer.appendInt32(1918567619) + buffer.appendInt32(-1122524854) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(updates.count)) - for item in updates { - item.serialize(buffer, true) + + break + case .topPeerCategoryPhoneCalls: + if boxed { + buffer.appendInt32(511092620) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .topPeerCategoryBotsInline: + return ("topPeerCategoryBotsInline", []) + case .topPeerCategoryBotsPM: + return ("topPeerCategoryBotsPM", []) + case .topPeerCategoryChannels: + return ("topPeerCategoryChannels", []) + case .topPeerCategoryCorrespondents: + return ("topPeerCategoryCorrespondents", []) + case .topPeerCategoryForwardChats: + return ("topPeerCategoryForwardChats", []) + case .topPeerCategoryForwardUsers: + return ("topPeerCategoryForwardUsers", []) + case .topPeerCategoryGroups: + return ("topPeerCategoryGroups", []) + case .topPeerCategoryPhoneCalls: + return ("topPeerCategoryPhoneCalls", []) + } + } + + public static func parse_topPeerCategoryBotsInline(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryBotsInline + } + public static func parse_topPeerCategoryBotsPM(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryBotsPM + } + public static func parse_topPeerCategoryChannels(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryChannels + } + public static func parse_topPeerCategoryCorrespondents(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryCorrespondents + } + public static func parse_topPeerCategoryForwardChats(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryForwardChats + } + public static func parse_topPeerCategoryForwardUsers(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryForwardUsers + } + public static func parse_topPeerCategoryGroups(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryGroups + } + public static func parse_topPeerCategoryPhoneCalls(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryPhoneCalls + } + + } +} +public extension Api { + enum TopPeerCategoryPeers: TypeConstructorDescription { + case topPeerCategoryPeers(category: Api.TopPeerCategory, count: Int32, peers: [Api.TopPeer]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .topPeerCategoryPeers(let category, let count, let peers): + if boxed { + buffer.appendInt32(-75283823) } + category.serialize(buffer, true) + serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(peers.count)) + for item in peers { item.serialize(buffer, true) } - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(seqStart, buffer: buffer, boxed: false) - serializeInt32(seq, buffer: buffer, boxed: false) - break - case .updatesTooLong: - if boxed { - buffer.appendInt32(-484987010) - } - break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .updateShort(let update, let date): - return ("updateShort", [("update", update as Any), ("date", date as Any)]) - case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities, let ttlPeriod): - return ("updateShortChatMessage", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("chatId", chatId as Any), ("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any), ("date", date as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("replyTo", replyTo as Any), ("entities", entities as Any), ("ttlPeriod", ttlPeriod as Any)]) - case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities, let ttlPeriod): - return ("updateShortMessage", [("flags", flags as Any), ("id", id as Any), ("userId", userId as Any), ("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any), ("date", date as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("replyTo", replyTo as Any), ("entities", entities as Any), ("ttlPeriod", ttlPeriod as Any)]) - case .updateShortSentMessage(let flags, let id, let pts, let ptsCount, let date, let media, let entities, let ttlPeriod): - return ("updateShortSentMessage", [("flags", flags as Any), ("id", id as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any), ("date", date as Any), ("media", media as Any), ("entities", entities as Any), ("ttlPeriod", ttlPeriod as Any)]) - case .updates(let updates, let users, let chats, let date, let seq): - return ("updates", [("updates", updates as Any), ("users", users as Any), ("chats", chats as Any), ("date", date as Any), ("seq", seq as Any)]) - case .updatesCombined(let updates, let users, let chats, let date, let seqStart, let seq): - return ("updatesCombined", [("updates", updates as Any), ("users", users as Any), ("chats", chats as Any), ("date", date as Any), ("seqStart", seqStart as Any), ("seq", seq as Any)]) - case .updatesTooLong: - return ("updatesTooLong", []) + case .topPeerCategoryPeers(let category, let count, let peers): + return ("topPeerCategoryPeers", [("category", category as Any), ("count", count as Any), ("peers", peers as Any)]) } } - public static func parse_updateShort(_ reader: BufferReader) -> Updates? { - var _1: Api.Update? + public static func parse_topPeerCategoryPeers(_ reader: BufferReader) -> TopPeerCategoryPeers? { + var _1: Api.TopPeerCategory? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Update - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Updates.updateShort(update: _1!, date: _2!) + _1 = Api.parse(reader, signature: signature) as? Api.TopPeerCategory } - else { - return nil - } - } - public static func parse_updateShortChatMessage(_ reader: BufferReader) -> Updates? { - var _1: Int32? - _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - _5 = parseString(reader) - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: Int32? - _8 = reader.readInt32() - var _9: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } } - var _10: Int64? - if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt64() } - var _11: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } } - var _12: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - var _13: Int32? - if Int(_1!) & Int(1 << 25) != 0 {_13 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 25) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.Updates.updateShortChatMessage(flags: _1!, id: _2!, fromId: _3!, chatId: _4!, message: _5!, pts: _6!, ptsCount: _7!, date: _8!, fwdFrom: _9, viaBotId: _10, replyTo: _11, entities: _12, ttlPeriod: _13) - } - else { - return nil + var _3: [Api.TopPeer]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeer.self) } - } - public static func parse_updateShortMessage(_ reader: BufferReader) -> Updates? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } } - var _9: Int64? - if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt64() } - var _10: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } } - var _11: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - var _12: Int32? - if Int(_1!) & Int(1 << 25) != 0 {_12 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 11) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 25) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.Updates.updateShortMessage(flags: _1!, id: _2!, userId: _3!, message: _4!, pts: _5!, ptsCount: _6!, date: _7!, fwdFrom: _8, viaBotId: _9, replyTo: _10, entities: _11, ttlPeriod: _12) + if _c1 && _c2 && _c3 { + return Api.TopPeerCategoryPeers.topPeerCategoryPeers(category: _1!, count: _2!, peers: _3!) } else { return nil } } - public static func parse_updateShortSentMessage(_ reader: BufferReader) -> Updates? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Api.MessageMedia? - if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } } - var _7: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + + } +} +public extension Api { + indirect enum Update: TypeConstructorDescription { + case updateAttachMenuBots + case updateAutoSaveSettings + case updateBotBusinessConnect(connection: Api.BotBusinessConnection, qts: Int32) + case updateBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, peer: Api.Peer, msgId: Int32, chatInstance: Int64, data: Buffer?, gameShortName: String?) + case updateBotChatBoost(peer: Api.Peer, boost: Api.Boost, qts: Int32) + case updateBotChatInviteRequester(peer: Api.Peer, date: Int32, userId: Int64, about: String, invite: Api.ExportedChatInvite, qts: Int32) + case updateBotCommands(peer: Api.Peer, botId: Int64, commands: [Api.BotCommand]) + case updateBotDeleteBusinessMessage(connectionId: String, peer: Api.Peer, messages: [Int32], qts: Int32) + case updateBotEditBusinessMessage(flags: Int32, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, qts: Int32) + case updateBotInlineQuery(flags: Int32, queryId: Int64, userId: Int64, query: String, geo: Api.GeoPoint?, peerType: Api.InlineQueryPeerType?, offset: String) + case updateBotInlineSend(flags: Int32, userId: Int64, query: String, geo: Api.GeoPoint?, id: String, msgId: Api.InputBotInlineMessageID?) + case updateBotMenuButton(botId: Int64, button: Api.BotMenuButton) + case updateBotMessageReaction(peer: Api.Peer, msgId: Int32, date: Int32, actor: Api.Peer, oldReactions: [Api.Reaction], newReactions: [Api.Reaction], qts: Int32) + case updateBotMessageReactions(peer: Api.Peer, msgId: Int32, date: Int32, reactions: [Api.ReactionCount], qts: Int32) + case updateBotNewBusinessMessage(flags: Int32, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, qts: Int32) + case updateBotPrecheckoutQuery(flags: Int32, queryId: Int64, userId: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, currency: String, totalAmount: Int64) + case updateBotShippingQuery(queryId: Int64, userId: Int64, payload: Buffer, shippingAddress: Api.PostAddress) + case updateBotStopped(userId: Int64, date: Int32, stopped: Api.Bool, qts: Int32) + case updateBotWebhookJSON(data: Api.DataJSON) + case updateBotWebhookJSONQuery(queryId: Int64, data: Api.DataJSON, timeout: Int32) + case updateBroadcastRevenueTransactions(peer: Api.Peer, balances: Api.BroadcastRevenueBalances) + case updateChannel(channelId: Int64) + case updateChannelAvailableMessages(channelId: Int64, availableMinId: Int32) + case updateChannelMessageForwards(channelId: Int64, id: Int32, forwards: Int32) + case updateChannelMessageViews(channelId: Int64, id: Int32, views: Int32) + case updateChannelParticipant(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) + case updateChannelPinnedTopic(flags: Int32, channelId: Int64, topicId: Int32) + case updateChannelPinnedTopics(flags: Int32, channelId: Int64, order: [Int32]?) + case updateChannelReadMessagesContents(flags: Int32, channelId: Int64, topMsgId: Int32?, messages: [Int32]) + case updateChannelTooLong(flags: Int32, channelId: Int64, pts: Int32?) + case updateChannelUserTyping(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction) + case updateChannelViewForumAsMessages(channelId: Int64, enabled: Api.Bool) + case updateChannelWebPage(channelId: Int64, webpage: Api.WebPage, pts: Int32, ptsCount: Int32) + case updateChat(chatId: Int64) + case updateChatDefaultBannedRights(peer: Api.Peer, defaultBannedRights: Api.ChatBannedRights, version: Int32) + case updateChatParticipant(flags: Int32, chatId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChatParticipant?, newParticipant: Api.ChatParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) + case updateChatParticipantAdd(chatId: Int64, userId: Int64, inviterId: Int64, date: Int32, version: Int32) + case updateChatParticipantAdmin(chatId: Int64, userId: Int64, isAdmin: Api.Bool, version: Int32) + case updateChatParticipantDelete(chatId: Int64, userId: Int64, version: Int32) + case updateChatParticipants(participants: Api.ChatParticipants) + case updateChatUserTyping(chatId: Int64, fromId: Api.Peer, action: Api.SendMessageAction) + case updateConfig + case updateContactsReset + case updateDcOptions(dcOptions: [Api.DcOption]) + case updateDeleteChannelMessages(channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) + case updateDeleteMessages(messages: [Int32], pts: Int32, ptsCount: Int32) + case updateDeleteQuickReply(shortcutId: Int32) + case updateDeleteQuickReplyMessages(shortcutId: Int32, messages: [Int32]) + case updateDeleteScheduledMessages(peer: Api.Peer, messages: [Int32]) + case updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?) + case updateDialogFilterOrder(order: [Int32]) + case updateDialogFilters + case updateDialogPinned(flags: Int32, folderId: Int32?, peer: Api.DialogPeer) + case updateDialogUnreadMark(flags: Int32, peer: Api.DialogPeer) + case updateDraftMessage(flags: Int32, peer: Api.Peer, topMsgId: Int32?, draft: Api.DraftMessage) + case updateEditChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32) + case updateEditMessage(message: Api.Message, pts: Int32, ptsCount: Int32) + case updateEncryptedChatTyping(chatId: Int32) + case updateEncryptedMessagesRead(chatId: Int32, maxDate: Int32, date: Int32) + case updateEncryption(chat: Api.EncryptedChat, date: Int32) + case updateFavedStickers + case updateFolderPeers(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32) + case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32) + case updateGroupCall(chatId: Int64, call: Api.GroupCall) + case updateGroupCallConnection(flags: Int32, params: Api.DataJSON) + case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32) + case updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) + case updateLangPack(difference: Api.LangPackDifference) + case updateLangPackTooLong(langCode: String) + case updateLoginToken + case updateMessageExtendedMedia(peer: Api.Peer, msgId: Int32, extendedMedia: Api.MessageExtendedMedia) + case updateMessageID(id: Int32, randomId: Int64) + case updateMessagePoll(flags: Int32, pollId: Int64, poll: Api.Poll?, results: Api.PollResults) + case updateMessagePollVote(pollId: Int64, peer: Api.Peer, options: [Buffer], qts: Int32) + case updateMessageReactions(flags: Int32, peer: Api.Peer, msgId: Int32, topMsgId: Int32?, reactions: Api.MessageReactions) + case updateMoveStickerSetToTop(flags: Int32, stickerset: Int64) + case updateNewAuthorization(flags: Int32, hash: Int64, date: Int32?, device: String?, location: String?) + case updateNewChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32) + case updateNewEncryptedMessage(message: Api.EncryptedMessage, qts: Int32) + case updateNewMessage(message: Api.Message, pts: Int32, ptsCount: Int32) + case updateNewQuickReply(quickReply: Api.QuickReply) + case updateNewScheduledMessage(message: Api.Message) + case updateNewStickerSet(stickerset: Api.messages.StickerSet) + case updateNewStoryReaction(storyId: Int32, peer: Api.Peer, reaction: Api.Reaction) + case updateNotifySettings(peer: Api.NotifyPeer, notifySettings: Api.PeerNotifySettings) + case updatePeerBlocked(flags: Int32, peerId: Api.Peer) + case updatePeerHistoryTTL(flags: Int32, peer: Api.Peer, ttlPeriod: Int32?) + case updatePeerLocated(peers: [Api.PeerLocated]) + case updatePeerSettings(peer: Api.Peer, settings: Api.PeerSettings) + case updatePeerWallpaper(flags: Int32, peer: Api.Peer, wallpaper: Api.WallPaper?) + case updatePendingJoinRequests(peer: Api.Peer, requestsPending: Int32, recentRequesters: [Int64]) + case updatePhoneCall(phoneCall: Api.PhoneCall) + case updatePhoneCallSignalingData(phoneCallId: Int64, data: Buffer) + case updatePinnedChannelMessages(flags: Int32, channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) + case updatePinnedDialogs(flags: Int32, folderId: Int32?, order: [Api.DialogPeer]?) + case updatePinnedMessages(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32) + case updatePinnedSavedDialogs(flags: Int32, order: [Api.DialogPeer]?) + case updatePrivacy(key: Api.PrivacyKey, rules: [Api.PrivacyRule]) + case updatePtsChanged + case updateQuickReplies(quickReplies: [Api.QuickReply]) + case updateQuickReplyMessage(message: Api.Message) + case updateReadChannelDiscussionInbox(flags: Int32, channelId: Int64, topMsgId: Int32, readMaxId: Int32, broadcastId: Int64?, broadcastPost: Int32?) + case updateReadChannelDiscussionOutbox(channelId: Int64, topMsgId: Int32, readMaxId: Int32) + case updateReadChannelInbox(flags: Int32, folderId: Int32?, channelId: Int64, maxId: Int32, stillUnreadCount: Int32, pts: Int32) + case updateReadChannelOutbox(channelId: Int64, maxId: Int32) + case updateReadFeaturedEmojiStickers + case updateReadFeaturedStickers + case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32) + case updateReadHistoryOutbox(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32) + case updateReadMessagesContents(flags: Int32, messages: [Int32], pts: Int32, ptsCount: Int32, date: Int32?) + case updateReadStories(peer: Api.Peer, maxId: Int32) + case updateRecentEmojiStatuses + case updateRecentReactions + case updateRecentStickers + case updateSavedDialogPinned(flags: Int32, peer: Api.DialogPeer) + case updateSavedGifs + case updateSavedReactionTags + case updateSavedRingtones + case updateSentStoryReaction(peer: Api.Peer, storyId: Int32, reaction: Api.Reaction) + case updateServiceNotification(flags: Int32, inboxDate: Int32?, type: String, message: String, media: Api.MessageMedia, entities: [Api.MessageEntity]) + case updateSmsJob(jobId: String) + case updateStarsBalance(balance: Int64) + case updateStickerSets(flags: Int32) + case updateStickerSetsOrder(flags: Int32, order: [Int64]) + case updateStoriesStealthMode(stealthMode: Api.StoriesStealthMode) + case updateStory(peer: Api.Peer, story: Api.StoryItem) + case updateStoryID(id: Int32, randomId: Int64) + case updateTheme(theme: Api.Theme) + case updateTranscribedAudio(flags: Int32, peer: Api.Peer, msgId: Int32, transcriptionId: Int64, text: String) + case updateUser(userId: Int64) + case updateUserEmojiStatus(userId: Int64, emojiStatus: Api.EmojiStatus) + case updateUserName(userId: Int64, firstName: String, lastName: String, usernames: [Api.Username]) + case updateUserPhone(userId: Int64, phone: String) + case updateUserStatus(userId: Int64, status: Api.UserStatus) + case updateUserTyping(userId: Int64, action: Api.SendMessageAction) + case updateWebPage(webpage: Api.WebPage, pts: Int32, ptsCount: Int32) + case updateWebViewResultSent(queryId: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .updateAttachMenuBots: + if boxed { + buffer.appendInt32(397910539) + } + + break + case .updateAutoSaveSettings: + if boxed { + buffer.appendInt32(-335171433) + } + + break + case .updateBotBusinessConnect(let connection, let qts): + if boxed { + buffer.appendInt32(-1964652166) + } + connection.serialize(buffer, true) + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateBotCallbackQuery(let flags, let queryId, let userId, let peer, let msgId, let chatInstance, let data, let gameShortName): + if boxed { + buffer.appendInt32(-1177566067) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt64(chatInstance, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(gameShortName!, buffer: buffer, boxed: false)} + break + case .updateBotChatBoost(let peer, let boost, let qts): + if boxed { + buffer.appendInt32(-1873947492) + } + peer.serialize(buffer, true) + boost.serialize(buffer, true) + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateBotChatInviteRequester(let peer, let date, let userId, let about, let invite, let qts): + if boxed { + buffer.appendInt32(299870598) + } + peer.serialize(buffer, true) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeString(about, buffer: buffer, boxed: false) + invite.serialize(buffer, true) + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateBotCommands(let peer, let botId, let commands): + if boxed { + buffer.appendInt32(1299263278) + } + peer.serialize(buffer, true) + serializeInt64(botId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(commands.count)) + for item in commands { + item.serialize(buffer, true) + } + break + case .updateBotDeleteBusinessMessage(let connectionId, let peer, let messages, let qts): + if boxed { + buffer.appendInt32(-1607821266) + } + serializeString(connectionId, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateBotEditBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): + if boxed { + buffer.appendInt32(132077692) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(connectionId, buffer: buffer, boxed: false) + message.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {replyToMessage!.serialize(buffer, true)} + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset): + if boxed { + buffer.appendInt32(1232025500) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeString(query, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {geo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {peerType!.serialize(buffer, true)} + serializeString(offset, buffer: buffer, boxed: false) + break + case .updateBotInlineSend(let flags, let userId, let query, let geo, let id, let msgId): + if boxed { + buffer.appendInt32(317794823) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeString(query, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {geo!.serialize(buffer, true)} + serializeString(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {msgId!.serialize(buffer, true)} + break + case .updateBotMenuButton(let botId, let button): + if boxed { + buffer.appendInt32(347625491) + } + serializeInt64(botId, buffer: buffer, boxed: false) + button.serialize(buffer, true) + break + case .updateBotMessageReaction(let peer, let msgId, let date, let actor, let oldReactions, let newReactions, let qts): + if boxed { + buffer.appendInt32(-1407069234) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + actor.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(oldReactions.count)) + for item in oldReactions { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(newReactions.count)) + for item in newReactions { + item.serialize(buffer, true) + } + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateBotMessageReactions(let peer, let msgId, let date, let reactions, let qts): + if boxed { + buffer.appendInt32(164329305) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(reactions.count)) + for item in reactions { + item.serialize(buffer, true) + } + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateBotNewBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): + if boxed { + buffer.appendInt32(-1646578564) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(connectionId, buffer: buffer, boxed: false) + message.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {replyToMessage!.serialize(buffer, true)} + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateBotPrecheckoutQuery(let flags, let queryId, let userId, let payload, let info, let shippingOptionId, let currency, let totalAmount): + if boxed { + buffer.appendInt32(-1934976362) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeBytes(payload, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(shippingOptionId!, buffer: buffer, boxed: false)} + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(totalAmount, buffer: buffer, boxed: false) + break + case .updateBotShippingQuery(let queryId, let userId, let payload, let shippingAddress): + if boxed { + buffer.appendInt32(-1246823043) + } + serializeInt64(queryId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeBytes(payload, buffer: buffer, boxed: false) + shippingAddress.serialize(buffer, true) + break + case .updateBotStopped(let userId, let date, let stopped, let qts): + if boxed { + buffer.appendInt32(-997782967) + } + serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + stopped.serialize(buffer, true) + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateBotWebhookJSON(let data): + if boxed { + buffer.appendInt32(-2095595325) + } + data.serialize(buffer, true) + break + case .updateBotWebhookJSONQuery(let queryId, let data, let timeout): + if boxed { + buffer.appendInt32(-1684914010) + } + serializeInt64(queryId, buffer: buffer, boxed: false) + data.serialize(buffer, true) + serializeInt32(timeout, buffer: buffer, boxed: false) + break + case .updateBroadcastRevenueTransactions(let peer, let balances): + if boxed { + buffer.appendInt32(-539401739) + } + peer.serialize(buffer, true) + balances.serialize(buffer, true) + break + case .updateChannel(let channelId): + if boxed { + buffer.appendInt32(1666927625) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + break + case .updateChannelAvailableMessages(let channelId, let availableMinId): + if boxed { + buffer.appendInt32(-1304443240) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(availableMinId, buffer: buffer, boxed: false) + break + case .updateChannelMessageForwards(let channelId, let id, let forwards): + if boxed { + buffer.appendInt32(-761649164) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(forwards, buffer: buffer, boxed: false) + break + case .updateChannelMessageViews(let channelId, let id, let views): + if boxed { + buffer.appendInt32(-232346616) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(views, buffer: buffer, boxed: false) + break + case .updateChannelParticipant(let flags, let channelId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): + if boxed { + buffer.appendInt32(-1738720581) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(actorId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {prevParticipant!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {newParticipant!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {invite!.serialize(buffer, true)} + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateChannelPinnedTopic(let flags, let channelId, let topicId): + if boxed { + buffer.appendInt32(422509539) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(topicId, buffer: buffer, boxed: false) + break + case .updateChannelPinnedTopics(let flags, let channelId, let order): + if boxed { + buffer.appendInt32(-31881726) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order!.count)) + for item in order! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + break + case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages): + if boxed { + buffer.appendInt32(-366410403) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .updateChannelTooLong(let flags, let channelId, let pts): + if boxed { + buffer.appendInt32(277713951) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(pts!, buffer: buffer, boxed: false)} + break + case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let fromId, let action): + if boxed { + buffer.appendInt32(-1937192669) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + fromId.serialize(buffer, true) + action.serialize(buffer, true) + break + case .updateChannelViewForumAsMessages(let channelId, let enabled): + if boxed { + buffer.appendInt32(129403168) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + enabled.serialize(buffer, true) + break + case .updateChannelWebPage(let channelId, let webpage, let pts, let ptsCount): + if boxed { + buffer.appendInt32(791390623) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + webpage.serialize(buffer, true) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateChat(let chatId): + if boxed { + buffer.appendInt32(-124097970) + } + serializeInt64(chatId, buffer: buffer, boxed: false) + break + case .updateChatDefaultBannedRights(let peer, let defaultBannedRights, let version): + if boxed { + buffer.appendInt32(1421875280) + } + peer.serialize(buffer, true) + defaultBannedRights.serialize(buffer, true) + serializeInt32(version, buffer: buffer, boxed: false) + break + case .updateChatParticipant(let flags, let chatId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): + if boxed { + buffer.appendInt32(-796432838) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(actorId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {prevParticipant!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {newParticipant!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {invite!.serialize(buffer, true)} + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateChatParticipantAdd(let chatId, let userId, let inviterId, let date, let version): + if boxed { + buffer.appendInt32(1037718609) + } + serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt64(inviterId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(version, buffer: buffer, boxed: false) + break + case .updateChatParticipantAdmin(let chatId, let userId, let isAdmin, let version): + if boxed { + buffer.appendInt32(-674602590) + } + serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + isAdmin.serialize(buffer, true) + serializeInt32(version, buffer: buffer, boxed: false) + break + case .updateChatParticipantDelete(let chatId, let userId, let version): + if boxed { + buffer.appendInt32(-483443337) + } + serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(version, buffer: buffer, boxed: false) + break + case .updateChatParticipants(let participants): + if boxed { + buffer.appendInt32(125178264) + } + participants.serialize(buffer, true) + break + case .updateChatUserTyping(let chatId, let fromId, let action): + if boxed { + buffer.appendInt32(-2092401936) + } + serializeInt64(chatId, buffer: buffer, boxed: false) + fromId.serialize(buffer, true) + action.serialize(buffer, true) + break + case .updateConfig: + if boxed { + buffer.appendInt32(-1574314746) + } + + break + case .updateContactsReset: + if boxed { + buffer.appendInt32(1887741886) + } + + break + case .updateDcOptions(let dcOptions): + if boxed { + buffer.appendInt32(-1906403213) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(dcOptions.count)) + for item in dcOptions { + item.serialize(buffer, true) + } + break + case .updateDeleteChannelMessages(let channelId, let messages, let pts, let ptsCount): + if boxed { + buffer.appendInt32(-1020437742) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateDeleteMessages(let messages, let pts, let ptsCount): + if boxed { + buffer.appendInt32(-1576161051) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateDeleteQuickReply(let shortcutId): + if boxed { + buffer.appendInt32(1407644140) + } + serializeInt32(shortcutId, buffer: buffer, boxed: false) + break + case .updateDeleteQuickReplyMessages(let shortcutId, let messages): + if boxed { + buffer.appendInt32(1450174413) + } + serializeInt32(shortcutId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .updateDeleteScheduledMessages(let peer, let messages): + if boxed { + buffer.appendInt32(-1870238482) + } + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .updateDialogFilter(let flags, let id, let filter): + if boxed { + buffer.appendInt32(654302845) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {filter!.serialize(buffer, true)} + break + case .updateDialogFilterOrder(let order): + if boxed { + buffer.appendInt32(-1512627963) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .updateDialogFilters: + if boxed { + buffer.appendInt32(889491791) + } + + break + case .updateDialogPinned(let flags, let folderId, let peer): + if boxed { + buffer.appendInt32(1852826908) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} + peer.serialize(buffer, true) + break + case .updateDialogUnreadMark(let flags, let peer): + if boxed { + buffer.appendInt32(-513517117) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + break + case .updateDraftMessage(let flags, let peer, let topMsgId, let draft): + if boxed { + buffer.appendInt32(457829485) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + draft.serialize(buffer, true) + break + case .updateEditChannelMessage(let message, let pts, let ptsCount): + if boxed { + buffer.appendInt32(457133559) + } + message.serialize(buffer, true) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateEditMessage(let message, let pts, let ptsCount): + if boxed { + buffer.appendInt32(-469536605) + } + message.serialize(buffer, true) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateEncryptedChatTyping(let chatId): + if boxed { + buffer.appendInt32(386986326) + } + serializeInt32(chatId, buffer: buffer, boxed: false) + break + case .updateEncryptedMessagesRead(let chatId, let maxDate, let date): + if boxed { + buffer.appendInt32(956179895) + } + serializeInt32(chatId, buffer: buffer, boxed: false) + serializeInt32(maxDate, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + break + case .updateEncryption(let chat, let date): + if boxed { + buffer.appendInt32(-1264392051) + } + chat.serialize(buffer, true) + serializeInt32(date, buffer: buffer, boxed: false) + break + case .updateFavedStickers: + if boxed { + buffer.appendInt32(-451831443) + } + + break + case .updateFolderPeers(let folderPeers, let pts, let ptsCount): + if boxed { + buffer.appendInt32(422972864) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(folderPeers.count)) + for item in folderPeers { + item.serialize(buffer, true) + } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateGeoLiveViewed(let peer, let msgId): + if boxed { + buffer.appendInt32(-2027964103) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + break + case .updateGroupCall(let chatId, let call): + if boxed { + buffer.appendInt32(347227392) + } + serializeInt64(chatId, buffer: buffer, boxed: false) + call.serialize(buffer, true) + break + case .updateGroupCallConnection(let flags, let params): + if boxed { + buffer.appendInt32(192428418) + } + serializeInt32(flags, buffer: buffer, boxed: false) + params.serialize(buffer, true) + break + case .updateGroupCallParticipants(let call, let participants, let version): + if boxed { + buffer.appendInt32(-219423922) + } + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(participants.count)) + for item in participants { + item.serialize(buffer, true) + } + serializeInt32(version, buffer: buffer, boxed: false) + break + case .updateInlineBotCallbackQuery(let flags, let queryId, let userId, let msgId, let chatInstance, let data, let gameShortName): + if boxed { + buffer.appendInt32(1763610706) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + msgId.serialize(buffer, true) + serializeInt64(chatInstance, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(gameShortName!, buffer: buffer, boxed: false)} + break + case .updateLangPack(let difference): + if boxed { + buffer.appendInt32(1442983757) + } + difference.serialize(buffer, true) + break + case .updateLangPackTooLong(let langCode): + if boxed { + buffer.appendInt32(1180041828) + } + serializeString(langCode, buffer: buffer, boxed: false) + break + case .updateLoginToken: + if boxed { + buffer.appendInt32(1448076945) + } + + break + case .updateMessageExtendedMedia(let peer, let msgId, let extendedMedia): + if boxed { + buffer.appendInt32(1517529484) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + extendedMedia.serialize(buffer, true) + break + case .updateMessageID(let id, let randomId): + if boxed { + buffer.appendInt32(1318109142) + } + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + break + case .updateMessagePoll(let flags, let pollId, let poll, let results): + if boxed { + buffer.appendInt32(-1398708869) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(pollId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {poll!.serialize(buffer, true)} + results.serialize(buffer, true) + break + case .updateMessagePollVote(let pollId, let peer, let options, let qts): + if boxed { + buffer.appendInt32(619974263) + } + serializeInt64(pollId, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(options.count)) + for item in options { + serializeBytes(item, buffer: buffer, boxed: false) + } + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateMessageReactions(let flags, let peer, let msgId, let topMsgId, let reactions): + if boxed { + buffer.appendInt32(1578843320) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + reactions.serialize(buffer, true) + break + case .updateMoveStickerSetToTop(let flags, let stickerset): + if boxed { + buffer.appendInt32(-2030252155) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(stickerset, buffer: buffer, boxed: false) + break + case .updateNewAuthorization(let flags, let hash, let date, let device, let location): + if boxed { + buffer.appendInt32(-1991136273) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(date!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeString(device!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeString(location!, buffer: buffer, boxed: false)} + break + case .updateNewChannelMessage(let message, let pts, let ptsCount): + if boxed { + buffer.appendInt32(1656358105) + } + message.serialize(buffer, true) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateNewEncryptedMessage(let message, let qts): + if boxed { + buffer.appendInt32(314359194) + } + message.serialize(buffer, true) + serializeInt32(qts, buffer: buffer, boxed: false) + break + case .updateNewMessage(let message, let pts, let ptsCount): + if boxed { + buffer.appendInt32(522914557) + } + message.serialize(buffer, true) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateNewQuickReply(let quickReply): + if boxed { + buffer.appendInt32(-180508905) + } + quickReply.serialize(buffer, true) + break + case .updateNewScheduledMessage(let message): + if boxed { + buffer.appendInt32(967122427) + } + message.serialize(buffer, true) + break + case .updateNewStickerSet(let stickerset): + if boxed { + buffer.appendInt32(1753886890) + } + stickerset.serialize(buffer, true) + break + case .updateNewStoryReaction(let storyId, let peer, let reaction): + if boxed { + buffer.appendInt32(405070859) + } + serializeInt32(storyId, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + reaction.serialize(buffer, true) + break + case .updateNotifySettings(let peer, let notifySettings): + if boxed { + buffer.appendInt32(-1094555409) + } + peer.serialize(buffer, true) + notifySettings.serialize(buffer, true) + break + case .updatePeerBlocked(let flags, let peerId): + if boxed { + buffer.appendInt32(-337610926) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peerId.serialize(buffer, true) + break + case .updatePeerHistoryTTL(let flags, let peer, let ttlPeriod): + if boxed { + buffer.appendInt32(-1147422299) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} + break + case .updatePeerLocated(let peers): + if boxed { + buffer.appendInt32(-1263546448) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + break + case .updatePeerSettings(let peer, let settings): + if boxed { + buffer.appendInt32(1786671974) + } + peer.serialize(buffer, true) + settings.serialize(buffer, true) + break + case .updatePeerWallpaper(let flags, let peer, let wallpaper): + if boxed { + buffer.appendInt32(-1371598819) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {wallpaper!.serialize(buffer, true)} + break + case .updatePendingJoinRequests(let peer, let requestsPending, let recentRequesters): + if boxed { + buffer.appendInt32(1885586395) + } + peer.serialize(buffer, true) + serializeInt32(requestsPending, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentRequesters.count)) + for item in recentRequesters { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .updatePhoneCall(let phoneCall): + if boxed { + buffer.appendInt32(-1425052898) + } + phoneCall.serialize(buffer, true) + break + case .updatePhoneCallSignalingData(let phoneCallId, let data): + if boxed { + buffer.appendInt32(643940105) + } + serializeInt64(phoneCallId, buffer: buffer, boxed: false) + serializeBytes(data, buffer: buffer, boxed: false) + break + case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount): + if boxed { + buffer.appendInt32(1538885128) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updatePinnedDialogs(let flags, let folderId, let order): + if boxed { + buffer.appendInt32(-99664734) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order!.count)) + for item in order! { + item.serialize(buffer, true) + }} + break + case .updatePinnedMessages(let flags, let peer, let messages, let pts, let ptsCount): + if boxed { + buffer.appendInt32(-309990731) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updatePinnedSavedDialogs(let flags, let order): + if boxed { + buffer.appendInt32(1751942566) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order!.count)) + for item in order! { + item.serialize(buffer, true) + }} + break + case .updatePrivacy(let key, let rules): + if boxed { + buffer.appendInt32(-298113238) + } + key.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(rules.count)) + for item in rules { + item.serialize(buffer, true) + } + break + case .updatePtsChanged: + if boxed { + buffer.appendInt32(861169551) + } + + break + case .updateQuickReplies(let quickReplies): + if boxed { + buffer.appendInt32(-112784718) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(quickReplies.count)) + for item in quickReplies { + item.serialize(buffer, true) + } + break + case .updateQuickReplyMessage(let message): + if boxed { + buffer.appendInt32(1040518415) + } + message.serialize(buffer, true) + break + case .updateReadChannelDiscussionInbox(let flags, let channelId, let topMsgId, let readMaxId, let broadcastId, let broadcastPost): + if boxed { + buffer.appendInt32(-693004986) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(topMsgId, buffer: buffer, boxed: false) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(broadcastId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(broadcastPost!, buffer: buffer, boxed: false)} + break + case .updateReadChannelDiscussionOutbox(let channelId, let topMsgId, let readMaxId): + if boxed { + buffer.appendInt32(1767677564) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(topMsgId, buffer: buffer, boxed: false) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + break + case .updateReadChannelInbox(let flags, let folderId, let channelId, let maxId, let stillUnreadCount, let pts): + if boxed { + buffer.appendInt32(-1842450928) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(stillUnreadCount, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + break + case .updateReadChannelOutbox(let channelId, let maxId): + if boxed { + buffer.appendInt32(-1218471511) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + break + case .updateReadFeaturedEmojiStickers: + if boxed { + buffer.appendInt32(-78886548) + } + + break + case .updateReadFeaturedStickers: + if boxed { + buffer.appendInt32(1461528386) + } + + break + case .updateReadHistoryInbox(let flags, let folderId, let peer, let maxId, let stillUnreadCount, let pts, let ptsCount): + if boxed { + buffer.appendInt32(-1667805217) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(stillUnreadCount, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateReadHistoryOutbox(let peer, let maxId, let pts, let ptsCount): + if boxed { + buffer.appendInt32(791617983) + } + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateReadMessagesContents(let flags, let messages, let pts, let ptsCount, let date): + if boxed { + buffer.appendInt32(-131960447) + } + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(date!, buffer: buffer, boxed: false)} + break + case .updateReadStories(let peer, let maxId): + if boxed { + buffer.appendInt32(-145845461) + } + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + break + case .updateRecentEmojiStatuses: + if boxed { + buffer.appendInt32(821314523) + } + + break + case .updateRecentReactions: + if boxed { + buffer.appendInt32(1870160884) + } + + break + case .updateRecentStickers: + if boxed { + buffer.appendInt32(-1706939360) + } + + break + case .updateSavedDialogPinned(let flags, let peer): + if boxed { + buffer.appendInt32(-1364222348) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + break + case .updateSavedGifs: + if boxed { + buffer.appendInt32(-1821035490) + } + + break + case .updateSavedReactionTags: + if boxed { + buffer.appendInt32(969307186) + } + + break + case .updateSavedRingtones: + if boxed { + buffer.appendInt32(1960361625) + } + + break + case .updateSentStoryReaction(let peer, let storyId, let reaction): + if boxed { + buffer.appendInt32(2103604867) + } + peer.serialize(buffer, true) + serializeInt32(storyId, buffer: buffer, boxed: false) + reaction.serialize(buffer, true) + break + case .updateServiceNotification(let flags, let inboxDate, let type, let message, let media, let entities): + if boxed { + buffer.appendInt32(-337352679) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(inboxDate!, buffer: buffer, boxed: false)} + serializeString(type, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + media.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities.count)) + for item in entities { + item.serialize(buffer, true) + } + break + case .updateSmsJob(let jobId): + if boxed { + buffer.appendInt32(-245208620) + } + serializeString(jobId, buffer: buffer, boxed: false) + break + case .updateStarsBalance(let balance): + if boxed { + buffer.appendInt32(263737752) + } + serializeInt64(balance, buffer: buffer, boxed: false) + break + case .updateStickerSets(let flags): + if boxed { + buffer.appendInt32(834816008) + } + serializeInt32(flags, buffer: buffer, boxed: false) + break + case .updateStickerSetsOrder(let flags, let order): + if boxed { + buffer.appendInt32(196268545) + } + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .updateStoriesStealthMode(let stealthMode): + if boxed { + buffer.appendInt32(738741697) + } + stealthMode.serialize(buffer, true) + break + case .updateStory(let peer, let story): + if boxed { + buffer.appendInt32(1974712216) + } + peer.serialize(buffer, true) + story.serialize(buffer, true) + break + case .updateStoryID(let id, let randomId): + if boxed { + buffer.appendInt32(468923833) + } + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + break + case .updateTheme(let theme): + if boxed { + buffer.appendInt32(-2112423005) + } + theme.serialize(buffer, true) + break + case .updateTranscribedAudio(let flags, let peer, let msgId, let transcriptionId, let text): + if boxed { + buffer.appendInt32(8703322) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt64(transcriptionId, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + break + case .updateUser(let userId): + if boxed { + buffer.appendInt32(542282808) + } + serializeInt64(userId, buffer: buffer, boxed: false) + break + case .updateUserEmojiStatus(let userId, let emojiStatus): + if boxed { + buffer.appendInt32(674706841) + } + serializeInt64(userId, buffer: buffer, boxed: false) + emojiStatus.serialize(buffer, true) + break + case .updateUserName(let userId, let firstName, let lastName, let usernames): + if boxed { + buffer.appendInt32(-1484486364) + } + serializeInt64(userId, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(usernames.count)) + for item in usernames { + item.serialize(buffer, true) + } + break + case .updateUserPhone(let userId, let phone): + if boxed { + buffer.appendInt32(88680979) + } + serializeInt64(userId, buffer: buffer, boxed: false) + serializeString(phone, buffer: buffer, boxed: false) + break + case .updateUserStatus(let userId, let status): + if boxed { + buffer.appendInt32(-440534818) + } + serializeInt64(userId, buffer: buffer, boxed: false) + status.serialize(buffer, true) + break + case .updateUserTyping(let userId, let action): + if boxed { + buffer.appendInt32(-1071741569) + } + serializeInt64(userId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + case .updateWebPage(let webpage, let pts, let ptsCount): + if boxed { + buffer.appendInt32(2139689491) + } + webpage.serialize(buffer, true) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + case .updateWebViewResultSent(let queryId): + if boxed { + buffer.appendInt32(361936797) + } + serializeInt64(queryId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .updateAttachMenuBots: + return ("updateAttachMenuBots", []) + case .updateAutoSaveSettings: + return ("updateAutoSaveSettings", []) + case .updateBotBusinessConnect(let connection, let qts): + return ("updateBotBusinessConnect", [("connection", connection as Any), ("qts", qts as Any)]) + case .updateBotCallbackQuery(let flags, let queryId, let userId, let peer, let msgId, let chatInstance, let data, let gameShortName): + return ("updateBotCallbackQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("peer", peer as Any), ("msgId", msgId as Any), ("chatInstance", chatInstance as Any), ("data", data as Any), ("gameShortName", gameShortName as Any)]) + case .updateBotChatBoost(let peer, let boost, let qts): + return ("updateBotChatBoost", [("peer", peer as Any), ("boost", boost as Any), ("qts", qts as Any)]) + case .updateBotChatInviteRequester(let peer, let date, let userId, let about, let invite, let qts): + return ("updateBotChatInviteRequester", [("peer", peer as Any), ("date", date as Any), ("userId", userId as Any), ("about", about as Any), ("invite", invite as Any), ("qts", qts as Any)]) + case .updateBotCommands(let peer, let botId, let commands): + return ("updateBotCommands", [("peer", peer as Any), ("botId", botId as Any), ("commands", commands as Any)]) + case .updateBotDeleteBusinessMessage(let connectionId, let peer, let messages, let qts): + return ("updateBotDeleteBusinessMessage", [("connectionId", connectionId as Any), ("peer", peer as Any), ("messages", messages as Any), ("qts", qts as Any)]) + case .updateBotEditBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): + return ("updateBotEditBusinessMessage", [("flags", flags as Any), ("connectionId", connectionId as Any), ("message", message as Any), ("replyToMessage", replyToMessage as Any), ("qts", qts as Any)]) + case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset): + return ("updateBotInlineQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("query", query as Any), ("geo", geo as Any), ("peerType", peerType as Any), ("offset", offset as Any)]) + case .updateBotInlineSend(let flags, let userId, let query, let geo, let id, let msgId): + return ("updateBotInlineSend", [("flags", flags as Any), ("userId", userId as Any), ("query", query as Any), ("geo", geo as Any), ("id", id as Any), ("msgId", msgId as Any)]) + case .updateBotMenuButton(let botId, let button): + return ("updateBotMenuButton", [("botId", botId as Any), ("button", button as Any)]) + case .updateBotMessageReaction(let peer, let msgId, let date, let actor, let oldReactions, let newReactions, let qts): + return ("updateBotMessageReaction", [("peer", peer as Any), ("msgId", msgId as Any), ("date", date as Any), ("actor", actor as Any), ("oldReactions", oldReactions as Any), ("newReactions", newReactions as Any), ("qts", qts as Any)]) + case .updateBotMessageReactions(let peer, let msgId, let date, let reactions, let qts): + return ("updateBotMessageReactions", [("peer", peer as Any), ("msgId", msgId as Any), ("date", date as Any), ("reactions", reactions as Any), ("qts", qts as Any)]) + case .updateBotNewBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): + return ("updateBotNewBusinessMessage", [("flags", flags as Any), ("connectionId", connectionId as Any), ("message", message as Any), ("replyToMessage", replyToMessage as Any), ("qts", qts as Any)]) + case .updateBotPrecheckoutQuery(let flags, let queryId, let userId, let payload, let info, let shippingOptionId, let currency, let totalAmount): + return ("updateBotPrecheckoutQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("payload", payload as Any), ("info", info as Any), ("shippingOptionId", shippingOptionId as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any)]) + case .updateBotShippingQuery(let queryId, let userId, let payload, let shippingAddress): + return ("updateBotShippingQuery", [("queryId", queryId as Any), ("userId", userId as Any), ("payload", payload as Any), ("shippingAddress", shippingAddress as Any)]) + case .updateBotStopped(let userId, let date, let stopped, let qts): + return ("updateBotStopped", [("userId", userId as Any), ("date", date as Any), ("stopped", stopped as Any), ("qts", qts as Any)]) + case .updateBotWebhookJSON(let data): + return ("updateBotWebhookJSON", [("data", data as Any)]) + case .updateBotWebhookJSONQuery(let queryId, let data, let timeout): + return ("updateBotWebhookJSONQuery", [("queryId", queryId as Any), ("data", data as Any), ("timeout", timeout as Any)]) + case .updateBroadcastRevenueTransactions(let peer, let balances): + return ("updateBroadcastRevenueTransactions", [("peer", peer as Any), ("balances", balances as Any)]) + case .updateChannel(let channelId): + return ("updateChannel", [("channelId", channelId as Any)]) + case .updateChannelAvailableMessages(let channelId, let availableMinId): + return ("updateChannelAvailableMessages", [("channelId", channelId as Any), ("availableMinId", availableMinId as Any)]) + case .updateChannelMessageForwards(let channelId, let id, let forwards): + return ("updateChannelMessageForwards", [("channelId", channelId as Any), ("id", id as Any), ("forwards", forwards as Any)]) + case .updateChannelMessageViews(let channelId, let id, let views): + return ("updateChannelMessageViews", [("channelId", channelId as Any), ("id", id as Any), ("views", views as Any)]) + case .updateChannelParticipant(let flags, let channelId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): + return ("updateChannelParticipant", [("flags", flags as Any), ("channelId", channelId as Any), ("date", date as Any), ("actorId", actorId as Any), ("userId", userId as Any), ("prevParticipant", prevParticipant as Any), ("newParticipant", newParticipant as Any), ("invite", invite as Any), ("qts", qts as Any)]) + case .updateChannelPinnedTopic(let flags, let channelId, let topicId): + return ("updateChannelPinnedTopic", [("flags", flags as Any), ("channelId", channelId as Any), ("topicId", topicId as Any)]) + case .updateChannelPinnedTopics(let flags, let channelId, let order): + return ("updateChannelPinnedTopics", [("flags", flags as Any), ("channelId", channelId as Any), ("order", order as Any)]) + case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages): + return ("updateChannelReadMessagesContents", [("flags", flags as Any), ("channelId", channelId as Any), ("topMsgId", topMsgId as Any), ("messages", messages as Any)]) + case .updateChannelTooLong(let flags, let channelId, let pts): + return ("updateChannelTooLong", [("flags", flags as Any), ("channelId", channelId as Any), ("pts", pts as Any)]) + case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let fromId, let action): + return ("updateChannelUserTyping", [("flags", flags as Any), ("channelId", channelId as Any), ("topMsgId", topMsgId as Any), ("fromId", fromId as Any), ("action", action as Any)]) + case .updateChannelViewForumAsMessages(let channelId, let enabled): + return ("updateChannelViewForumAsMessages", [("channelId", channelId as Any), ("enabled", enabled as Any)]) + case .updateChannelWebPage(let channelId, let webpage, let pts, let ptsCount): + return ("updateChannelWebPage", [("channelId", channelId as Any), ("webpage", webpage as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateChat(let chatId): + return ("updateChat", [("chatId", chatId as Any)]) + case .updateChatDefaultBannedRights(let peer, let defaultBannedRights, let version): + return ("updateChatDefaultBannedRights", [("peer", peer as Any), ("defaultBannedRights", defaultBannedRights as Any), ("version", version as Any)]) + case .updateChatParticipant(let flags, let chatId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): + return ("updateChatParticipant", [("flags", flags as Any), ("chatId", chatId as Any), ("date", date as Any), ("actorId", actorId as Any), ("userId", userId as Any), ("prevParticipant", prevParticipant as Any), ("newParticipant", newParticipant as Any), ("invite", invite as Any), ("qts", qts as Any)]) + case .updateChatParticipantAdd(let chatId, let userId, let inviterId, let date, let version): + return ("updateChatParticipantAdd", [("chatId", chatId as Any), ("userId", userId as Any), ("inviterId", inviterId as Any), ("date", date as Any), ("version", version as Any)]) + case .updateChatParticipantAdmin(let chatId, let userId, let isAdmin, let version): + return ("updateChatParticipantAdmin", [("chatId", chatId as Any), ("userId", userId as Any), ("isAdmin", isAdmin as Any), ("version", version as Any)]) + case .updateChatParticipantDelete(let chatId, let userId, let version): + return ("updateChatParticipantDelete", [("chatId", chatId as Any), ("userId", userId as Any), ("version", version as Any)]) + case .updateChatParticipants(let participants): + return ("updateChatParticipants", [("participants", participants as Any)]) + case .updateChatUserTyping(let chatId, let fromId, let action): + return ("updateChatUserTyping", [("chatId", chatId as Any), ("fromId", fromId as Any), ("action", action as Any)]) + case .updateConfig: + return ("updateConfig", []) + case .updateContactsReset: + return ("updateContactsReset", []) + case .updateDcOptions(let dcOptions): + return ("updateDcOptions", [("dcOptions", dcOptions as Any)]) + case .updateDeleteChannelMessages(let channelId, let messages, let pts, let ptsCount): + return ("updateDeleteChannelMessages", [("channelId", channelId as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateDeleteMessages(let messages, let pts, let ptsCount): + return ("updateDeleteMessages", [("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateDeleteQuickReply(let shortcutId): + return ("updateDeleteQuickReply", [("shortcutId", shortcutId as Any)]) + case .updateDeleteQuickReplyMessages(let shortcutId, let messages): + return ("updateDeleteQuickReplyMessages", [("shortcutId", shortcutId as Any), ("messages", messages as Any)]) + case .updateDeleteScheduledMessages(let peer, let messages): + return ("updateDeleteScheduledMessages", [("peer", peer as Any), ("messages", messages as Any)]) + case .updateDialogFilter(let flags, let id, let filter): + return ("updateDialogFilter", [("flags", flags as Any), ("id", id as Any), ("filter", filter as Any)]) + case .updateDialogFilterOrder(let order): + return ("updateDialogFilterOrder", [("order", order as Any)]) + case .updateDialogFilters: + return ("updateDialogFilters", []) + case .updateDialogPinned(let flags, let folderId, let peer): + return ("updateDialogPinned", [("flags", flags as Any), ("folderId", folderId as Any), ("peer", peer as Any)]) + case .updateDialogUnreadMark(let flags, let peer): + return ("updateDialogUnreadMark", [("flags", flags as Any), ("peer", peer as Any)]) + case .updateDraftMessage(let flags, let peer, let topMsgId, let draft): + return ("updateDraftMessage", [("flags", flags as Any), ("peer", peer as Any), ("topMsgId", topMsgId as Any), ("draft", draft as Any)]) + case .updateEditChannelMessage(let message, let pts, let ptsCount): + return ("updateEditChannelMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateEditMessage(let message, let pts, let ptsCount): + return ("updateEditMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateEncryptedChatTyping(let chatId): + return ("updateEncryptedChatTyping", [("chatId", chatId as Any)]) + case .updateEncryptedMessagesRead(let chatId, let maxDate, let date): + return ("updateEncryptedMessagesRead", [("chatId", chatId as Any), ("maxDate", maxDate as Any), ("date", date as Any)]) + case .updateEncryption(let chat, let date): + return ("updateEncryption", [("chat", chat as Any), ("date", date as Any)]) + case .updateFavedStickers: + return ("updateFavedStickers", []) + case .updateFolderPeers(let folderPeers, let pts, let ptsCount): + return ("updateFolderPeers", [("folderPeers", folderPeers as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateGeoLiveViewed(let peer, let msgId): + return ("updateGeoLiveViewed", [("peer", peer as Any), ("msgId", msgId as Any)]) + case .updateGroupCall(let chatId, let call): + return ("updateGroupCall", [("chatId", chatId as Any), ("call", call as Any)]) + case .updateGroupCallConnection(let flags, let params): + return ("updateGroupCallConnection", [("flags", flags as Any), ("params", params as Any)]) + case .updateGroupCallParticipants(let call, let participants, let version): + return ("updateGroupCallParticipants", [("call", call as Any), ("participants", participants as Any), ("version", version as Any)]) + case .updateInlineBotCallbackQuery(let flags, let queryId, let userId, let msgId, let chatInstance, let data, let gameShortName): + return ("updateInlineBotCallbackQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("msgId", msgId as Any), ("chatInstance", chatInstance as Any), ("data", data as Any), ("gameShortName", gameShortName as Any)]) + case .updateLangPack(let difference): + return ("updateLangPack", [("difference", difference as Any)]) + case .updateLangPackTooLong(let langCode): + return ("updateLangPackTooLong", [("langCode", langCode as Any)]) + case .updateLoginToken: + return ("updateLoginToken", []) + case .updateMessageExtendedMedia(let peer, let msgId, let extendedMedia): + return ("updateMessageExtendedMedia", [("peer", peer as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any)]) + case .updateMessageID(let id, let randomId): + return ("updateMessageID", [("id", id as Any), ("randomId", randomId as Any)]) + case .updateMessagePoll(let flags, let pollId, let poll, let results): + return ("updateMessagePoll", [("flags", flags as Any), ("pollId", pollId as Any), ("poll", poll as Any), ("results", results as Any)]) + case .updateMessagePollVote(let pollId, let peer, let options, let qts): + return ("updateMessagePollVote", [("pollId", pollId as Any), ("peer", peer as Any), ("options", options as Any), ("qts", qts as Any)]) + case .updateMessageReactions(let flags, let peer, let msgId, let topMsgId, let reactions): + return ("updateMessageReactions", [("flags", flags as Any), ("peer", peer as Any), ("msgId", msgId as Any), ("topMsgId", topMsgId as Any), ("reactions", reactions as Any)]) + case .updateMoveStickerSetToTop(let flags, let stickerset): + return ("updateMoveStickerSetToTop", [("flags", flags as Any), ("stickerset", stickerset as Any)]) + case .updateNewAuthorization(let flags, let hash, let date, let device, let location): + return ("updateNewAuthorization", [("flags", flags as Any), ("hash", hash as Any), ("date", date as Any), ("device", device as Any), ("location", location as Any)]) + case .updateNewChannelMessage(let message, let pts, let ptsCount): + return ("updateNewChannelMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateNewEncryptedMessage(let message, let qts): + return ("updateNewEncryptedMessage", [("message", message as Any), ("qts", qts as Any)]) + case .updateNewMessage(let message, let pts, let ptsCount): + return ("updateNewMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateNewQuickReply(let quickReply): + return ("updateNewQuickReply", [("quickReply", quickReply as Any)]) + case .updateNewScheduledMessage(let message): + return ("updateNewScheduledMessage", [("message", message as Any)]) + case .updateNewStickerSet(let stickerset): + return ("updateNewStickerSet", [("stickerset", stickerset as Any)]) + case .updateNewStoryReaction(let storyId, let peer, let reaction): + return ("updateNewStoryReaction", [("storyId", storyId as Any), ("peer", peer as Any), ("reaction", reaction as Any)]) + case .updateNotifySettings(let peer, let notifySettings): + return ("updateNotifySettings", [("peer", peer as Any), ("notifySettings", notifySettings as Any)]) + case .updatePeerBlocked(let flags, let peerId): + return ("updatePeerBlocked", [("flags", flags as Any), ("peerId", peerId as Any)]) + case .updatePeerHistoryTTL(let flags, let peer, let ttlPeriod): + return ("updatePeerHistoryTTL", [("flags", flags as Any), ("peer", peer as Any), ("ttlPeriod", ttlPeriod as Any)]) + case .updatePeerLocated(let peers): + return ("updatePeerLocated", [("peers", peers as Any)]) + case .updatePeerSettings(let peer, let settings): + return ("updatePeerSettings", [("peer", peer as Any), ("settings", settings as Any)]) + case .updatePeerWallpaper(let flags, let peer, let wallpaper): + return ("updatePeerWallpaper", [("flags", flags as Any), ("peer", peer as Any), ("wallpaper", wallpaper as Any)]) + case .updatePendingJoinRequests(let peer, let requestsPending, let recentRequesters): + return ("updatePendingJoinRequests", [("peer", peer as Any), ("requestsPending", requestsPending as Any), ("recentRequesters", recentRequesters as Any)]) + case .updatePhoneCall(let phoneCall): + return ("updatePhoneCall", [("phoneCall", phoneCall as Any)]) + case .updatePhoneCallSignalingData(let phoneCallId, let data): + return ("updatePhoneCallSignalingData", [("phoneCallId", phoneCallId as Any), ("data", data as Any)]) + case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount): + return ("updatePinnedChannelMessages", [("flags", flags as Any), ("channelId", channelId as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updatePinnedDialogs(let flags, let folderId, let order): + return ("updatePinnedDialogs", [("flags", flags as Any), ("folderId", folderId as Any), ("order", order as Any)]) + case .updatePinnedMessages(let flags, let peer, let messages, let pts, let ptsCount): + return ("updatePinnedMessages", [("flags", flags as Any), ("peer", peer as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updatePinnedSavedDialogs(let flags, let order): + return ("updatePinnedSavedDialogs", [("flags", flags as Any), ("order", order as Any)]) + case .updatePrivacy(let key, let rules): + return ("updatePrivacy", [("key", key as Any), ("rules", rules as Any)]) + case .updatePtsChanged: + return ("updatePtsChanged", []) + case .updateQuickReplies(let quickReplies): + return ("updateQuickReplies", [("quickReplies", quickReplies as Any)]) + case .updateQuickReplyMessage(let message): + return ("updateQuickReplyMessage", [("message", message as Any)]) + case .updateReadChannelDiscussionInbox(let flags, let channelId, let topMsgId, let readMaxId, let broadcastId, let broadcastPost): + return ("updateReadChannelDiscussionInbox", [("flags", flags as Any), ("channelId", channelId as Any), ("topMsgId", topMsgId as Any), ("readMaxId", readMaxId as Any), ("broadcastId", broadcastId as Any), ("broadcastPost", broadcastPost as Any)]) + case .updateReadChannelDiscussionOutbox(let channelId, let topMsgId, let readMaxId): + return ("updateReadChannelDiscussionOutbox", [("channelId", channelId as Any), ("topMsgId", topMsgId as Any), ("readMaxId", readMaxId as Any)]) + case .updateReadChannelInbox(let flags, let folderId, let channelId, let maxId, let stillUnreadCount, let pts): + return ("updateReadChannelInbox", [("flags", flags as Any), ("folderId", folderId as Any), ("channelId", channelId as Any), ("maxId", maxId as Any), ("stillUnreadCount", stillUnreadCount as Any), ("pts", pts as Any)]) + case .updateReadChannelOutbox(let channelId, let maxId): + return ("updateReadChannelOutbox", [("channelId", channelId as Any), ("maxId", maxId as Any)]) + case .updateReadFeaturedEmojiStickers: + return ("updateReadFeaturedEmojiStickers", []) + case .updateReadFeaturedStickers: + return ("updateReadFeaturedStickers", []) + case .updateReadHistoryInbox(let flags, let folderId, let peer, let maxId, let stillUnreadCount, let pts, let ptsCount): + return ("updateReadHistoryInbox", [("flags", flags as Any), ("folderId", folderId as Any), ("peer", peer as Any), ("maxId", maxId as Any), ("stillUnreadCount", stillUnreadCount as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateReadHistoryOutbox(let peer, let maxId, let pts, let ptsCount): + return ("updateReadHistoryOutbox", [("peer", peer as Any), ("maxId", maxId as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateReadMessagesContents(let flags, let messages, let pts, let ptsCount, let date): + return ("updateReadMessagesContents", [("flags", flags as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any), ("date", date as Any)]) + case .updateReadStories(let peer, let maxId): + return ("updateReadStories", [("peer", peer as Any), ("maxId", maxId as Any)]) + case .updateRecentEmojiStatuses: + return ("updateRecentEmojiStatuses", []) + case .updateRecentReactions: + return ("updateRecentReactions", []) + case .updateRecentStickers: + return ("updateRecentStickers", []) + case .updateSavedDialogPinned(let flags, let peer): + return ("updateSavedDialogPinned", [("flags", flags as Any), ("peer", peer as Any)]) + case .updateSavedGifs: + return ("updateSavedGifs", []) + case .updateSavedReactionTags: + return ("updateSavedReactionTags", []) + case .updateSavedRingtones: + return ("updateSavedRingtones", []) + case .updateSentStoryReaction(let peer, let storyId, let reaction): + return ("updateSentStoryReaction", [("peer", peer as Any), ("storyId", storyId as Any), ("reaction", reaction as Any)]) + case .updateServiceNotification(let flags, let inboxDate, let type, let message, let media, let entities): + return ("updateServiceNotification", [("flags", flags as Any), ("inboxDate", inboxDate as Any), ("type", type as Any), ("message", message as Any), ("media", media as Any), ("entities", entities as Any)]) + case .updateSmsJob(let jobId): + return ("updateSmsJob", [("jobId", jobId as Any)]) + case .updateStarsBalance(let balance): + return ("updateStarsBalance", [("balance", balance as Any)]) + case .updateStickerSets(let flags): + return ("updateStickerSets", [("flags", flags as Any)]) + case .updateStickerSetsOrder(let flags, let order): + return ("updateStickerSetsOrder", [("flags", flags as Any), ("order", order as Any)]) + case .updateStoriesStealthMode(let stealthMode): + return ("updateStoriesStealthMode", [("stealthMode", stealthMode as Any)]) + case .updateStory(let peer, let story): + return ("updateStory", [("peer", peer as Any), ("story", story as Any)]) + case .updateStoryID(let id, let randomId): + return ("updateStoryID", [("id", id as Any), ("randomId", randomId as Any)]) + case .updateTheme(let theme): + return ("updateTheme", [("theme", theme as Any)]) + case .updateTranscribedAudio(let flags, let peer, let msgId, let transcriptionId, let text): + return ("updateTranscribedAudio", [("flags", flags as Any), ("peer", peer as Any), ("msgId", msgId as Any), ("transcriptionId", transcriptionId as Any), ("text", text as Any)]) + case .updateUser(let userId): + return ("updateUser", [("userId", userId as Any)]) + case .updateUserEmojiStatus(let userId, let emojiStatus): + return ("updateUserEmojiStatus", [("userId", userId as Any), ("emojiStatus", emojiStatus as Any)]) + case .updateUserName(let userId, let firstName, let lastName, let usernames): + return ("updateUserName", [("userId", userId as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("usernames", usernames as Any)]) + case .updateUserPhone(let userId, let phone): + return ("updateUserPhone", [("userId", userId as Any), ("phone", phone as Any)]) + case .updateUserStatus(let userId, let status): + return ("updateUserStatus", [("userId", userId as Any), ("status", status as Any)]) + case .updateUserTyping(let userId, let action): + return ("updateUserTyping", [("userId", userId as Any), ("action", action as Any)]) + case .updateWebPage(let webpage, let pts, let ptsCount): + return ("updateWebPage", [("webpage", webpage as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateWebViewResultSent(let queryId): + return ("updateWebViewResultSent", [("queryId", queryId as Any)]) + } + } + + public static func parse_updateAttachMenuBots(_ reader: BufferReader) -> Update? { + return Api.Update.updateAttachMenuBots + } + public static func parse_updateAutoSaveSettings(_ reader: BufferReader) -> Update? { + return Api.Update.updateAutoSaveSettings + } + public static func parse_updateBotBusinessConnect(_ reader: BufferReader) -> Update? { + var _1: Api.BotBusinessConnection? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.BotBusinessConnection + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateBotBusinessConnect(connection: _1!, qts: _2!) + } + else { + return nil + } + } + public static func parse_updateBotCallbackQuery(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int64? + _6 = reader.readInt64() + var _7: Buffer? + if Int(_1!) & Int(1 << 0) != 0 {_7 = parseBytes(reader) } + var _8: String? + if Int(_1!) & Int(1 << 1) != 0 {_8 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Update.updateBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, peer: _4!, msgId: _5!, chatInstance: _6!, data: _7, gameShortName: _8) + } + else { + return nil + } + } + public static func parse_updateBotChatBoost(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.Boost? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Boost + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateBotChatBoost(peer: _1!, boost: _2!, qts: _3!) + } + else { + return nil + } + } + public static func parse_updateBotChatInviteRequester(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: Api.ExportedChatInvite? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Update.updateBotChatInviteRequester(peer: _1!, date: _2!, userId: _3!, about: _4!, invite: _5!, qts: _6!) + } + else { + return nil + } + } + public static func parse_updateBotCommands(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int64? + _2 = reader.readInt64() + var _3: [Api.BotCommand]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateBotCommands(peer: _1!, botId: _2!, commands: _3!) + } + else { + return nil + } + } + public static func parse_updateBotDeleteBusinessMessage(_ reader: BufferReader) -> Update? { + var _1: String? + _1 = parseString(reader) + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: [Int32]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateBotDeleteBusinessMessage(connectionId: _1!, peer: _2!, messages: _3!, qts: _4!) + } + else { + return nil + } + } + public static func parse_updateBotEditBusinessMessage(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.Message? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Message + } + var _4: Api.Message? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Message + } } + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateBotEditBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!) + } + else { + return nil + } + } + public static func parse_updateBotInlineQuery(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: Api.GeoPoint? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } } + var _6: Api.InlineQueryPeerType? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.InlineQueryPeerType + } } + var _7: String? + _7 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.Update.updateBotInlineQuery(flags: _1!, queryId: _2!, userId: _3!, query: _4!, geo: _5, peerType: _6, offset: _7!) + } + else { + return nil + } + } + public static func parse_updateBotInlineSend(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: Api.GeoPoint? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } } + var _5: String? + _5 = parseString(reader) + var _6: Api.InputBotInlineMessageID? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Update.updateBotInlineSend(flags: _1!, userId: _2!, query: _3!, geo: _4, id: _5!, msgId: _6) + } + else { + return nil + } + } + public static func parse_updateBotMenuButton(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.BotMenuButton? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.BotMenuButton + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateBotMenuButton(botId: _1!, button: _2!) + } + else { + return nil + } + } + public static func parse_updateBotMessageReaction(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: [Api.Reaction]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) + } + var _6: [Api.Reaction]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) + } + var _7: Int32? + _7 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.Update.updateBotMessageReaction(peer: _1!, msgId: _2!, date: _3!, actor: _4!, oldReactions: _5!, newReactions: _6!, qts: _7!) + } + else { + return nil + } + } + public static func parse_updateBotMessageReactions(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Api.ReactionCount]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) + } + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateBotMessageReactions(peer: _1!, msgId: _2!, date: _3!, reactions: _4!, qts: _5!) + } + else { + return nil + } + } + public static func parse_updateBotNewBusinessMessage(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.Message? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Message + } + var _4: Api.Message? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Message + } } + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateBotNewBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!) + } + else { + return nil + } + } + public static func parse_updateBotPrecheckoutQuery(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Api.PaymentRequestedInfo? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } } + var _6: String? + if Int(_1!) & Int(1 << 1) != 0 {_6 = parseString(reader) } + var _7: String? + _7 = parseString(reader) + var _8: Int64? + _8 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Update.updateBotPrecheckoutQuery(flags: _1!, queryId: _2!, userId: _3!, payload: _4!, info: _5, shippingOptionId: _6, currency: _7!, totalAmount: _8!) + } + else { + return nil + } + } + public static func parse_updateBotShippingQuery(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Buffer? + _3 = parseBytes(reader) + var _4: Api.PostAddress? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.PostAddress + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateBotShippingQuery(queryId: _1!, userId: _2!, payload: _3!, shippingAddress: _4!) + } + else { + return nil + } + } + public static func parse_updateBotStopped(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Bool? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateBotStopped(userId: _1!, date: _2!, stopped: _3!, qts: _4!) + } + else { + return nil + } + } + public static func parse_updateBotWebhookJSON(_ reader: BufferReader) -> Update? { + var _1: Api.DataJSON? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateBotWebhookJSON(data: _1!) + } + else { + return nil + } + } + public static func parse_updateBotWebhookJSONQuery(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.DataJSON? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateBotWebhookJSONQuery(queryId: _1!, data: _2!, timeout: _3!) + } + else { + return nil + } + } + public static func parse_updateBroadcastRevenueTransactions(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.BroadcastRevenueBalances? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.BroadcastRevenueBalances + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateBroadcastRevenueTransactions(peer: _1!, balances: _2!) + } + else { + return nil + } + } + public static func parse_updateChannel(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateChannel(channelId: _1!) + } + else { + return nil + } + } + public static func parse_updateChannelAvailableMessages(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateChannelAvailableMessages(channelId: _1!, availableMinId: _2!) + } + else { + return nil + } + } + public static func parse_updateChannelMessageForwards(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChannelMessageForwards(channelId: _1!, id: _2!, forwards: _3!) + } + else { + return nil + } + } + public static func parse_updateChannelMessageViews(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChannelMessageViews(channelId: _1!, id: _2!, views: _3!) + } + else { + return nil + } + } + public static func parse_updateChannelParticipant(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + var _5: Int64? + _5 = reader.readInt64() + var _6: Api.ChannelParticipant? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant + } } + var _7: Api.ChannelParticipant? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant + } } + var _8: Api.ExportedChatInvite? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } } + var _9: Int32? + _9 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.Update.updateChannelParticipant(flags: _1!, channelId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!) + } + else { + return nil + } + } + public static func parse_updateChannelPinnedTopic(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChannelPinnedTopic(flags: _1!, channelId: _2!, topicId: _3!) + } + else { + return nil + } + } + public static func parse_updateChannelPinnedTopics(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: [Int32]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChannelPinnedTopics(flags: _1!, channelId: _2!, order: _3) + } + else { + return nil + } + } + public static func parse_updateChannelReadMessagesContents(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: [Int32]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateChannelReadMessagesContents(flags: _1!, channelId: _2!, topMsgId: _3, messages: _4!) + } + else { + return nil + } + } + public static func parse_updateChannelTooLong(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChannelTooLong(flags: _1!, channelId: _2!, pts: _3) + } + else { + return nil + } + } + public static func parse_updateChannelUserTyping(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: Api.SendMessageAction? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.SendMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateChannelUserTyping(flags: _1!, channelId: _2!, topMsgId: _3, fromId: _4!, action: _5!) + } + else { + return nil + } + } + public static func parse_updateChannelViewForumAsMessages(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Bool? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateChannelViewForumAsMessages(channelId: _1!, enabled: _2!) + } + else { + return nil + } + } + public static func parse_updateChannelWebPage(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.WebPage? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.WebPage + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateChannelWebPage(channelId: _1!, webpage: _2!, pts: _3!, ptsCount: _4!) + } + else { + return nil + } + } + public static func parse_updateChat(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateChat(chatId: _1!) + } + else { + return nil + } + } + public static func parse_updateChatDefaultBannedRights(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.ChatBannedRights? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChatDefaultBannedRights(peer: _1!, defaultBannedRights: _2!, version: _3!) + } + else { + return nil + } + } + public static func parse_updateChatParticipant(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + var _5: Int64? + _5 = reader.readInt64() + var _6: Api.ChatParticipant? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.ChatParticipant + } } + var _7: Api.ChatParticipant? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.ChatParticipant + } } + var _8: Api.ExportedChatInvite? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } } + var _9: Int32? + _9 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.Update.updateChatParticipant(flags: _1!, chatId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!) + } + else { + return nil + } + } + public static func parse_updateChatParticipantAdd(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateChatParticipantAdd(chatId: _1!, userId: _2!, inviterId: _3!, date: _4!, version: _5!) + } + else { + return nil + } + } + public static func parse_updateChatParticipantAdmin(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.Bool? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateChatParticipantAdmin(chatId: _1!, userId: _2!, isAdmin: _3!, version: _4!) + } + else { + return nil + } + } + public static func parse_updateChatParticipantDelete(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChatParticipantDelete(chatId: _1!, userId: _2!, version: _3!) + } + else { + return nil + } + } + public static func parse_updateChatParticipants(_ reader: BufferReader) -> Update? { + var _1: Api.ChatParticipants? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ChatParticipants + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateChatParticipants(participants: _1!) + } + else { + return nil + } + } + public static func parse_updateChatUserTyping(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Api.SendMessageAction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.SendMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChatUserTyping(chatId: _1!, fromId: _2!, action: _3!) + } + else { + return nil + } + } + public static func parse_updateConfig(_ reader: BufferReader) -> Update? { + return Api.Update.updateConfig + } + public static func parse_updateContactsReset(_ reader: BufferReader) -> Update? { + return Api.Update.updateContactsReset + } + public static func parse_updateDcOptions(_ reader: BufferReader) -> Update? { + var _1: [Api.DcOption]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DcOption.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateDcOptions(dcOptions: _1!) + } + else { + return nil + } + } + public static func parse_updateDeleteChannelMessages(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateDeleteChannelMessages(channelId: _1!, messages: _2!, pts: _3!, ptsCount: _4!) + } + else { + return nil + } + } + public static func parse_updateDeleteMessages(_ reader: BufferReader) -> Update? { + var _1: [Int32]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateDeleteMessages(messages: _1!, pts: _2!, ptsCount: _3!) + } + else { + return nil + } + } + public static func parse_updateDeleteQuickReply(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateDeleteQuickReply(shortcutId: _1!) + } + else { + return nil + } + } + public static func parse_updateDeleteQuickReplyMessages(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateDeleteQuickReplyMessages(shortcutId: _1!, messages: _2!) + } + else { + return nil + } + } + public static func parse_updateDeleteScheduledMessages(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateDeleteScheduledMessages(peer: _1!, messages: _2!) + } + else { + return nil + } + } + public static func parse_updateDialogFilter(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.DialogFilter? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.DialogFilter + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateDialogFilter(flags: _1!, id: _2!, filter: _3) + } + else { + return nil + } + } + public static func parse_updateDialogFilterOrder(_ reader: BufferReader) -> Update? { + var _1: [Int32]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateDialogFilterOrder(order: _1!) + } + else { + return nil + } + } + public static func parse_updateDialogFilters(_ reader: BufferReader) -> Update? { + return Api.Update.updateDialogFilters + } + public static func parse_updateDialogPinned(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } + var _3: Api.DialogPeer? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.DialogPeer + } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateDialogPinned(flags: _1!, folderId: _2, peer: _3!) + } + else { + return nil + } + } + public static func parse_updateDialogUnreadMark(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DialogPeer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DialogPeer + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateDialogUnreadMark(flags: _1!, peer: _2!) + } + else { + return nil + } + } + public static func parse_updateDraftMessage(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: Api.DraftMessage? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.DraftMessage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateDraftMessage(flags: _1!, peer: _2!, topMsgId: _3, draft: _4!) + } + else { + return nil + } + } + public static func parse_updateEditChannelMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateEditChannelMessage(message: _1!, pts: _2!, ptsCount: _3!) + } + else { + return nil + } + } + public static func parse_updateEditMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateEditMessage(message: _1!, pts: _2!, ptsCount: _3!) + } + else { + return nil + } + } + public static func parse_updateEncryptedChatTyping(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateEncryptedChatTyping(chatId: _1!) + } + else { + return nil + } + } + public static func parse_updateEncryptedMessagesRead(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateEncryptedMessagesRead(chatId: _1!, maxDate: _2!, date: _3!) + } + else { + return nil + } + } + public static func parse_updateEncryption(_ reader: BufferReader) -> Update? { + var _1: Api.EncryptedChat? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.EncryptedChat + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateEncryption(chat: _1!, date: _2!) + } + else { + return nil + } + } + public static func parse_updateFavedStickers(_ reader: BufferReader) -> Update? { + return Api.Update.updateFavedStickers + } + public static func parse_updateFolderPeers(_ reader: BufferReader) -> Update? { + var _1: [Api.FolderPeer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FolderPeer.self) + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateFolderPeers(folderPeers: _1!, pts: _2!, ptsCount: _3!) + } + else { + return nil + } + } + public static func parse_updateGeoLiveViewed(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateGeoLiveViewed(peer: _1!, msgId: _2!) + } + else { + return nil + } + } + public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.GroupCall? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.GroupCall + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateGroupCall(chatId: _1!, call: _2!) + } + else { + return nil + } + } + public static func parse_updateGroupCallConnection(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DataJSON? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateGroupCallConnection(flags: _1!, params: _2!) + } + else { + return nil + } + } + public static func parse_updateGroupCallParticipants(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: [Api.GroupCallParticipant]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateGroupCallParticipants(call: _1!, participants: _2!, version: _3!) + } + else { + return nil + } + } + public static func parse_updateInlineBotCallbackQuery(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Api.InputBotInlineMessageID? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID + } + var _5: Int64? + _5 = reader.readInt64() + var _6: Buffer? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseBytes(reader) } + var _7: String? + if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.Update.updateInlineBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, msgId: _4!, chatInstance: _5!, data: _6, gameShortName: _7) + } + else { + return nil + } + } + public static func parse_updateLangPack(_ reader: BufferReader) -> Update? { + var _1: Api.LangPackDifference? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.LangPackDifference + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateLangPack(difference: _1!) + } + else { + return nil + } + } + public static func parse_updateLangPackTooLong(_ reader: BufferReader) -> Update? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateLangPackTooLong(langCode: _1!) + } + else { + return nil + } + } + public static func parse_updateLoginToken(_ reader: BufferReader) -> Update? { + return Api.Update.updateLoginToken + } + public static func parse_updateMessageExtendedMedia(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.MessageExtendedMedia? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.MessageExtendedMedia + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateMessageExtendedMedia(peer: _1!, msgId: _2!, extendedMedia: _3!) + } + else { + return nil + } + } + public static func parse_updateMessageID(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateMessageID(id: _1!, randomId: _2!) + } + else { + return nil + } + } + public static func parse_updateMessagePoll(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.Poll? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Poll + } } + var _4: Api.PollResults? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.PollResults + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateMessagePoll(flags: _1!, pollId: _2!, poll: _3, results: _4!) + } + else { + return nil + } + } + public static func parse_updateMessagePollVote(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: [Buffer]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateMessagePollVote(pollId: _1!, peer: _2!, options: _3!, qts: _4!) + } + else { + return nil + } + } + public static func parse_updateMessageReactions(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } + var _5: Api.MessageReactions? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.MessageReactions + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateMessageReactions(flags: _1!, peer: _2!, msgId: _3!, topMsgId: _4, reactions: _5!) + } + else { + return nil + } + } + public static func parse_updateMoveStickerSetToTop(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateMoveStickerSetToTop(flags: _1!, stickerset: _2!) + } + else { + return nil + } + } + public static func parse_updateNewAuthorization(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + var _5: String? + if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateNewAuthorization(flags: _1!, hash: _2!, date: _3, device: _4, location: _5) + } + else { + return nil + } + } + public static func parse_updateNewChannelMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateNewChannelMessage(message: _1!, pts: _2!, ptsCount: _3!) + } + else { + return nil + } + } + public static func parse_updateNewEncryptedMessage(_ reader: BufferReader) -> Update? { + var _1: Api.EncryptedMessage? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.EncryptedMessage + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateNewEncryptedMessage(message: _1!, qts: _2!) + } + else { + return nil + } + } + public static func parse_updateNewMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateNewMessage(message: _1!, pts: _2!, ptsCount: _3!) + } + else { + return nil + } + } + public static func parse_updateNewQuickReply(_ reader: BufferReader) -> Update? { + var _1: Api.QuickReply? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.QuickReply + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateNewQuickReply(quickReply: _1!) + } + else { + return nil + } + } + public static func parse_updateNewScheduledMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateNewScheduledMessage(message: _1!) + } + else { + return nil + } + } + public static func parse_updateNewStickerSet(_ reader: BufferReader) -> Update? { + var _1: Api.messages.StickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateNewStickerSet(stickerset: _1!) + } + else { + return nil + } + } + public static func parse_updateNewStoryReaction(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Api.Reaction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Reaction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateNewStoryReaction(storyId: _1!, peer: _2!, reaction: _3!) + } + else { + return nil + } + } + public static func parse_updateNotifySettings(_ reader: BufferReader) -> Update? { + var _1: Api.NotifyPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.NotifyPeer + } + var _2: Api.PeerNotifySettings? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateNotifySettings(peer: _1!, notifySettings: _2!) + } + else { + return nil + } + } + public static func parse_updatePeerBlocked(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updatePeerBlocked(flags: _1!, peerId: _2!) + } + else { + return nil + } + } + public static func parse_updatePeerHistoryTTL(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePeerHistoryTTL(flags: _1!, peer: _2!, ttlPeriod: _3) + } + else { + return nil + } + } + public static func parse_updatePeerLocated(_ reader: BufferReader) -> Update? { + var _1: [Api.PeerLocated]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerLocated.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updatePeerLocated(peers: _1!) + } + else { + return nil + } + } + public static func parse_updatePeerSettings(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.PeerSettings? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PeerSettings + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updatePeerSettings(peer: _1!, settings: _2!) + } + else { + return nil + } + } + public static func parse_updatePeerWallpaper(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Api.WallPaper? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.WallPaper + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePeerWallpaper(flags: _1!, peer: _2!, wallpaper: _3) + } + else { + return nil + } + } + public static func parse_updatePendingJoinRequests(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: [Int64]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePendingJoinRequests(peer: _1!, requestsPending: _2!, recentRequesters: _3!) + } + else { + return nil + } + } + public static func parse_updatePhoneCall(_ reader: BufferReader) -> Update? { + var _1: Api.PhoneCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updatePhoneCall(phoneCall: _1!) + } + else { + return nil + } + } + public static func parse_updatePhoneCallSignalingData(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updatePhoneCallSignalingData(phoneCallId: _1!, data: _2!) + } + else { + return nil + } + } + public static func parse_updatePinnedChannelMessages(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: [Int32]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updatePinnedChannelMessages(flags: _1!, channelId: _2!, messages: _3!, pts: _4!, ptsCount: _5!) + } + else { + return nil + } + } + public static func parse_updatePinnedDialogs(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } + var _3: [Api.DialogPeer]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) } } - var _8: Int32? - if Int(_1!) & Int(1 << 25) != 0 {_8 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 9) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 25) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Updates.updateShortSentMessage(flags: _1!, id: _2!, pts: _3!, ptsCount: _4!, date: _5!, media: _6, entities: _7, ttlPeriod: _8) + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePinnedDialogs(flags: _1!, folderId: _2, order: _3) } else { return nil } } - public static func parse_updates(_ reader: BufferReader) -> Updates? { - var _1: [Api.Update]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + public static func parse_updatePinnedMessages(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer } - var _3: [Api.Chat]? + var _3: [Int32]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } var _4: Int32? _4 = reader.readInt32() @@ -328,1328 +3651,595 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Updates.updates(updates: _1!, users: _2!, chats: _3!, date: _4!, seq: _5!) + return Api.Update.updatePinnedMessages(flags: _1!, peer: _2!, messages: _3!, pts: _4!, ptsCount: _5!) } else { return nil } } - public static func parse_updatesCombined(_ reader: BufferReader) -> Updates? { - var _1: [Api.Update]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + public static func parse_updatePinnedSavedDialogs(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.DialogPeer]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.Update.updatePinnedSavedDialogs(flags: _1!, order: _2) } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + else { + return nil + } + } + public static func parse_updatePrivacy(_ reader: BufferReader) -> Update? { + var _1: Api.PrivacyKey? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PrivacyKey } - var _3: [Api.Chat]? + var _2: [Api.PrivacyRule]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Updates.updatesCombined(updates: _1!, users: _2!, chats: _3!, date: _4!, seqStart: _5!, seq: _6!) + if _c1 && _c2 { + return Api.Update.updatePrivacy(key: _1!, rules: _2!) } else { return nil } } - public static func parse_updatesTooLong(_ reader: BufferReader) -> Updates? { - return Api.Updates.updatesTooLong + public static func parse_updatePtsChanged(_ reader: BufferReader) -> Update? { + return Api.Update.updatePtsChanged } - - } -} -public extension Api { - enum UrlAuthResult: TypeConstructorDescription { - case urlAuthResultAccepted(url: String) - case urlAuthResultDefault - case urlAuthResultRequest(flags: Int32, bot: Api.User, domain: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .urlAuthResultAccepted(let url): - if boxed { - buffer.appendInt32(-1886646706) - } - serializeString(url, buffer: buffer, boxed: false) - break - case .urlAuthResultDefault: - if boxed { - buffer.appendInt32(-1445536993) - } - - break - case .urlAuthResultRequest(let flags, let bot, let domain): - if boxed { - buffer.appendInt32(-1831650802) - } - serializeInt32(flags, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - serializeString(domain, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .urlAuthResultAccepted(let url): - return ("urlAuthResultAccepted", [("url", url as Any)]) - case .urlAuthResultDefault: - return ("urlAuthResultDefault", []) - case .urlAuthResultRequest(let flags, let bot, let domain): - return ("urlAuthResultRequest", [("flags", flags as Any), ("bot", bot as Any), ("domain", domain as Any)]) - } - } - - public static func parse_urlAuthResultAccepted(_ reader: BufferReader) -> UrlAuthResult? { - var _1: String? - _1 = parseString(reader) + public static func parse_updateQuickReplies(_ reader: BufferReader) -> Update? { + var _1: [Api.QuickReply]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) + } let _c1 = _1 != nil if _c1 { - return Api.UrlAuthResult.urlAuthResultAccepted(url: _1!) + return Api.Update.updateQuickReplies(quickReplies: _1!) } else { return nil } } - public static func parse_urlAuthResultDefault(_ reader: BufferReader) -> UrlAuthResult? { - return Api.UrlAuthResult.urlAuthResultDefault + public static func parse_updateQuickReplyMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateQuickReplyMessage(message: _1!) + } + else { + return nil + } } - public static func parse_urlAuthResultRequest(_ reader: BufferReader) -> UrlAuthResult? { + public static func parse_updateReadChannelDiscussionInbox(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.User? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.User + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } + var _6: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Update.updateReadChannelDiscussionInbox(flags: _1!, channelId: _2!, topMsgId: _3!, readMaxId: _4!, broadcastId: _5, broadcastPost: _6) } - var _3: String? - _3 = parseString(reader) + else { + return nil + } + } + public static func parse_updateReadChannelDiscussionOutbox(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.UrlAuthResult.urlAuthResultRequest(flags: _1!, bot: _2!, domain: _3!) + return Api.Update.updateReadChannelDiscussionOutbox(channelId: _1!, topMsgId: _2!, readMaxId: _3!) } else { return nil } } - - } -} -public extension Api { - enum User: TypeConstructorDescription { - case user(flags: Int32, flags2: Int32, id: Int64, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: Api.UserProfilePhoto?, status: Api.UserStatus?, botInfoVersion: Int32?, restrictionReason: [Api.RestrictionReason]?, botInlinePlaceholder: String?, langCode: String?, emojiStatus: Api.EmojiStatus?, usernames: [Api.Username]?, storiesMaxId: Int32?, color: Api.PeerColor?, profileColor: Api.PeerColor?) - case userEmpty(id: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .user(let flags, let flags2, let id, let accessHash, let firstName, let lastName, let username, let phone, let photo, let status, let botInfoVersion, let restrictionReason, let botInlinePlaceholder, let langCode, let emojiStatus, let usernames, let storiesMaxId, let color, let profileColor): - if boxed { - buffer.appendInt32(559694904) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(flags2, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(accessHash!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(firstName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(lastName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeString(username!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeString(phone!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 5) != 0 {photo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 6) != 0 {status!.serialize(buffer, true)} - if Int(flags) & Int(1 << 14) != 0 {serializeInt32(botInfoVersion!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 18) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(restrictionReason!.count)) - for item in restrictionReason! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 19) != 0 {serializeString(botInlinePlaceholder!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 22) != 0 {serializeString(langCode!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 30) != 0 {emojiStatus!.serialize(buffer, true)} - if Int(flags2) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(usernames!.count)) - for item in usernames! { - item.serialize(buffer, true) - }} - if Int(flags2) & Int(1 << 5) != 0 {serializeInt32(storiesMaxId!, buffer: buffer, boxed: false)} - if Int(flags2) & Int(1 << 8) != 0 {color!.serialize(buffer, true)} - if Int(flags2) & Int(1 << 9) != 0 {profileColor!.serialize(buffer, true)} - break - case .userEmpty(let id): - if boxed { - buffer.appendInt32(-742634630) - } - serializeInt64(id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .user(let flags, let flags2, let id, let accessHash, let firstName, let lastName, let username, let phone, let photo, let status, let botInfoVersion, let restrictionReason, let botInlinePlaceholder, let langCode, let emojiStatus, let usernames, let storiesMaxId, let color, let profileColor): - return ("user", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("username", username as Any), ("phone", phone as Any), ("photo", photo as Any), ("status", status as Any), ("botInfoVersion", botInfoVersion as Any), ("restrictionReason", restrictionReason as Any), ("botInlinePlaceholder", botInlinePlaceholder as Any), ("langCode", langCode as Any), ("emojiStatus", emojiStatus as Any), ("usernames", usernames as Any), ("storiesMaxId", storiesMaxId as Any), ("color", color as Any), ("profileColor", profileColor as Any)]) - case .userEmpty(let id): - return ("userEmpty", [("id", id as Any)]) - } - } - - public static func parse_user(_ reader: BufferReader) -> User? { + public static func parse_updateReadChannelInbox(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - _2 = reader.readInt32() + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } var _3: Int64? _3 = reader.readInt64() - var _4: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt64() } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } - var _6: String? - if Int(_1!) & Int(1 << 2) != 0 {_6 = parseString(reader) } - var _7: String? - if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) } - var _8: String? - if Int(_1!) & Int(1 << 4) != 0 {_8 = parseString(reader) } - var _9: Api.UserProfilePhoto? - if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.UserProfilePhoto - } } - var _10: Api.UserStatus? - if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.UserStatus - } } - var _11: Int32? - if Int(_1!) & Int(1 << 14) != 0 {_11 = reader.readInt32() } - var _12: [Api.RestrictionReason]? - if Int(_1!) & Int(1 << 18) != 0 {if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) - } } - var _13: String? - if Int(_1!) & Int(1 << 19) != 0 {_13 = parseString(reader) } - var _14: String? - if Int(_1!) & Int(1 << 22) != 0 {_14 = parseString(reader) } - var _15: Api.EmojiStatus? - if Int(_1!) & Int(1 << 30) != 0 {if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.EmojiStatus - } } - var _16: [Api.Username]? - if Int(_2!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self) - } } - var _17: Int32? - if Int(_2!) & Int(1 << 5) != 0 {_17 = reader.readInt32() } - var _18: Api.PeerColor? - if Int(_2!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { - _18 = Api.parse(reader, signature: signature) as? Api.PeerColor - } } - var _19: Api.PeerColor? - if Int(_2!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _19 = Api.parse(reader, signature: signature) as? Api.PeerColor - } } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 6) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 14) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 18) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 19) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 22) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 30) == 0) || _15 != nil - let _c16 = (Int(_2!) & Int(1 << 0) == 0) || _16 != nil - let _c17 = (Int(_2!) & Int(1 << 5) == 0) || _17 != nil - let _c18 = (Int(_2!) & Int(1 << 8) == 0) || _18 != nil - let _c19 = (Int(_2!) & Int(1 << 9) == 0) || _19 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { - return Api.User.user(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, firstName: _5, lastName: _6, username: _7, phone: _8, photo: _9, status: _10, botInfoVersion: _11, restrictionReason: _12, botInlinePlaceholder: _13, langCode: _14, emojiStatus: _15, usernames: _16, storiesMaxId: _17, color: _18, profileColor: _19) + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Update.updateReadChannelInbox(flags: _1!, folderId: _2, channelId: _3!, maxId: _4!, stillUnreadCount: _5!, pts: _6!) } else { return nil } } - public static func parse_userEmpty(_ reader: BufferReader) -> User? { + public static func parse_updateReadChannelOutbox(_ reader: BufferReader) -> Update? { var _1: Int64? _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.User.userEmpty(id: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateReadChannelOutbox(channelId: _1!, maxId: _2!) } else { return nil } } - - } -} -public extension Api { - enum UserFull: TypeConstructorDescription { - case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage): - if boxed { - buffer.appendInt32(-862357728) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(flags2, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(about!, buffer: buffer, boxed: false)} - settings.serialize(buffer, true) - if Int(flags) & Int(1 << 21) != 0 {personalPhoto!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {profilePhoto!.serialize(buffer, true)} - if Int(flags) & Int(1 << 22) != 0 {fallbackPhoto!.serialize(buffer, true)} - notifySettings.serialize(buffer, true) - if Int(flags) & Int(1 << 3) != 0 {botInfo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)} - serializeInt32(commonChatsCount, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 14) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 15) != 0 {serializeString(themeEmoticon!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 16) != 0 {serializeString(privateForwardName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 17) != 0 {botGroupAdminRights!.serialize(buffer, true)} - if Int(flags) & Int(1 << 18) != 0 {botBroadcastAdminRights!.serialize(buffer, true)} - if Int(flags) & Int(1 << 19) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(premiumGifts!.count)) - for item in premiumGifts! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 24) != 0 {wallpaper!.serialize(buffer, true)} - if Int(flags) & Int(1 << 25) != 0 {stories!.serialize(buffer, true)} - if Int(flags2) & Int(1 << 0) != 0 {businessWorkHours!.serialize(buffer, true)} - if Int(flags2) & Int(1 << 1) != 0 {businessLocation!.serialize(buffer, true)} - if Int(flags2) & Int(1 << 2) != 0 {businessGreetingMessage!.serialize(buffer, true)} - if Int(flags2) & Int(1 << 3) != 0 {businessAwayMessage!.serialize(buffer, true)} - if Int(flags2) & Int(1 << 4) != 0 {businessIntro!.serialize(buffer, true)} - if Int(flags2) & Int(1 << 5) != 0 {birthday!.serialize(buffer, true)} - if Int(flags2) & Int(1 << 6) != 0 {serializeInt64(personalChannelId!, buffer: buffer, boxed: false)} - if Int(flags2) & Int(1 << 6) != 0 {serializeInt32(personalChannelMessage!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage): - return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("premiumGifts", premiumGifts as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any)]) - } - } - - public static func parse_userFull(_ reader: BufferReader) -> UserFull? { + public static func parse_updateReadFeaturedEmojiStickers(_ reader: BufferReader) -> Update? { + return Api.Update.updateReadFeaturedEmojiStickers + } + public static func parse_updateReadFeaturedStickers(_ reader: BufferReader) -> Update? { + return Api.Update.updateReadFeaturedStickers + } + public static func parse_updateReadHistoryInbox(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - var _5: Api.PeerSettings? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.PeerSettings - } - var _6: Api.Photo? - if Int(_1!) & Int(1 << 21) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Photo - } } - var _7: Api.Photo? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Photo - } } - var _8: Api.Photo? - if Int(_1!) & Int(1 << 22) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Photo - } } - var _9: Api.PeerNotifySettings? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _3: Api.Peer? if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings + _3 = Api.parse(reader, signature: signature) as? Api.Peer } - var _10: Api.BotInfo? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.BotInfo - } } - var _11: Int32? - if Int(_1!) & Int(1 << 6) != 0 {_11 = reader.readInt32() } - var _12: Int32? - _12 = reader.readInt32() - var _13: Int32? - if Int(_1!) & Int(1 << 11) != 0 {_13 = reader.readInt32() } - var _14: Int32? - if Int(_1!) & Int(1 << 14) != 0 {_14 = reader.readInt32() } - var _15: String? - if Int(_1!) & Int(1 << 15) != 0 {_15 = parseString(reader) } - var _16: String? - if Int(_1!) & Int(1 << 16) != 0 {_16 = parseString(reader) } - var _17: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { - _17 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } } - var _18: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 18) != 0 {if let signature = reader.readInt32() { - _18 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } } - var _19: [Api.PremiumGiftOption]? - if Int(_1!) & Int(1 << 19) != 0 {if let _ = reader.readInt32() { - _19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumGiftOption.self) - } } - var _20: Api.WallPaper? - if Int(_1!) & Int(1 << 24) != 0 {if let signature = reader.readInt32() { - _20 = Api.parse(reader, signature: signature) as? Api.WallPaper - } } - var _21: Api.PeerStories? - if Int(_1!) & Int(1 << 25) != 0 {if let signature = reader.readInt32() { - _21 = Api.parse(reader, signature: signature) as? Api.PeerStories - } } - var _22: Api.BusinessWorkHours? - if Int(_2!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _22 = Api.parse(reader, signature: signature) as? Api.BusinessWorkHours - } } - var _23: Api.BusinessLocation? - if Int(_2!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _23 = Api.parse(reader, signature: signature) as? Api.BusinessLocation - } } - var _24: Api.BusinessGreetingMessage? - if Int(_2!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _24 = Api.parse(reader, signature: signature) as? Api.BusinessGreetingMessage - } } - var _25: Api.BusinessAwayMessage? - if Int(_2!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _25 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessage - } } - var _26: Api.BusinessIntro? - if Int(_2!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _26 = Api.parse(reader, signature: signature) as? Api.BusinessIntro - } } - var _27: Api.Birthday? - if Int(_2!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { - _27 = Api.parse(reader, signature: signature) as? Api.Birthday - } } - var _28: Int64? - if Int(_2!) & Int(1 << 6) != 0 {_28 = reader.readInt64() } - var _29: Int32? - if Int(_2!) & Int(1 << 6) != 0 {_29 = reader.readInt32() } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 21) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 22) == 0) || _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 6) == 0) || _11 != nil - let _c12 = _12 != nil - let _c13 = (Int(_1!) & Int(1 << 11) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 14) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 15) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 16) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 17) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 18) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 19) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 24) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 25) == 0) || _21 != nil - let _c22 = (Int(_2!) & Int(1 << 0) == 0) || _22 != nil - let _c23 = (Int(_2!) & Int(1 << 1) == 0) || _23 != nil - let _c24 = (Int(_2!) & Int(1 << 2) == 0) || _24 != nil - let _c25 = (Int(_2!) & Int(1 << 3) == 0) || _25 != nil - let _c26 = (Int(_2!) & Int(1 << 4) == 0) || _26 != nil - let _c27 = (Int(_2!) & Int(1 << 5) == 0) || _27 != nil - let _c28 = (Int(_2!) & Int(1 << 6) == 0) || _28 != nil - let _c29 = (Int(_2!) & Int(1 << 6) == 0) || _29 != 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 && _c29 { - return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, themeEmoticon: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, premiumGifts: _19, wallpaper: _20, stories: _21, businessWorkHours: _22, businessLocation: _23, businessGreetingMessage: _24, businessAwayMessage: _25, businessIntro: _26, birthday: _27, personalChannelId: _28, personalChannelMessage: _29) + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.Update.updateReadHistoryInbox(flags: _1!, folderId: _2, peer: _3!, maxId: _4!, stillUnreadCount: _5!, pts: _6!, ptsCount: _7!) } else { return nil - } - } - - } -} -public extension Api { - enum UserProfilePhoto: TypeConstructorDescription { - case userProfilePhoto(flags: Int32, photoId: Int64, strippedThumb: Buffer?, dcId: Int32) - case userProfilePhotoEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .userProfilePhoto(let flags, let photoId, let strippedThumb, let dcId): - if boxed { - buffer.appendInt32(-2100168954) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(photoId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeBytes(strippedThumb!, buffer: buffer, boxed: false)} - serializeInt32(dcId, buffer: buffer, boxed: false) - break - case .userProfilePhotoEmpty: - if boxed { - buffer.appendInt32(1326562017) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .userProfilePhoto(let flags, let photoId, let strippedThumb, let dcId): - return ("userProfilePhoto", [("flags", flags as Any), ("photoId", photoId as Any), ("strippedThumb", strippedThumb as Any), ("dcId", dcId as Any)]) - case .userProfilePhotoEmpty: - return ("userProfilePhotoEmpty", []) - } - } - - public static func parse_userProfilePhoto(_ reader: BufferReader) -> UserProfilePhoto? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Buffer? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseBytes(reader) } + } + } + public static func parse_updateReadHistoryOutbox(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() var _4: Int32? _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.UserProfilePhoto.userProfilePhoto(flags: _1!, photoId: _2!, strippedThumb: _3, dcId: _4!) + return Api.Update.updateReadHistoryOutbox(peer: _1!, maxId: _2!, pts: _3!, ptsCount: _4!) } else { return nil } } - public static func parse_userProfilePhotoEmpty(_ reader: BufferReader) -> UserProfilePhoto? { - return Api.UserProfilePhoto.userProfilePhotoEmpty - } - - } -} -public extension Api { - enum UserStatus: TypeConstructorDescription { - case userStatusEmpty - case userStatusLastMonth(flags: Int32) - case userStatusLastWeek(flags: Int32) - case userStatusOffline(wasOnline: Int32) - case userStatusOnline(expires: Int32) - case userStatusRecently(flags: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .userStatusEmpty: - if boxed { - buffer.appendInt32(164646985) - } - - break - case .userStatusLastMonth(let flags): - if boxed { - buffer.appendInt32(1703516023) - } - serializeInt32(flags, buffer: buffer, boxed: false) - break - case .userStatusLastWeek(let flags): - if boxed { - buffer.appendInt32(1410997530) - } - serializeInt32(flags, buffer: buffer, boxed: false) - break - case .userStatusOffline(let wasOnline): - if boxed { - buffer.appendInt32(9203775) - } - serializeInt32(wasOnline, buffer: buffer, boxed: false) - break - case .userStatusOnline(let expires): - if boxed { - buffer.appendInt32(-306628279) - } - serializeInt32(expires, buffer: buffer, boxed: false) - break - case .userStatusRecently(let flags): - if boxed { - buffer.appendInt32(2065268168) - } - serializeInt32(flags, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .userStatusEmpty: - return ("userStatusEmpty", []) - case .userStatusLastMonth(let flags): - return ("userStatusLastMonth", [("flags", flags as Any)]) - case .userStatusLastWeek(let flags): - return ("userStatusLastWeek", [("flags", flags as Any)]) - case .userStatusOffline(let wasOnline): - return ("userStatusOffline", [("wasOnline", wasOnline as Any)]) - case .userStatusOnline(let expires): - return ("userStatusOnline", [("expires", expires as Any)]) - case .userStatusRecently(let flags): - return ("userStatusRecently", [("flags", flags as Any)]) - } - } - - public static func parse_userStatusEmpty(_ reader: BufferReader) -> UserStatus? { - return Api.UserStatus.userStatusEmpty - } - public static func parse_userStatusLastMonth(_ reader: BufferReader) -> UserStatus? { + public static func parse_updateReadMessagesContents(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusLastMonth(flags: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateReadMessagesContents(flags: _1!, messages: _2!, pts: _3!, ptsCount: _4!, date: _5) } else { return nil } } - public static func parse_userStatusLastWeek(_ reader: BufferReader) -> UserStatus? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_updateReadStories(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusLastWeek(flags: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateReadStories(peer: _1!, maxId: _2!) } else { return nil } } - public static func parse_userStatusOffline(_ reader: BufferReader) -> UserStatus? { + public static func parse_updateRecentEmojiStatuses(_ reader: BufferReader) -> Update? { + return Api.Update.updateRecentEmojiStatuses + } + public static func parse_updateRecentReactions(_ reader: BufferReader) -> Update? { + return Api.Update.updateRecentReactions + } + public static func parse_updateRecentStickers(_ reader: BufferReader) -> Update? { + return Api.Update.updateRecentStickers + } + public static func parse_updateSavedDialogPinned(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() + var _2: Api.DialogPeer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DialogPeer + } let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusOffline(wasOnline: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateSavedDialogPinned(flags: _1!, peer: _2!) } else { return nil } } - public static func parse_userStatusOnline(_ reader: BufferReader) -> UserStatus? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_updateSavedGifs(_ reader: BufferReader) -> Update? { + return Api.Update.updateSavedGifs + } + public static func parse_updateSavedReactionTags(_ reader: BufferReader) -> Update? { + return Api.Update.updateSavedReactionTags + } + public static func parse_updateSavedRingtones(_ reader: BufferReader) -> Update? { + return Api.Update.updateSavedRingtones + } + public static func parse_updateSentStoryReaction(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Reaction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Reaction + } let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusOnline(expires: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateSentStoryReaction(peer: _1!, storyId: _2!, reaction: _3!) } else { return nil } } - public static func parse_userStatusRecently(_ reader: BufferReader) -> UserStatus? { + public static func parse_updateServiceNotification(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Api.MessageMedia? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + var _6: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Update.updateServiceNotification(flags: _1!, inboxDate: _2, type: _3!, message: _4!, media: _5!, entities: _6!) + } + else { + return nil + } + } + public static func parse_updateSmsJob(_ reader: BufferReader) -> Update? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.UserStatus.userStatusRecently(flags: _1!) + return Api.Update.updateSmsJob(jobId: _1!) } else { return nil } } - - } -} -public extension Api { - enum Username: TypeConstructorDescription { - case username(flags: Int32, username: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .username(let flags, let username): - if boxed { - buffer.appendInt32(-1274595769) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(username, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .username(let flags, let username): - return ("username", [("flags", flags as Any), ("username", username as Any)]) - } - } - - public static func parse_username(_ reader: BufferReader) -> Username? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) + public static func parse_updateStarsBalance(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Username.username(flags: _1!, username: _2!) + if _c1 { + return Api.Update.updateStarsBalance(balance: _1!) } else { return nil } } - - } -} -public extension Api { - enum VideoSize: TypeConstructorDescription { - case videoSize(flags: Int32, type: String, w: Int32, h: Int32, size: Int32, videoStartTs: Double?) - case videoSizeEmojiMarkup(emojiId: Int64, backgroundColors: [Int32]) - case videoSizeStickerMarkup(stickerset: Api.InputStickerSet, stickerId: Int64, backgroundColors: [Int32]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .videoSize(let flags, let type, let w, let h, let size, let videoStartTs): - if boxed { - buffer.appendInt32(-567037804) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(type, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)} - break - case .videoSizeEmojiMarkup(let emojiId, let backgroundColors): - if boxed { - buffer.appendInt32(-128171716) - } - serializeInt64(emojiId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(backgroundColors.count)) - for item in backgroundColors { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .videoSizeStickerMarkup(let stickerset, let stickerId, let backgroundColors): - if boxed { - buffer.appendInt32(228623102) - } - stickerset.serialize(buffer, true) - serializeInt64(stickerId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(backgroundColors.count)) - for item in backgroundColors { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .videoSize(let flags, let type, let w, let h, let size, let videoStartTs): - return ("videoSize", [("flags", flags as Any), ("type", type as Any), ("w", w as Any), ("h", h as Any), ("size", size as Any), ("videoStartTs", videoStartTs as Any)]) - case .videoSizeEmojiMarkup(let emojiId, let backgroundColors): - return ("videoSizeEmojiMarkup", [("emojiId", emojiId as Any), ("backgroundColors", backgroundColors as Any)]) - case .videoSizeStickerMarkup(let stickerset, let stickerId, let backgroundColors): - return ("videoSizeStickerMarkup", [("stickerset", stickerset as Any), ("stickerId", stickerId as Any), ("backgroundColors", backgroundColors as Any)]) - } - } - - public static func parse_videoSize(_ reader: BufferReader) -> VideoSize? { + public static func parse_updateStickerSets(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Double? - if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readDouble() } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.VideoSize.videoSize(flags: _1!, type: _2!, w: _3!, h: _4!, size: _5!, videoStartTs: _6) + if _c1 { + return Api.Update.updateStickerSets(flags: _1!) } else { return nil } } - public static func parse_videoSizeEmojiMarkup(_ reader: BufferReader) -> VideoSize? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Int32]? + public static func parse_updateStickerSetsOrder(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Int64]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.VideoSize.videoSizeEmojiMarkup(emojiId: _1!, backgroundColors: _2!) + return Api.Update.updateStickerSetsOrder(flags: _1!, order: _2!) } else { return nil } } - public static func parse_videoSizeStickerMarkup(_ reader: BufferReader) -> VideoSize? { - var _1: Api.InputStickerSet? + public static func parse_updateStoriesStealthMode(_ reader: BufferReader) -> Update? { + var _1: Api.StoriesStealthMode? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputStickerSet - } - var _2: Int64? - _2 = reader.readInt64() - var _3: [Int32]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _1 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.VideoSize.videoSizeStickerMarkup(stickerset: _1!, stickerId: _2!, backgroundColors: _3!) + if _c1 { + return Api.Update.updateStoriesStealthMode(stealthMode: _1!) } else { return nil } } - - } -} -public extension Api { - enum WallPaper: TypeConstructorDescription { - case wallPaper(id: Int64, flags: Int32, accessHash: Int64, slug: String, document: Api.Document, settings: Api.WallPaperSettings?) - case wallPaperNoFile(id: Int64, flags: Int32, settings: Api.WallPaperSettings?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .wallPaper(let id, let flags, let accessHash, let slug, let document, let settings): - if boxed { - buffer.appendInt32(-1539849235) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeString(slug, buffer: buffer, boxed: false) - document.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {settings!.serialize(buffer, true)} - break - case .wallPaperNoFile(let id, let flags, let settings): - if boxed { - buffer.appendInt32(-528465642) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {settings!.serialize(buffer, true)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .wallPaper(let id, let flags, let accessHash, let slug, let document, let settings): - return ("wallPaper", [("id", id as Any), ("flags", flags as Any), ("accessHash", accessHash as Any), ("slug", slug as Any), ("document", document as Any), ("settings", settings as Any)]) - case .wallPaperNoFile(let id, let flags, let settings): - return ("wallPaperNoFile", [("id", id as Any), ("flags", flags as Any), ("settings", settings as Any)]) - } - } - - public static func parse_wallPaper(_ reader: BufferReader) -> WallPaper? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Api.Document? + public static func parse_updateStory(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Document + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.StoryItem? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StoryItem } - var _6: Api.WallPaperSettings? - if Int(_2!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings - } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_2!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.WallPaper.wallPaper(id: _1!, flags: _2!, accessHash: _3!, slug: _4!, document: _5!, settings: _6) + if _c1 && _c2 { + return Api.Update.updateStory(peer: _1!, story: _2!) } else { return nil } } - public static func parse_wallPaperNoFile(_ reader: BufferReader) -> WallPaper? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.WallPaperSettings? - if Int(_2!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings - } } + public static func parse_updateStoryID(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_2!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.WallPaper.wallPaperNoFile(id: _1!, flags: _2!, settings: _3) + if _c1 && _c2 { + return Api.Update.updateStoryID(id: _1!, randomId: _2!) } else { return nil } } - - } -} -public extension Api { - enum WallPaperSettings: TypeConstructorDescription { - case wallPaperSettings(flags: Int32, backgroundColor: Int32?, secondBackgroundColor: Int32?, thirdBackgroundColor: Int32?, fourthBackgroundColor: Int32?, intensity: Int32?, rotation: Int32?, emoticon: String?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .wallPaperSettings(let flags, let backgroundColor, let secondBackgroundColor, let thirdBackgroundColor, let fourthBackgroundColor, let intensity, let rotation, let emoticon): - if boxed { - buffer.appendInt32(925826256) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(backgroundColor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(secondBackgroundColor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 5) != 0 {serializeInt32(thirdBackgroundColor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 6) != 0 {serializeInt32(fourthBackgroundColor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(intensity!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(rotation!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 7) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .wallPaperSettings(let flags, let backgroundColor, let secondBackgroundColor, let thirdBackgroundColor, let fourthBackgroundColor, let intensity, let rotation, let emoticon): - return ("wallPaperSettings", [("flags", flags as Any), ("backgroundColor", backgroundColor as Any), ("secondBackgroundColor", secondBackgroundColor as Any), ("thirdBackgroundColor", thirdBackgroundColor as Any), ("fourthBackgroundColor", fourthBackgroundColor as Any), ("intensity", intensity as Any), ("rotation", rotation as Any), ("emoticon", emoticon as Any)]) - } - } - - public static func parse_wallPaperSettings(_ reader: BufferReader) -> WallPaperSettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_3 = reader.readInt32() } - var _4: Int32? - if Int(_1!) & Int(1 << 5) != 0 {_4 = reader.readInt32() } - var _5: Int32? - if Int(_1!) & Int(1 << 6) != 0 {_5 = reader.readInt32() } - var _6: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_6 = reader.readInt32() } - var _7: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_7 = reader.readInt32() } - var _8: String? - if Int(_1!) & Int(1 << 7) != 0 {_8 = parseString(reader) } + public static func parse_updateTheme(_ reader: BufferReader) -> Update? { + var _1: Api.Theme? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Theme + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 4) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 5) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 6) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.WallPaperSettings.wallPaperSettings(flags: _1!, backgroundColor: _2, secondBackgroundColor: _3, thirdBackgroundColor: _4, fourthBackgroundColor: _5, intensity: _6, rotation: _7, emoticon: _8) + if _c1 { + return Api.Update.updateTheme(theme: _1!) } else { return nil } } - - } -} -public extension Api { - enum WebAuthorization: TypeConstructorDescription { - case webAuthorization(hash: Int64, botId: Int64, domain: String, browser: String, platform: String, dateCreated: Int32, dateActive: Int32, ip: String, region: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webAuthorization(let hash, let botId, let domain, let browser, let platform, let dateCreated, let dateActive, let ip, let region): - if boxed { - buffer.appendInt32(-1493633966) - } - serializeInt64(hash, buffer: buffer, boxed: false) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeString(domain, buffer: buffer, boxed: false) - serializeString(browser, buffer: buffer, boxed: false) - serializeString(platform, buffer: buffer, boxed: false) - serializeInt32(dateCreated, buffer: buffer, boxed: false) - serializeInt32(dateActive, buffer: buffer, boxed: false) - serializeString(ip, buffer: buffer, boxed: false) - serializeString(region, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .webAuthorization(let hash, let botId, let domain, let browser, let platform, let dateCreated, let dateActive, let ip, let region): - return ("webAuthorization", [("hash", hash as Any), ("botId", botId as Any), ("domain", domain as Any), ("browser", browser as Any), ("platform", platform as Any), ("dateCreated", dateCreated as Any), ("dateActive", dateActive as Any), ("ip", ip as Any), ("region", region as Any)]) - } - } - - public static func parse_webAuthorization(_ reader: BufferReader) -> WebAuthorization? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) + public static func parse_updateTranscribedAudio(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() var _5: String? _5 = parseString(reader) - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: String? - _8 = parseString(reader) - var _9: String? - _9 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.WebAuthorization.webAuthorization(hash: _1!, botId: _2!, domain: _3!, browser: _4!, platform: _5!, dateCreated: _6!, dateActive: _7!, ip: _8!, region: _9!) + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateTranscribedAudio(flags: _1!, peer: _2!, msgId: _3!, transcriptionId: _4!, text: _5!) } else { return nil } } - - } -} -public extension Api { - enum WebDocument: TypeConstructorDescription { - case webDocument(url: String, accessHash: Int64, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) - case webDocumentNoProxy(url: String, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webDocument(let url, let accessHash, let size, let mimeType, let attributes): - if boxed { - buffer.appendInt32(475467473) - } - serializeString(url, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - break - case .webDocumentNoProxy(let url, let size, let mimeType, let attributes): - if boxed { - buffer.appendInt32(-104284986) - } - serializeString(url, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .webDocument(let url, let accessHash, let size, let mimeType, let attributes): - return ("webDocument", [("url", url as Any), ("accessHash", accessHash as Any), ("size", size as Any), ("mimeType", mimeType as Any), ("attributes", attributes as Any)]) - case .webDocumentNoProxy(let url, let size, let mimeType, let attributes): - return ("webDocumentNoProxy", [("url", url as Any), ("size", size as Any), ("mimeType", mimeType as Any), ("attributes", attributes as Any)]) - } - } - - public static func parse_webDocument(_ reader: BufferReader) -> WebDocument? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: [Api.DocumentAttribute]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) + public static func parse_updateUser(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateUser(userId: _1!) + } + else { + return nil + } + } + public static func parse_updateUserEmojiStatus(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.EmojiStatus? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.EmojiStatus } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.WebDocument.webDocument(url: _1!, accessHash: _2!, size: _3!, mimeType: _4!, attributes: _5!) + if _c1 && _c2 { + return Api.Update.updateUserEmojiStatus(userId: _1!, emojiStatus: _2!) } else { return nil } } - public static func parse_webDocumentNoProxy(_ reader: BufferReader) -> WebDocument? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() + public static func parse_updateUserName(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: String? + _2 = parseString(reader) var _3: String? _3 = parseString(reader) - var _4: [Api.DocumentAttribute]? + var _4: [Api.Username]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.WebDocument.webDocumentNoProxy(url: _1!, size: _2!, mimeType: _3!, attributes: _4!) + return Api.Update.updateUserName(userId: _1!, firstName: _2!, lastName: _3!, usernames: _4!) } else { return nil } } - - } -} -public extension Api { - enum WebPage: TypeConstructorDescription { - case webPage(flags: Int32, id: Int64, url: String, displayUrl: String, hash: Int32, type: String?, siteName: String?, title: String?, description: String?, photo: Api.Photo?, embedUrl: String?, embedType: String?, embedWidth: Int32?, embedHeight: Int32?, duration: Int32?, author: String?, document: Api.Document?, cachedPage: Api.Page?, attributes: [Api.WebPageAttribute]?) - case webPageEmpty(flags: Int32, id: Int64, url: String?) - case webPageNotModified(flags: Int32, cachedPageViews: Int32?) - case webPagePending(flags: Int32, id: Int64, url: String?, date: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes): - if boxed { - buffer.appendInt32(-392411726) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - serializeString(displayUrl, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(type!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(siteName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeString(description!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {photo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 5) != 0 {serializeString(embedUrl!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 5) != 0 {serializeString(embedType!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 6) != 0 {serializeInt32(embedWidth!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 6) != 0 {serializeInt32(embedHeight!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 7) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 8) != 0 {serializeString(author!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {document!.serialize(buffer, true)} - if Int(flags) & Int(1 << 10) != 0 {cachedPage!.serialize(buffer, true)} - if Int(flags) & Int(1 << 12) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes!.count)) - for item in attributes! { - item.serialize(buffer, true) - }} - break - case .webPageEmpty(let flags, let id, let url): - if boxed { - buffer.appendInt32(555358088) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - break - case .webPageNotModified(let flags, let cachedPageViews): - if boxed { - buffer.appendInt32(1930545681) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(cachedPageViews!, buffer: buffer, boxed: false)} - break - case .webPagePending(let flags, let id, let url, let date): - if boxed { - buffer.appendInt32(-1328464313) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - serializeInt32(date, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes): - return ("webPage", [("flags", flags as Any), ("id", id as Any), ("url", url as Any), ("displayUrl", displayUrl as Any), ("hash", hash as Any), ("type", type as Any), ("siteName", siteName as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("embedUrl", embedUrl as Any), ("embedType", embedType as Any), ("embedWidth", embedWidth as Any), ("embedHeight", embedHeight as Any), ("duration", duration as Any), ("author", author as Any), ("document", document as Any), ("cachedPage", cachedPage as Any), ("attributes", attributes as Any)]) - case .webPageEmpty(let flags, let id, let url): - return ("webPageEmpty", [("flags", flags as Any), ("id", id as Any), ("url", url as Any)]) - case .webPageNotModified(let flags, let cachedPageViews): - return ("webPageNotModified", [("flags", flags as Any), ("cachedPageViews", cachedPageViews as Any)]) - case .webPagePending(let flags, let id, let url, let date): - return ("webPagePending", [("flags", flags as Any), ("id", id as Any), ("url", url as Any), ("date", date as Any)]) - } - } - - public static func parse_webPage(_ reader: BufferReader) -> WebPage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } - var _8: String? - if Int(_1!) & Int(1 << 2) != 0 {_8 = parseString(reader) } - var _9: String? - if Int(_1!) & Int(1 << 3) != 0 {_9 = parseString(reader) } - var _10: Api.Photo? - if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.Photo - } } - var _11: String? - if Int(_1!) & Int(1 << 5) != 0 {_11 = parseString(reader) } - var _12: String? - if Int(_1!) & Int(1 << 5) != 0 {_12 = parseString(reader) } - var _13: Int32? - if Int(_1!) & Int(1 << 6) != 0 {_13 = reader.readInt32() } - var _14: Int32? - if Int(_1!) & Int(1 << 6) != 0 {_14 = reader.readInt32() } - var _15: Int32? - if Int(_1!) & Int(1 << 7) != 0 {_15 = reader.readInt32() } - var _16: String? - if Int(_1!) & Int(1 << 8) != 0 {_16 = parseString(reader) } - var _17: Api.Document? - if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _17 = Api.parse(reader, signature: signature) as? Api.Document - } } - var _18: Api.Page? - if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { - _18 = Api.parse(reader, signature: signature) as? Api.Page - } } - var _19: [Api.WebPageAttribute]? - if Int(_1!) & Int(1 << 12) != 0 {if let _ = reader.readInt32() { - _19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebPageAttribute.self) - } } + public static func parse_updateUserPhone(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 5) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 5) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 6) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 7) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 12) == 0) || _19 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { - return Api.WebPage.webPage(flags: _1!, id: _2!, url: _3!, displayUrl: _4!, hash: _5!, type: _6, siteName: _7, title: _8, description: _9, photo: _10, embedUrl: _11, embedType: _12, embedWidth: _13, embedHeight: _14, duration: _15, author: _16, document: _17, cachedPage: _18, attributes: _19) + if _c1 && _c2 { + return Api.Update.updateUserPhone(userId: _1!, phone: _2!) } else { return nil } } - public static func parse_webPageEmpty(_ reader: BufferReader) -> WebPage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + public static func parse_updateUserStatus(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.UserStatus? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.UserStatus + } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.WebPage.webPageEmpty(flags: _1!, id: _2!, url: _3) + if _c1 && _c2 { + return Api.Update.updateUserStatus(userId: _1!, status: _2!) } else { return nil } } - public static func parse_webPageNotModified(_ reader: BufferReader) -> WebPage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + public static func parse_updateUserTyping(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.SendMessageAction? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.SendMessageAction + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c2 = _2 != nil if _c1 && _c2 { - return Api.WebPage.webPageNotModified(flags: _1!, cachedPageViews: _2) + return Api.Update.updateUserTyping(userId: _1!, action: _2!) } else { return nil } } - public static func parse_webPagePending(_ reader: BufferReader) -> WebPage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: Int32? - _4 = reader.readInt32() + public static func parse_updateWebPage(_ reader: BufferReader) -> Update? { + var _1: Api.WebPage? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.WebPage + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.WebPage.webPagePending(flags: _1!, id: _2!, url: _3, date: _4!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateWebPage(webpage: _1!, pts: _2!, ptsCount: _3!) + } + else { + return nil + } + } + public static func parse_updateWebViewResultSent(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateWebViewResultSent(queryId: _1!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index 189331107c9..e09e22c1644 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -1,227 +1,111 @@ public extension Api { - indirect enum WebPageAttribute: TypeConstructorDescription { - case webPageAttributeStickerSet(flags: Int32, stickers: [Api.Document]) - case webPageAttributeStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) - case webPageAttributeTheme(flags: Int32, documents: [Api.Document]?, settings: Api.ThemeSettings?) + indirect enum Updates: TypeConstructorDescription { + case updateShort(update: Api.Update, date: Int32) + case updateShortChatMessage(flags: Int32, id: Int32, fromId: Int64, chatId: Int64, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) + case updateShortMessage(flags: Int32, id: Int32, userId: Int64, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) + case updateShortSentMessage(flags: Int32, id: Int32, pts: Int32, ptsCount: Int32, date: Int32, media: Api.MessageMedia?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) + case updates(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seq: Int32) + case updatesCombined(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seqStart: Int32, seq: Int32) + case updatesTooLong public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .webPageAttributeStickerSet(let flags, let stickers): + case .updateShort(let update, let date): if boxed { - buffer.appendInt32(1355547603) - } - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers.count)) - for item in stickers { - item.serialize(buffer, true) + buffer.appendInt32(2027216577) } + update.serialize(buffer, true) + serializeInt32(date, buffer: buffer, boxed: false) break - case .webPageAttributeStory(let flags, let peer, let id, let story): + case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities, let ttlPeriod): if boxed { - buffer.appendInt32(781501415) + buffer.appendInt32(1299050149) } serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {story!.serialize(buffer, true)} + serializeInt64(fromId, buffer: buffer, boxed: false) + serializeInt64(chatId, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} + if Int(flags) & Int(1 << 11) != 0 {serializeInt64(viaBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} break - case .webPageAttributeTheme(let flags, let documents, let settings): + case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities, let ttlPeriod): if boxed { - buffer.appendInt32(1421174295) + buffer.appendInt32(826001400) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documents!.count)) - for item in documents! { + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} + if Int(flags) & Int(1 << 11) != 0 {serializeInt64(viaBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { item.serialize(buffer, true) }} - if Int(flags) & Int(1 << 1) != 0 {settings!.serialize(buffer, true)} + if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .webPageAttributeStickerSet(let flags, let stickers): - return ("webPageAttributeStickerSet", [("flags", flags as Any), ("stickers", stickers as Any)]) - case .webPageAttributeStory(let flags, let peer, let id, let story): - return ("webPageAttributeStory", [("flags", flags as Any), ("peer", peer as Any), ("id", id as Any), ("story", story as Any)]) - case .webPageAttributeTheme(let flags, let documents, let settings): - return ("webPageAttributeTheme", [("flags", flags as Any), ("documents", documents as Any), ("settings", settings as Any)]) - } - } - - public static func parse_webPageAttributeStickerSet(_ reader: BufferReader) -> WebPageAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Document]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.WebPageAttribute.webPageAttributeStickerSet(flags: _1!, stickers: _2!) - } - else { - return nil - } - } - public static func parse_webPageAttributeStory(_ reader: BufferReader) -> WebPageAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.StoryItem? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StoryItem - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.WebPageAttribute.webPageAttributeStory(flags: _1!, peer: _2!, id: _3!, story: _4) - } - else { - return nil - } - } - public static func parse_webPageAttributeTheme(_ reader: BufferReader) -> WebPageAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Document]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } } - var _3: Api.ThemeSettings? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.ThemeSettings - } } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.WebPageAttribute.webPageAttributeTheme(flags: _1!, documents: _2, settings: _3) - } - else { - return nil - } - } - - } -} -public extension Api { - enum WebViewMessageSent: TypeConstructorDescription { - case webViewMessageSent(flags: Int32, msgId: Api.InputBotInlineMessageID?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webViewMessageSent(let flags, let msgId): + case .updateShortSentMessage(let flags, let id, let pts, let ptsCount, let date, let media, let entities, let ttlPeriod): if boxed { - buffer.appendInt32(211046684) + buffer.appendInt32(-1877614335) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {msgId!.serialize(buffer, true)} + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} + if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .webViewMessageSent(let flags, let msgId): - return ("webViewMessageSent", [("flags", flags as Any), ("msgId", msgId as Any)]) - } - } - - public static func parse_webViewMessageSent(_ reader: BufferReader) -> WebViewMessageSent? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputBotInlineMessageID? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID - } } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.WebViewMessageSent.webViewMessageSent(flags: _1!, msgId: _2) - } - else { - return nil - } - } - - } -} -public extension Api { - enum WebViewResult: TypeConstructorDescription { - case webViewResultUrl(queryId: Int64, url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webViewResultUrl(let queryId, let url): + case .updates(let updates, let users, let chats, let date, let seq): if boxed { - buffer.appendInt32(202659196) + buffer.appendInt32(1957577280) } - serializeInt64(queryId, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .webViewResultUrl(let queryId, let url): - return ("webViewResultUrl", [("queryId", queryId as Any), ("url", url as Any)]) - } - } - - public static func parse_webViewResultUrl(_ reader: BufferReader) -> WebViewResult? { - var _1: Int64? - _1 = reader.readInt64() - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.WebViewResult.webViewResultUrl(queryId: _1!, url: _2!) - } - else { - return nil - } - } - - } -} -public extension Api.account { - enum AuthorizationForm: TypeConstructorDescription { - case authorizationForm(flags: Int32, requiredTypes: [Api.SecureRequiredType], values: [Api.SecureValue], errors: [Api.SecureValueError], users: [Api.User], privacyPolicyUrl: String?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .authorizationForm(let flags, let requiredTypes, let values, let errors, let users, let privacyPolicyUrl): - if boxed { - buffer.appendInt32(-1389486888) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(updates.count)) + for item in updates { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(requiredTypes.count)) - for item in requiredTypes { + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(values.count)) - for item in values { + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(seq, buffer: buffer, boxed: false) + break + case .updatesCombined(let updates, let users, let chats, let date, let seqStart, let seq): + if boxed { + buffer.appendInt32(1918567619) + } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(errors.count)) - for item in errors { + buffer.appendInt32(Int32(updates.count)) + for item in updates { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -229,435 +113,335 @@ public extension Api.account { for item in users { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 0) != 0 {serializeString(privacyPolicyUrl!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .authorizationForm(let flags, let requiredTypes, let values, let errors, let users, let privacyPolicyUrl): - return ("authorizationForm", [("flags", flags as Any), ("requiredTypes", requiredTypes as Any), ("values", values as Any), ("errors", errors as Any), ("users", users as Any), ("privacyPolicyUrl", privacyPolicyUrl as Any)]) - } - } - - public static func parse_authorizationForm(_ reader: BufferReader) -> AuthorizationForm? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.SecureRequiredType]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureRequiredType.self) - } - var _3: [Api.SecureValue]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) - } - var _4: [Api.SecureValueError]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValueError.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.AuthorizationForm.authorizationForm(flags: _1!, requiredTypes: _2!, values: _3!, errors: _4!, users: _5!, privacyPolicyUrl: _6) - } - else { - return nil - } - } - - } -} -public extension Api.account { - enum Authorizations: TypeConstructorDescription { - case authorizations(authorizationTtlDays: Int32, authorizations: [Api.Authorization]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .authorizations(let authorizationTtlDays, let authorizations): - if boxed { - buffer.appendInt32(1275039392) - } - serializeInt32(authorizationTtlDays, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(authorizations.count)) - for item in authorizations { + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(seqStart, buffer: buffer, boxed: false) + serializeInt32(seq, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .authorizations(let authorizationTtlDays, let authorizations): - return ("authorizations", [("authorizationTtlDays", authorizationTtlDays as Any), ("authorizations", authorizations as Any)]) - } - } - - public static func parse_authorizations(_ reader: BufferReader) -> Authorizations? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Authorization]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Authorization.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.Authorizations.authorizations(authorizationTtlDays: _1!, authorizations: _2!) - } - else { - return nil - } - } - - } -} -public extension Api.account { - enum AutoDownloadSettings: TypeConstructorDescription { - case autoDownloadSettings(low: Api.AutoDownloadSettings, medium: Api.AutoDownloadSettings, high: Api.AutoDownloadSettings) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .autoDownloadSettings(let low, let medium, let high): + case .updatesTooLong: if boxed { - buffer.appendInt32(1674235686) + buffer.appendInt32(-484987010) } - low.serialize(buffer, true) - medium.serialize(buffer, true) - high.serialize(buffer, true) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .autoDownloadSettings(let low, let medium, let high): - return ("autoDownloadSettings", [("low", low as Any), ("medium", medium as Any), ("high", high as Any)]) - } - } - - public static func parse_autoDownloadSettings(_ reader: BufferReader) -> AutoDownloadSettings? { - var _1: Api.AutoDownloadSettings? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings - } - var _2: Api.AutoDownloadSettings? + case .updateShort(let update, let date): + return ("updateShort", [("update", update as Any), ("date", date as Any)]) + case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities, let ttlPeriod): + return ("updateShortChatMessage", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("chatId", chatId as Any), ("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any), ("date", date as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("replyTo", replyTo as Any), ("entities", entities as Any), ("ttlPeriod", ttlPeriod as Any)]) + case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities, let ttlPeriod): + return ("updateShortMessage", [("flags", flags as Any), ("id", id as Any), ("userId", userId as Any), ("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any), ("date", date as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("replyTo", replyTo as Any), ("entities", entities as Any), ("ttlPeriod", ttlPeriod as Any)]) + case .updateShortSentMessage(let flags, let id, let pts, let ptsCount, let date, let media, let entities, let ttlPeriod): + return ("updateShortSentMessage", [("flags", flags as Any), ("id", id as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any), ("date", date as Any), ("media", media as Any), ("entities", entities as Any), ("ttlPeriod", ttlPeriod as Any)]) + case .updates(let updates, let users, let chats, let date, let seq): + return ("updates", [("updates", updates as Any), ("users", users as Any), ("chats", chats as Any), ("date", date as Any), ("seq", seq as Any)]) + case .updatesCombined(let updates, let users, let chats, let date, let seqStart, let seq): + return ("updatesCombined", [("updates", updates as Any), ("users", users as Any), ("chats", chats as Any), ("date", date as Any), ("seqStart", seqStart as Any), ("seq", seq as Any)]) + case .updatesTooLong: + return ("updatesTooLong", []) + } + } + + public static func parse_updateShort(_ reader: BufferReader) -> Updates? { + var _1: Api.Update? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings - } - var _3: Api.AutoDownloadSettings? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings + _1 = Api.parse(reader, signature: signature) as? Api.Update } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.AutoDownloadSettings.autoDownloadSettings(low: _1!, medium: _2!, high: _3!) + if _c1 && _c2 { + return Api.Updates.updateShort(update: _1!, date: _2!) } else { return nil } } - - } -} -public extension Api.account { - enum AutoSaveSettings: TypeConstructorDescription { - case autoSaveSettings(usersSettings: Api.AutoSaveSettings, chatsSettings: Api.AutoSaveSettings, broadcastsSettings: Api.AutoSaveSettings, exceptions: [Api.AutoSaveException], chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .autoSaveSettings(let usersSettings, let chatsSettings, let broadcastsSettings, let exceptions, let chats, let users): - if boxed { - buffer.appendInt32(1279133341) - } - usersSettings.serialize(buffer, true) - chatsSettings.serialize(buffer, true) - broadcastsSettings.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(exceptions.count)) - for item in exceptions { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .autoSaveSettings(let usersSettings, let chatsSettings, let broadcastsSettings, let exceptions, let chats, let users): - return ("autoSaveSettings", [("usersSettings", usersSettings as Any), ("chatsSettings", chatsSettings as Any), ("broadcastsSettings", broadcastsSettings as Any), ("exceptions", exceptions as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_autoSaveSettings(_ reader: BufferReader) -> AutoSaveSettings? { - var _1: Api.AutoSaveSettings? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings - } - var _2: Api.AutoSaveSettings? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings - } - var _3: Api.AutoSaveSettings? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings - } - var _4: [Api.AutoSaveException]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AutoSaveException.self) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } + public static func parse_updateShortChatMessage(_ reader: BufferReader) -> Updates? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Api.MessageFwdHeader? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } } + var _10: Int64? + if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt64() } + var _11: Api.MessageReplyHeader? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } } + var _12: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { + _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + var _13: Int32? + if Int(_1!) & Int(1 << 25) != 0 {_13 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.AutoSaveSettings.autoSaveSettings(usersSettings: _1!, chatsSettings: _2!, broadcastsSettings: _3!, exceptions: _4!, chats: _5!, users: _6!) + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 25) == 0) || _13 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { + return Api.Updates.updateShortChatMessage(flags: _1!, id: _2!, fromId: _3!, chatId: _4!, message: _5!, pts: _6!, ptsCount: _7!, date: _8!, fwdFrom: _9, viaBotId: _10, replyTo: _11, entities: _12, ttlPeriod: _13) } else { return nil } } - - } -} -public extension Api.account { - enum BusinessChatLinks: TypeConstructorDescription { - case businessChatLinks(links: [Api.BusinessChatLink], chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .businessChatLinks(let links, let chats, let users): - if boxed { - buffer.appendInt32(-331111727) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(links.count)) - for item in links { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .businessChatLinks(let links, let chats, let users): - return ("businessChatLinks", [("links", links as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_businessChatLinks(_ reader: BufferReader) -> BusinessChatLinks? { - var _1: [Api.BusinessChatLink]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BusinessChatLink.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } + public static func parse_updateShortMessage(_ reader: BufferReader) -> Updates? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Api.MessageFwdHeader? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } } + var _9: Int64? + if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt64() } + var _10: Api.MessageReplyHeader? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } } + var _11: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + var _12: Int32? + if Int(_1!) & Int(1 << 25) != 0 {_12 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.BusinessChatLinks.businessChatLinks(links: _1!, chats: _2!, users: _3!) + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 11) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 25) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.Updates.updateShortMessage(flags: _1!, id: _2!, userId: _3!, message: _4!, pts: _5!, ptsCount: _6!, date: _7!, fwdFrom: _8, viaBotId: _9, replyTo: _10, entities: _11, ttlPeriod: _12) } else { return nil } } - - } -} -public extension Api.account { - enum ConnectedBots: TypeConstructorDescription { - case connectedBots(connectedBots: [Api.ConnectedBot], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .connectedBots(let connectedBots, let users): - if boxed { - buffer.appendInt32(400029819) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(connectedBots.count)) - for item in connectedBots { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .connectedBots(let connectedBots, let users): - return ("connectedBots", [("connectedBots", connectedBots as Any), ("users", users as Any)]) - } - } - - public static func parse_connectedBots(_ reader: BufferReader) -> ConnectedBots? { - var _1: [Api.ConnectedBot]? + public static func parse_updateShortSentMessage(_ reader: BufferReader) -> Updates? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Api.MessageMedia? + if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } } + var _7: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + var _8: Int32? + if Int(_1!) & Int(1 << 25) != 0 {_8 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 9) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 25) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Updates.updateShortSentMessage(flags: _1!, id: _2!, pts: _3!, ptsCount: _4!, date: _5!, media: _6, entities: _7, ttlPeriod: _8) + } + else { + return nil + } + } + public static func parse_updates(_ reader: BufferReader) -> Updates? { + var _1: [Api.Update]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ConnectedBot.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) } var _2: [Api.User]? if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.ConnectedBots.connectedBots(connectedBots: _1!, users: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Updates.updates(updates: _1!, users: _2!, chats: _3!, date: _4!, seq: _5!) } else { return nil } } - - } -} -public extension Api.account { - enum ContentSettings: TypeConstructorDescription { - case contentSettings(flags: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .contentSettings(let flags): - if boxed { - buffer.appendInt32(1474462241) - } - serializeInt32(flags, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .contentSettings(let flags): - return ("contentSettings", [("flags", flags as Any)]) - } - } - - public static func parse_contentSettings(_ reader: BufferReader) -> ContentSettings? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_updatesCombined(_ reader: BufferReader) -> Updates? { + var _1: [Api.Update]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.account.ContentSettings.contentSettings(flags: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Updates.updatesCombined(updates: _1!, users: _2!, chats: _3!, date: _4!, seqStart: _5!, seq: _6!) } else { return nil } } + public static func parse_updatesTooLong(_ reader: BufferReader) -> Updates? { + return Api.Updates.updatesTooLong + } } } -public extension Api.account { - enum EmailVerified: TypeConstructorDescription { - case emailVerified(email: String) - case emailVerifiedLogin(email: String, sentCode: Api.auth.SentCode) +public extension Api { + enum UrlAuthResult: TypeConstructorDescription { + case urlAuthResultAccepted(url: String) + case urlAuthResultDefault + case urlAuthResultRequest(flags: Int32, bot: Api.User, domain: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .emailVerified(let email): + case .urlAuthResultAccepted(let url): + if boxed { + buffer.appendInt32(-1886646706) + } + serializeString(url, buffer: buffer, boxed: false) + break + case .urlAuthResultDefault: if boxed { - buffer.appendInt32(731303195) + buffer.appendInt32(-1445536993) } - serializeString(email, buffer: buffer, boxed: false) + break - case .emailVerifiedLogin(let email, let sentCode): + case .urlAuthResultRequest(let flags, let bot, let domain): if boxed { - buffer.appendInt32(-507835039) + buffer.appendInt32(-1831650802) } - serializeString(email, buffer: buffer, boxed: false) - sentCode.serialize(buffer, true) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + serializeString(domain, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .emailVerified(let email): - return ("emailVerified", [("email", email as Any)]) - case .emailVerifiedLogin(let email, let sentCode): - return ("emailVerifiedLogin", [("email", email as Any), ("sentCode", sentCode as Any)]) + case .urlAuthResultAccepted(let url): + return ("urlAuthResultAccepted", [("url", url as Any)]) + case .urlAuthResultDefault: + return ("urlAuthResultDefault", []) + case .urlAuthResultRequest(let flags, let bot, let domain): + return ("urlAuthResultRequest", [("flags", flags as Any), ("bot", bot as Any), ("domain", domain as Any)]) } } - public static func parse_emailVerified(_ reader: BufferReader) -> EmailVerified? { + public static func parse_urlAuthResultAccepted(_ reader: BufferReader) -> UrlAuthResult? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.account.EmailVerified.emailVerified(email: _1!) + return Api.UrlAuthResult.urlAuthResultAccepted(url: _1!) } else { return nil } } - public static func parse_emailVerifiedLogin(_ reader: BufferReader) -> EmailVerified? { - var _1: String? - _1 = parseString(reader) - var _2: Api.auth.SentCode? + public static func parse_urlAuthResultDefault(_ reader: BufferReader) -> UrlAuthResult? { + return Api.UrlAuthResult.urlAuthResultDefault + } + public static func parse_urlAuthResultRequest(_ reader: BufferReader) -> UrlAuthResult? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.User? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.auth.SentCode + _2 = Api.parse(reader, signature: signature) as? Api.User } + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.EmailVerified.emailVerifiedLogin(email: _1!, sentCode: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.UrlAuthResult.urlAuthResultRequest(flags: _1!, bot: _2!, domain: _3!) } else { return nil @@ -666,138 +450,334 @@ public extension Api.account { } } -public extension Api.account { - enum EmojiStatuses: TypeConstructorDescription { - case emojiStatuses(hash: Int64, statuses: [Api.EmojiStatus]) - case emojiStatusesNotModified +public extension Api { + enum User: TypeConstructorDescription { + case user(flags: Int32, flags2: Int32, id: Int64, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: Api.UserProfilePhoto?, status: Api.UserStatus?, botInfoVersion: Int32?, restrictionReason: [Api.RestrictionReason]?, botInlinePlaceholder: String?, langCode: String?, emojiStatus: Api.EmojiStatus?, usernames: [Api.Username]?, storiesMaxId: Int32?, color: Api.PeerColor?, profileColor: Api.PeerColor?) + case userEmpty(id: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .emojiStatuses(let hash, let statuses): + case .user(let flags, let flags2, let id, let accessHash, let firstName, let lastName, let username, let phone, let photo, let status, let botInfoVersion, let restrictionReason, let botInlinePlaceholder, let langCode, let emojiStatus, let usernames, let storiesMaxId, let color, let profileColor): if boxed { - buffer.appendInt32(-1866176559) + buffer.appendInt32(559694904) } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(statuses.count)) - for item in statuses { + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(flags2, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(accessHash!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(firstName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(lastName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeString(username!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeString(phone!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {photo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 6) != 0 {status!.serialize(buffer, true)} + if Int(flags) & Int(1 << 14) != 0 {serializeInt32(botInfoVersion!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 18) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(restrictionReason!.count)) + for item in restrictionReason! { item.serialize(buffer, true) - } + }} + if Int(flags) & Int(1 << 19) != 0 {serializeString(botInlinePlaceholder!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 22) != 0 {serializeString(langCode!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 30) != 0 {emojiStatus!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(usernames!.count)) + for item in usernames! { + item.serialize(buffer, true) + }} + if Int(flags2) & Int(1 << 5) != 0 {serializeInt32(storiesMaxId!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 8) != 0 {color!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 9) != 0 {profileColor!.serialize(buffer, true)} break - case .emojiStatusesNotModified: + case .userEmpty(let id): if boxed { - buffer.appendInt32(-796072379) + buffer.appendInt32(-742634630) } - + serializeInt64(id, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .emojiStatuses(let hash, let statuses): - return ("emojiStatuses", [("hash", hash as Any), ("statuses", statuses as Any)]) - case .emojiStatusesNotModified: - return ("emojiStatusesNotModified", []) + case .user(let flags, let flags2, let id, let accessHash, let firstName, let lastName, let username, let phone, let photo, let status, let botInfoVersion, let restrictionReason, let botInlinePlaceholder, let langCode, let emojiStatus, let usernames, let storiesMaxId, let color, let profileColor): + return ("user", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("username", username as Any), ("phone", phone as Any), ("photo", photo as Any), ("status", status as Any), ("botInfoVersion", botInfoVersion as Any), ("restrictionReason", restrictionReason as Any), ("botInlinePlaceholder", botInlinePlaceholder as Any), ("langCode", langCode as Any), ("emojiStatus", emojiStatus as Any), ("usernames", usernames as Any), ("storiesMaxId", storiesMaxId as Any), ("color", color as Any), ("profileColor", profileColor as Any)]) + case .userEmpty(let id): + return ("userEmpty", [("id", id as Any)]) } } - public static func parse_emojiStatuses(_ reader: BufferReader) -> EmojiStatuses? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.EmojiStatus]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiStatus.self) - } + public static func parse_user(_ reader: BufferReader) -> User? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt64() } + var _5: String? + if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } + var _6: String? + if Int(_1!) & Int(1 << 2) != 0 {_6 = parseString(reader) } + var _7: String? + if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) } + var _8: String? + if Int(_1!) & Int(1 << 4) != 0 {_8 = parseString(reader) } + var _9: Api.UserProfilePhoto? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.UserProfilePhoto + } } + var _10: Api.UserStatus? + if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.UserStatus + } } + var _11: Int32? + if Int(_1!) & Int(1 << 14) != 0 {_11 = reader.readInt32() } + var _12: [Api.RestrictionReason]? + if Int(_1!) & Int(1 << 18) != 0 {if let _ = reader.readInt32() { + _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + } } + var _13: String? + if Int(_1!) & Int(1 << 19) != 0 {_13 = parseString(reader) } + var _14: String? + if Int(_1!) & Int(1 << 22) != 0 {_14 = parseString(reader) } + var _15: Api.EmojiStatus? + if Int(_1!) & Int(1 << 30) != 0 {if let signature = reader.readInt32() { + _15 = Api.parse(reader, signature: signature) as? Api.EmojiStatus + } } + var _16: [Api.Username]? + if Int(_2!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self) + } } + var _17: Int32? + if Int(_2!) & Int(1 << 5) != 0 {_17 = reader.readInt32() } + var _18: Api.PeerColor? + if Int(_2!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _18 = Api.parse(reader, signature: signature) as? Api.PeerColor + } } + var _19: Api.PeerColor? + if Int(_2!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.PeerColor + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.EmojiStatuses.emojiStatuses(hash: _1!, statuses: _2!) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 6) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 14) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 18) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 19) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 22) == 0) || _14 != nil + let _c15 = (Int(_1!) & Int(1 << 30) == 0) || _15 != nil + let _c16 = (Int(_2!) & Int(1 << 0) == 0) || _16 != nil + let _c17 = (Int(_2!) & Int(1 << 5) == 0) || _17 != nil + let _c18 = (Int(_2!) & Int(1 << 8) == 0) || _18 != nil + let _c19 = (Int(_2!) & Int(1 << 9) == 0) || _19 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { + return Api.User.user(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, firstName: _5, lastName: _6, username: _7, phone: _8, photo: _9, status: _10, botInfoVersion: _11, restrictionReason: _12, botInlinePlaceholder: _13, langCode: _14, emojiStatus: _15, usernames: _16, storiesMaxId: _17, color: _18, profileColor: _19) } else { return nil } } - public static func parse_emojiStatusesNotModified(_ reader: BufferReader) -> EmojiStatuses? { - return Api.account.EmojiStatuses.emojiStatusesNotModified + public static func parse_userEmpty(_ reader: BufferReader) -> User? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.User.userEmpty(id: _1!) + } + else { + return nil + } } } } -public extension Api.account { - enum Password: TypeConstructorDescription { - case password(flags: Int32, currentAlgo: Api.PasswordKdfAlgo?, srpB: Buffer?, srpId: Int64?, hint: String?, emailUnconfirmedPattern: String?, newAlgo: Api.PasswordKdfAlgo, newSecureAlgo: Api.SecurePasswordKdfAlgo, secureRandom: Buffer, pendingResetDate: Int32?, loginEmailPattern: String?) +public extension Api { + enum UserFull: TypeConstructorDescription { + case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .password(let flags, let currentAlgo, let srpB, let srpId, let hint, let emailUnconfirmedPattern, let newAlgo, let newSecureAlgo, let secureRandom, let pendingResetDate, let loginEmailPattern): + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage): if boxed { - buffer.appendInt32(-1787080453) + buffer.appendInt32(-862357728) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {currentAlgo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeBytes(srpB!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt64(srpId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeString(hint!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeString(emailUnconfirmedPattern!, buffer: buffer, boxed: false)} - newAlgo.serialize(buffer, true) - newSecureAlgo.serialize(buffer, true) - serializeBytes(secureRandom, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 5) != 0 {serializeInt32(pendingResetDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 6) != 0 {serializeString(loginEmailPattern!, buffer: buffer, boxed: false)} + serializeInt32(flags2, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(about!, buffer: buffer, boxed: false)} + settings.serialize(buffer, true) + if Int(flags) & Int(1 << 21) != 0 {personalPhoto!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {profilePhoto!.serialize(buffer, true)} + if Int(flags) & Int(1 << 22) != 0 {fallbackPhoto!.serialize(buffer, true)} + notifySettings.serialize(buffer, true) + if Int(flags) & Int(1 << 3) != 0 {botInfo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)} + serializeInt32(commonChatsCount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 14) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 15) != 0 {serializeString(themeEmoticon!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 16) != 0 {serializeString(privateForwardName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 17) != 0 {botGroupAdminRights!.serialize(buffer, true)} + if Int(flags) & Int(1 << 18) != 0 {botBroadcastAdminRights!.serialize(buffer, true)} + if Int(flags) & Int(1 << 19) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(premiumGifts!.count)) + for item in premiumGifts! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 24) != 0 {wallpaper!.serialize(buffer, true)} + if Int(flags) & Int(1 << 25) != 0 {stories!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 0) != 0 {businessWorkHours!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 1) != 0 {businessLocation!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 2) != 0 {businessGreetingMessage!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 3) != 0 {businessAwayMessage!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 4) != 0 {businessIntro!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 5) != 0 {birthday!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 6) != 0 {serializeInt64(personalChannelId!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 6) != 0 {serializeInt32(personalChannelMessage!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .password(let flags, let currentAlgo, let srpB, let srpId, let hint, let emailUnconfirmedPattern, let newAlgo, let newSecureAlgo, let secureRandom, let pendingResetDate, let loginEmailPattern): - return ("password", [("flags", flags as Any), ("currentAlgo", currentAlgo as Any), ("srpB", srpB as Any), ("srpId", srpId as Any), ("hint", hint as Any), ("emailUnconfirmedPattern", emailUnconfirmedPattern as Any), ("newAlgo", newAlgo as Any), ("newSecureAlgo", newSecureAlgo as Any), ("secureRandom", secureRandom as Any), ("pendingResetDate", pendingResetDate as Any), ("loginEmailPattern", loginEmailPattern as Any)]) + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage): + return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("premiumGifts", premiumGifts as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any)]) } } - public static func parse_password(_ reader: BufferReader) -> Password? { + public static func parse_userFull(_ reader: BufferReader) -> UserFull? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.PasswordKdfAlgo? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo - } } - var _3: Buffer? - if Int(_1!) & Int(1 << 2) != 0 {_3 = parseBytes(reader) } - var _4: Int64? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt64() } - var _5: String? - if Int(_1!) & Int(1 << 3) != 0 {_5 = parseString(reader) } - var _6: String? - if Int(_1!) & Int(1 << 4) != 0 {_6 = parseString(reader) } - var _7: Api.PasswordKdfAlgo? + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } + var _5: Api.PeerSettings? if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo + _5 = Api.parse(reader, signature: signature) as? Api.PeerSettings } - var _8: Api.SecurePasswordKdfAlgo? + var _6: Api.Photo? + if Int(_1!) & Int(1 << 21) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Photo + } } + var _7: Api.Photo? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Photo + } } + var _8: Api.Photo? + if Int(_1!) & Int(1 << 22) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Photo + } } + var _9: Api.PeerNotifySettings? if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.SecurePasswordKdfAlgo + _9 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings } - var _9: Buffer? - _9 = parseBytes(reader) - var _10: Int32? - if Int(_1!) & Int(1 << 5) != 0 {_10 = reader.readInt32() } - var _11: String? - if Int(_1!) & Int(1 << 6) != 0 {_11 = parseString(reader) } + var _10: Api.BotInfo? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.BotInfo + } } + var _11: Int32? + if Int(_1!) & Int(1 << 6) != 0 {_11 = reader.readInt32() } + var _12: Int32? + _12 = reader.readInt32() + var _13: Int32? + if Int(_1!) & Int(1 << 11) != 0 {_13 = reader.readInt32() } + var _14: Int32? + if Int(_1!) & Int(1 << 14) != 0 {_14 = reader.readInt32() } + var _15: String? + if Int(_1!) & Int(1 << 15) != 0 {_15 = parseString(reader) } + var _16: String? + if Int(_1!) & Int(1 << 16) != 0 {_16 = parseString(reader) } + var _17: Api.ChatAdminRights? + if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { + _17 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } } + var _18: Api.ChatAdminRights? + if Int(_1!) & Int(1 << 18) != 0 {if let signature = reader.readInt32() { + _18 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } } + var _19: [Api.PremiumGiftOption]? + if Int(_1!) & Int(1 << 19) != 0 {if let _ = reader.readInt32() { + _19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumGiftOption.self) + } } + var _20: Api.WallPaper? + if Int(_1!) & Int(1 << 24) != 0 {if let signature = reader.readInt32() { + _20 = Api.parse(reader, signature: signature) as? Api.WallPaper + } } + var _21: Api.PeerStories? + if Int(_1!) & Int(1 << 25) != 0 {if let signature = reader.readInt32() { + _21 = Api.parse(reader, signature: signature) as? Api.PeerStories + } } + var _22: Api.BusinessWorkHours? + if Int(_2!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _22 = Api.parse(reader, signature: signature) as? Api.BusinessWorkHours + } } + var _23: Api.BusinessLocation? + if Int(_2!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _23 = Api.parse(reader, signature: signature) as? Api.BusinessLocation + } } + var _24: Api.BusinessGreetingMessage? + if Int(_2!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _24 = Api.parse(reader, signature: signature) as? Api.BusinessGreetingMessage + } } + var _25: Api.BusinessAwayMessage? + if Int(_2!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _25 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessage + } } + var _26: Api.BusinessIntro? + if Int(_2!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _26 = Api.parse(reader, signature: signature) as? Api.BusinessIntro + } } + var _27: Api.Birthday? + if Int(_2!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _27 = Api.parse(reader, signature: signature) as? Api.Birthday + } } + var _28: Int64? + if Int(_2!) & Int(1 << 6) != 0 {_28 = reader.readInt64() } + var _29: Int32? + if Int(_2!) & Int(1 << 6) != 0 {_29 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 21) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 22) == 0) || _8 != nil let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 5) == 0) || _10 != nil + let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 6) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.account.Password.password(flags: _1!, currentAlgo: _2, srpB: _3, srpId: _4, hint: _5, emailUnconfirmedPattern: _6, newAlgo: _7!, newSecureAlgo: _8!, secureRandom: _9!, pendingResetDate: _10, loginEmailPattern: _11) + let _c12 = _12 != nil + let _c13 = (Int(_1!) & Int(1 << 11) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 14) == 0) || _14 != nil + let _c15 = (Int(_1!) & Int(1 << 15) == 0) || _15 != nil + let _c16 = (Int(_1!) & Int(1 << 16) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 17) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 18) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 19) == 0) || _19 != nil + let _c20 = (Int(_1!) & Int(1 << 24) == 0) || _20 != nil + let _c21 = (Int(_1!) & Int(1 << 25) == 0) || _21 != nil + let _c22 = (Int(_2!) & Int(1 << 0) == 0) || _22 != nil + let _c23 = (Int(_2!) & Int(1 << 1) == 0) || _23 != nil + let _c24 = (Int(_2!) & Int(1 << 2) == 0) || _24 != nil + let _c25 = (Int(_2!) & Int(1 << 3) == 0) || _25 != nil + let _c26 = (Int(_2!) & Int(1 << 4) == 0) || _26 != nil + let _c27 = (Int(_2!) & Int(1 << 5) == 0) || _27 != nil + let _c28 = (Int(_2!) & Int(1 << 6) == 0) || _28 != nil + let _c29 = (Int(_2!) & Int(1 << 6) == 0) || _29 != 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 && _c29 { + return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, themeEmoticon: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, premiumGifts: _19, wallpaper: _20, stories: _21, businessWorkHours: _22, businessLocation: _23, businessGreetingMessage: _24, businessAwayMessage: _25, businessIntro: _26, birthday: _27, personalChannelId: _28, personalChannelMessage: _29) } else { return nil @@ -806,104 +786,186 @@ public extension Api.account { } } -public extension Api.account { - enum PasswordInputSettings: TypeConstructorDescription { - case passwordInputSettings(flags: Int32, newAlgo: Api.PasswordKdfAlgo?, newPasswordHash: Buffer?, hint: String?, email: String?, newSecureSettings: Api.SecureSecretSettings?) +public extension Api { + enum UserProfilePhoto: TypeConstructorDescription { + case userProfilePhoto(flags: Int32, photoId: Int64, strippedThumb: Buffer?, dcId: Int32) + case userProfilePhotoEmpty public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .passwordInputSettings(let flags, let newAlgo, let newPasswordHash, let hint, let email, let newSecureSettings): + case .userProfilePhoto(let flags, let photoId, let strippedThumb, let dcId): if boxed { - buffer.appendInt32(-1036572727) + buffer.appendInt32(-2100168954) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {newAlgo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 0) != 0 {serializeBytes(newPasswordHash!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeString(hint!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(email!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {newSecureSettings!.serialize(buffer, true)} + serializeInt64(photoId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeBytes(strippedThumb!, buffer: buffer, boxed: false)} + serializeInt32(dcId, buffer: buffer, boxed: false) + break + case .userProfilePhotoEmpty: + if boxed { + buffer.appendInt32(1326562017) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .passwordInputSettings(let flags, let newAlgo, let newPasswordHash, let hint, let email, let newSecureSettings): - return ("passwordInputSettings", [("flags", flags as Any), ("newAlgo", newAlgo as Any), ("newPasswordHash", newPasswordHash as Any), ("hint", hint as Any), ("email", email as Any), ("newSecureSettings", newSecureSettings as Any)]) + case .userProfilePhoto(let flags, let photoId, let strippedThumb, let dcId): + return ("userProfilePhoto", [("flags", flags as Any), ("photoId", photoId as Any), ("strippedThumb", strippedThumb as Any), ("dcId", dcId as Any)]) + case .userProfilePhotoEmpty: + return ("userProfilePhotoEmpty", []) } } - public static func parse_passwordInputSettings(_ reader: BufferReader) -> PasswordInputSettings? { + public static func parse_userProfilePhoto(_ reader: BufferReader) -> UserProfilePhoto? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.PasswordKdfAlgo? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo - } } + var _2: Int64? + _2 = reader.readInt64() var _3: Buffer? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseBytes(reader) } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } - var _6: Api.SecureSecretSettings? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.SecureSecretSettings - } } + if Int(_1!) & Int(1 << 1) != 0 {_3 = parseBytes(reader) } + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.PasswordInputSettings.passwordInputSettings(flags: _1!, newAlgo: _2, newPasswordHash: _3, hint: _4, email: _5, newSecureSettings: _6) + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.UserProfilePhoto.userProfilePhoto(flags: _1!, photoId: _2!, strippedThumb: _3, dcId: _4!) } else { return nil } } + public static func parse_userProfilePhotoEmpty(_ reader: BufferReader) -> UserProfilePhoto? { + return Api.UserProfilePhoto.userProfilePhotoEmpty + } } } -public extension Api.account { - enum PasswordSettings: TypeConstructorDescription { - case passwordSettings(flags: Int32, email: String?, secureSettings: Api.SecureSecretSettings?) +public extension Api { + enum UserStatus: TypeConstructorDescription { + case userStatusEmpty + case userStatusLastMonth(flags: Int32) + case userStatusLastWeek(flags: Int32) + case userStatusOffline(wasOnline: Int32) + case userStatusOnline(expires: Int32) + case userStatusRecently(flags: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .passwordSettings(let flags, let email, let secureSettings): + case .userStatusEmpty: + if boxed { + buffer.appendInt32(164646985) + } + + break + case .userStatusLastMonth(let flags): + if boxed { + buffer.appendInt32(1703516023) + } + serializeInt32(flags, buffer: buffer, boxed: false) + break + case .userStatusLastWeek(let flags): + if boxed { + buffer.appendInt32(1410997530) + } + serializeInt32(flags, buffer: buffer, boxed: false) + break + case .userStatusOffline(let wasOnline): if boxed { - buffer.appendInt32(-1705233435) + buffer.appendInt32(9203775) + } + serializeInt32(wasOnline, buffer: buffer, boxed: false) + break + case .userStatusOnline(let expires): + if boxed { + buffer.appendInt32(-306628279) + } + serializeInt32(expires, buffer: buffer, boxed: false) + break + case .userStatusRecently(let flags): + if boxed { + buffer.appendInt32(2065268168) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(email!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {secureSettings!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .passwordSettings(let flags, let email, let secureSettings): - return ("passwordSettings", [("flags", flags as Any), ("email", email as Any), ("secureSettings", secureSettings as Any)]) - } - } - - public static func parse_passwordSettings(_ reader: BufferReader) -> PasswordSettings? { + case .userStatusEmpty: + return ("userStatusEmpty", []) + case .userStatusLastMonth(let flags): + return ("userStatusLastMonth", [("flags", flags as Any)]) + case .userStatusLastWeek(let flags): + return ("userStatusLastWeek", [("flags", flags as Any)]) + case .userStatusOffline(let wasOnline): + return ("userStatusOffline", [("wasOnline", wasOnline as Any)]) + case .userStatusOnline(let expires): + return ("userStatusOnline", [("expires", expires as Any)]) + case .userStatusRecently(let flags): + return ("userStatusRecently", [("flags", flags as Any)]) + } + } + + public static func parse_userStatusEmpty(_ reader: BufferReader) -> UserStatus? { + return Api.UserStatus.userStatusEmpty + } + public static func parse_userStatusLastMonth(_ reader: BufferReader) -> UserStatus? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - var _3: Api.SecureSecretSettings? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.SecureSecretSettings - } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.PasswordSettings.passwordSettings(flags: _1!, email: _2, secureSettings: _3) + if _c1 { + return Api.UserStatus.userStatusLastMonth(flags: _1!) + } + else { + return nil + } + } + public static func parse_userStatusLastWeek(_ reader: BufferReader) -> UserStatus? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.UserStatus.userStatusLastWeek(flags: _1!) + } + else { + return nil + } + } + public static func parse_userStatusOffline(_ reader: BufferReader) -> UserStatus? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.UserStatus.userStatusOffline(wasOnline: _1!) + } + else { + return nil + } + } + public static func parse_userStatusOnline(_ reader: BufferReader) -> UserStatus? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.UserStatus.userStatusOnline(expires: _1!) + } + else { + return nil + } + } + public static func parse_userStatusRecently(_ reader: BufferReader) -> UserStatus? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.UserStatus.userStatusRecently(flags: _1!) } else { return nil @@ -912,60 +974,38 @@ public extension Api.account { } } -public extension Api.account { - enum PrivacyRules: TypeConstructorDescription { - case privacyRules(rules: [Api.PrivacyRule], chats: [Api.Chat], users: [Api.User]) +public extension Api { + enum Username: TypeConstructorDescription { + case username(flags: Int32, username: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .privacyRules(let rules, let chats, let users): + case .username(let flags, let username): if boxed { - buffer.appendInt32(1352683077) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(rules.count)) - for item in rules { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(-1274595769) } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(username, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .privacyRules(let rules, let chats, let users): - return ("privacyRules", [("rules", rules as Any), ("chats", chats as Any), ("users", users as Any)]) + case .username(let flags, let username): + return ("username", [("flags", flags as Any), ("username", username as Any)]) } } - public static func parse_privacyRules(_ reader: BufferReader) -> PrivacyRules? { - var _1: [Api.PrivacyRule]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } + public static func parse_username(_ reader: BufferReader) -> Username? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.PrivacyRules.privacyRules(rules: _1!, chats: _2!, users: _3!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Username.username(flags: _1!, username: _2!) } else { return nil @@ -974,66 +1014,120 @@ public extension Api.account { } } -public extension Api.account { - enum ResetPasswordResult: TypeConstructorDescription { - case resetPasswordFailedWait(retryDate: Int32) - case resetPasswordOk - case resetPasswordRequestedWait(untilDate: Int32) +public extension Api { + enum VideoSize: TypeConstructorDescription { + case videoSize(flags: Int32, type: String, w: Int32, h: Int32, size: Int32, videoStartTs: Double?) + case videoSizeEmojiMarkup(emojiId: Int64, backgroundColors: [Int32]) + case videoSizeStickerMarkup(stickerset: Api.InputStickerSet, stickerId: Int64, backgroundColors: [Int32]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .resetPasswordFailedWait(let retryDate): + case .videoSize(let flags, let type, let w, let h, let size, let videoStartTs): if boxed { - buffer.appendInt32(-478701471) + buffer.appendInt32(-567037804) } - serializeInt32(retryDate, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(type, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)} break - case .resetPasswordOk: + case .videoSizeEmojiMarkup(let emojiId, let backgroundColors): if boxed { - buffer.appendInt32(-383330754) + buffer.appendInt32(-128171716) + } + serializeInt64(emojiId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(backgroundColors.count)) + for item in backgroundColors { + serializeInt32(item, buffer: buffer, boxed: false) } - break - case .resetPasswordRequestedWait(let untilDate): + case .videoSizeStickerMarkup(let stickerset, let stickerId, let backgroundColors): if boxed { - buffer.appendInt32(-370148227) + buffer.appendInt32(228623102) + } + stickerset.serialize(buffer, true) + serializeInt64(stickerId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(backgroundColors.count)) + for item in backgroundColors { + serializeInt32(item, buffer: buffer, boxed: false) } - serializeInt32(untilDate, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .resetPasswordFailedWait(let retryDate): - return ("resetPasswordFailedWait", [("retryDate", retryDate as Any)]) - case .resetPasswordOk: - return ("resetPasswordOk", []) - case .resetPasswordRequestedWait(let untilDate): - return ("resetPasswordRequestedWait", [("untilDate", untilDate as Any)]) + case .videoSize(let flags, let type, let w, let h, let size, let videoStartTs): + return ("videoSize", [("flags", flags as Any), ("type", type as Any), ("w", w as Any), ("h", h as Any), ("size", size as Any), ("videoStartTs", videoStartTs as Any)]) + case .videoSizeEmojiMarkup(let emojiId, let backgroundColors): + return ("videoSizeEmojiMarkup", [("emojiId", emojiId as Any), ("backgroundColors", backgroundColors as Any)]) + case .videoSizeStickerMarkup(let stickerset, let stickerId, let backgroundColors): + return ("videoSizeStickerMarkup", [("stickerset", stickerset as Any), ("stickerId", stickerId as Any), ("backgroundColors", backgroundColors as Any)]) } } - public static func parse_resetPasswordFailedWait(_ reader: BufferReader) -> ResetPasswordResult? { + public static func parse_videoSize(_ reader: BufferReader) -> VideoSize? { var _1: Int32? _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Double? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readDouble() } let _c1 = _1 != nil - if _c1 { - return Api.account.ResetPasswordResult.resetPasswordFailedWait(retryDate: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.VideoSize.videoSize(flags: _1!, type: _2!, w: _3!, h: _4!, size: _5!, videoStartTs: _6) } else { return nil } } - public static func parse_resetPasswordOk(_ reader: BufferReader) -> ResetPasswordResult? { - return Api.account.ResetPasswordResult.resetPasswordOk + public static func parse_videoSizeEmojiMarkup(_ reader: BufferReader) -> VideoSize? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.VideoSize.videoSizeEmojiMarkup(emojiId: _1!, backgroundColors: _2!) + } + else { + return nil + } } - public static func parse_resetPasswordRequestedWait(_ reader: BufferReader) -> ResetPasswordResult? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_videoSizeStickerMarkup(_ reader: BufferReader) -> VideoSize? { + var _1: Api.InputStickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputStickerSet + } + var _2: Int64? + _2 = reader.readInt64() + var _3: [Int32]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } let _c1 = _1 != nil - if _c1 { - return Api.account.ResetPasswordResult.resetPasswordRequestedWait(untilDate: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.VideoSize.videoSizeStickerMarkup(stickerset: _1!, stickerId: _2!, backgroundColors: _3!) } else { return nil @@ -1042,74 +1136,88 @@ public extension Api.account { } } -public extension Api.account { - enum ResolvedBusinessChatLinks: TypeConstructorDescription { - case resolvedBusinessChatLinks(flags: Int32, peer: Api.Peer, message: String, entities: [Api.MessageEntity]?, chats: [Api.Chat], users: [Api.User]) +public extension Api { + enum WallPaper: TypeConstructorDescription { + case wallPaper(id: Int64, flags: Int32, accessHash: Int64, slug: String, document: Api.Document, settings: Api.WallPaperSettings?) + case wallPaperNoFile(id: Int64, flags: Int32, settings: Api.WallPaperSettings?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .resolvedBusinessChatLinks(let flags, let peer, let message, let entities, let chats, let users): + case .wallPaper(let id, let flags, let accessHash, let slug, let document, let settings): if boxed { - buffer.appendInt32(-1708937439) + buffer.appendInt32(-1539849235) } + serializeInt64(id, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeString(slug, buffer: buffer, boxed: false) + document.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 {settings!.serialize(buffer, true)} + break + case .wallPaperNoFile(let id, let flags, let settings): + if boxed { + buffer.appendInt32(-528465642) } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {settings!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .resolvedBusinessChatLinks(let flags, let peer, let message, let entities, let chats, let users): - return ("resolvedBusinessChatLinks", [("flags", flags as Any), ("peer", peer as Any), ("message", message as Any), ("entities", entities as Any), ("chats", chats as Any), ("users", users as Any)]) + case .wallPaper(let id, let flags, let accessHash, let slug, let document, let settings): + return ("wallPaper", [("id", id as Any), ("flags", flags as Any), ("accessHash", accessHash as Any), ("slug", slug as Any), ("document", document as Any), ("settings", settings as Any)]) + case .wallPaperNoFile(let id, let flags, let settings): + return ("wallPaperNoFile", [("id", id as Any), ("flags", flags as Any), ("settings", settings as Any)]) } } - public static func parse_resolvedBusinessChatLinks(_ reader: BufferReader) -> ResolvedBusinessChatLinks? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? + public static func parse_wallPaper(_ reader: BufferReader) -> WallPaper? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: Api.Document? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer + _5 = Api.parse(reader, signature: signature) as? Api.Document } - var _3: String? - _3 = parseString(reader) - var _4: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + var _6: Api.WallPaperSettings? + if Int(_2!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings } } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil + let _c6 = (Int(_2!) & Int(1 << 2) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.ResolvedBusinessChatLinks.resolvedBusinessChatLinks(flags: _1!, peer: _2!, message: _3!, entities: _4, chats: _5!, users: _6!) + return Api.WallPaper.wallPaper(id: _1!, flags: _2!, accessHash: _3!, slug: _4!, document: _5!, settings: _6) + } + else { + return nil + } + } + public static func parse_wallPaperNoFile(_ reader: BufferReader) -> WallPaper? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.WallPaperSettings? + if Int(_2!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_2!) & Int(1 << 2) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.WallPaper.wallPaperNoFile(id: _1!, flags: _2!, settings: _3) } else { return nil @@ -1118,48 +1226,62 @@ public extension Api.account { } } -public extension Api.account { - enum SavedRingtone: TypeConstructorDescription { - case savedRingtone - case savedRingtoneConverted(document: Api.Document) +public extension Api { + enum WallPaperSettings: TypeConstructorDescription { + case wallPaperSettings(flags: Int32, backgroundColor: Int32?, secondBackgroundColor: Int32?, thirdBackgroundColor: Int32?, fourthBackgroundColor: Int32?, intensity: Int32?, rotation: Int32?, emoticon: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedRingtone: - if boxed { - buffer.appendInt32(-1222230163) - } - - break - case .savedRingtoneConverted(let document): + case .wallPaperSettings(let flags, let backgroundColor, let secondBackgroundColor, let thirdBackgroundColor, let fourthBackgroundColor, let intensity, let rotation, let emoticon): if boxed { - buffer.appendInt32(523271863) + buffer.appendInt32(925826256) } - document.serialize(buffer, true) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(backgroundColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(secondBackgroundColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {serializeInt32(thirdBackgroundColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {serializeInt32(fourthBackgroundColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(intensity!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(rotation!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 7) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedRingtone: - return ("savedRingtone", []) - case .savedRingtoneConverted(let document): - return ("savedRingtoneConverted", [("document", document as Any)]) + case .wallPaperSettings(let flags, let backgroundColor, let secondBackgroundColor, let thirdBackgroundColor, let fourthBackgroundColor, let intensity, let rotation, let emoticon): + return ("wallPaperSettings", [("flags", flags as Any), ("backgroundColor", backgroundColor as Any), ("secondBackgroundColor", secondBackgroundColor as Any), ("thirdBackgroundColor", thirdBackgroundColor as Any), ("fourthBackgroundColor", fourthBackgroundColor as Any), ("intensity", intensity as Any), ("rotation", rotation as Any), ("emoticon", emoticon as Any)]) } } - public static func parse_savedRingtone(_ reader: BufferReader) -> SavedRingtone? { - return Api.account.SavedRingtone.savedRingtone - } - public static func parse_savedRingtoneConverted(_ reader: BufferReader) -> SavedRingtone? { - var _1: Api.Document? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Document - } + public static func parse_wallPaperSettings(_ reader: BufferReader) -> WallPaperSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _3: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_3 = reader.readInt32() } + var _4: Int32? + if Int(_1!) & Int(1 << 5) != 0 {_4 = reader.readInt32() } + var _5: Int32? + if Int(_1!) & Int(1 << 6) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_6 = reader.readInt32() } + var _7: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_7 = reader.readInt32() } + var _8: String? + if Int(_1!) & Int(1 << 7) != 0 {_8 = parseString(reader) } let _c1 = _1 != nil - if _c1 { - return Api.account.SavedRingtone.savedRingtoneConverted(document: _1!) + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 4) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 5) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 6) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.WallPaperSettings.wallPaperSettings(flags: _1!, backgroundColor: _2, secondBackgroundColor: _3, thirdBackgroundColor: _4, fourthBackgroundColor: _5, intensity: _6, rotation: _7, emoticon: _8) } else { return nil @@ -1168,132 +1290,162 @@ public extension Api.account { } } -public extension Api.account { - enum SavedRingtones: TypeConstructorDescription { - case savedRingtones(hash: Int64, ringtones: [Api.Document]) - case savedRingtonesNotModified +public extension Api { + enum WebAuthorization: TypeConstructorDescription { + case webAuthorization(hash: Int64, botId: Int64, domain: String, browser: String, platform: String, dateCreated: Int32, dateActive: Int32, ip: String, region: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedRingtones(let hash, let ringtones): + case .webAuthorization(let hash, let botId, let domain, let browser, let platform, let dateCreated, let dateActive, let ip, let region): if boxed { - buffer.appendInt32(-1041683259) + buffer.appendInt32(-1493633966) } serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(ringtones.count)) - for item in ringtones { - item.serialize(buffer, true) - } - break - case .savedRingtonesNotModified: - if boxed { - buffer.appendInt32(-67704655) - } - + serializeInt64(botId, buffer: buffer, boxed: false) + serializeString(domain, buffer: buffer, boxed: false) + serializeString(browser, buffer: buffer, boxed: false) + serializeString(platform, buffer: buffer, boxed: false) + serializeInt32(dateCreated, buffer: buffer, boxed: false) + serializeInt32(dateActive, buffer: buffer, boxed: false) + serializeString(ip, buffer: buffer, boxed: false) + serializeString(region, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedRingtones(let hash, let ringtones): - return ("savedRingtones", [("hash", hash as Any), ("ringtones", ringtones as Any)]) - case .savedRingtonesNotModified: - return ("savedRingtonesNotModified", []) + case .webAuthorization(let hash, let botId, let domain, let browser, let platform, let dateCreated, let dateActive, let ip, let region): + return ("webAuthorization", [("hash", hash as Any), ("botId", botId as Any), ("domain", domain as Any), ("browser", browser as Any), ("platform", platform as Any), ("dateCreated", dateCreated as Any), ("dateActive", dateActive as Any), ("ip", ip as Any), ("region", region as Any)]) } } - public static func parse_savedRingtones(_ reader: BufferReader) -> SavedRingtones? { + public static func parse_webAuthorization(_ reader: BufferReader) -> WebAuthorization? { var _1: Int64? _1 = reader.readInt64() - var _2: [Api.Document]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: String? + _8 = parseString(reader) + var _9: String? + _9 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.SavedRingtones.savedRingtones(hash: _1!, ringtones: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.WebAuthorization.webAuthorization(hash: _1!, botId: _2!, domain: _3!, browser: _4!, platform: _5!, dateCreated: _6!, dateActive: _7!, ip: _8!, region: _9!) } else { return nil } } - public static func parse_savedRingtonesNotModified(_ reader: BufferReader) -> SavedRingtones? { - return Api.account.SavedRingtones.savedRingtonesNotModified - } } } -public extension Api.account { - enum SentEmailCode: TypeConstructorDescription { - case sentEmailCode(emailPattern: String, length: Int32) +public extension Api { + enum WebDocument: TypeConstructorDescription { + case webDocument(url: String, accessHash: Int64, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) + case webDocumentNoProxy(url: String, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sentEmailCode(let emailPattern, let length): + case .webDocument(let url, let accessHash, let size, let mimeType, let attributes): + if boxed { + buffer.appendInt32(475467473) + } + serializeString(url, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + break + case .webDocumentNoProxy(let url, let size, let mimeType, let attributes): if boxed { - buffer.appendInt32(-2128640689) + buffer.appendInt32(-104284986) + } + serializeString(url, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) } - serializeString(emailPattern, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sentEmailCode(let emailPattern, let length): - return ("sentEmailCode", [("emailPattern", emailPattern as Any), ("length", length as Any)]) + case .webDocument(let url, let accessHash, let size, let mimeType, let attributes): + return ("webDocument", [("url", url as Any), ("accessHash", accessHash as Any), ("size", size as Any), ("mimeType", mimeType as Any), ("attributes", attributes as Any)]) + case .webDocumentNoProxy(let url, let size, let mimeType, let attributes): + return ("webDocumentNoProxy", [("url", url as Any), ("size", size as Any), ("mimeType", mimeType as Any), ("attributes", attributes as Any)]) } } - public static func parse_sentEmailCode(_ reader: BufferReader) -> SentEmailCode? { + public static func parse_webDocument(_ reader: BufferReader) -> WebDocument? { var _1: String? _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: [Api.DocumentAttribute]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) + } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.SentEmailCode.sentEmailCode(emailPattern: _1!, length: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.WebDocument.webDocument(url: _1!, accessHash: _2!, size: _3!, mimeType: _4!, attributes: _5!) } else { return nil } } - - } -} -public extension Api.account { - enum Takeout: TypeConstructorDescription { - case takeout(id: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .takeout(let id): - if boxed { - buffer.appendInt32(1304052993) - } - serializeInt64(id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .takeout(let id): - return ("takeout", [("id", id as Any)]) - } - } - - public static func parse_takeout(_ reader: BufferReader) -> Takeout? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_webDocumentNoProxy(_ reader: BufferReader) -> WebDocument? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: [Api.DocumentAttribute]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) + } let _c1 = _1 != nil - if _c1 { - return Api.account.Takeout.takeout(id: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.WebDocument.webDocumentNoProxy(url: _1!, size: _2!, mimeType: _3!, attributes: _4!) } else { return nil @@ -1302,60 +1454,206 @@ public extension Api.account { } } -public extension Api.account { - enum Themes: TypeConstructorDescription { - case themes(hash: Int64, themes: [Api.Theme]) - case themesNotModified +public extension Api { + enum WebPage: TypeConstructorDescription { + case webPage(flags: Int32, id: Int64, url: String, displayUrl: String, hash: Int32, type: String?, siteName: String?, title: String?, description: String?, photo: Api.Photo?, embedUrl: String?, embedType: String?, embedWidth: Int32?, embedHeight: Int32?, duration: Int32?, author: String?, document: Api.Document?, cachedPage: Api.Page?, attributes: [Api.WebPageAttribute]?) + case webPageEmpty(flags: Int32, id: Int64, url: String?) + case webPageNotModified(flags: Int32, cachedPageViews: Int32?) + case webPagePending(flags: Int32, id: Int64, url: String?, date: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .themes(let hash, let themes): + case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes): if boxed { - buffer.appendInt32(-1707242387) + buffer.appendInt32(-392411726) } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(themes.count)) - for item in themes { + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + serializeString(displayUrl, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(type!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(siteName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeString(description!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {photo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 5) != 0 {serializeString(embedUrl!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {serializeString(embedType!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {serializeInt32(embedWidth!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {serializeInt32(embedHeight!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 7) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 8) != 0 {serializeString(author!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 9) != 0 {document!.serialize(buffer, true)} + if Int(flags) & Int(1 << 10) != 0 {cachedPage!.serialize(buffer, true)} + if Int(flags) & Int(1 << 12) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes!.count)) + for item in attributes! { item.serialize(buffer, true) + }} + break + case .webPageEmpty(let flags, let id, let url): + if boxed { + buffer.appendInt32(555358088) } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} break - case .themesNotModified: + case .webPageNotModified(let flags, let cachedPageViews): if boxed { - buffer.appendInt32(-199313886) + buffer.appendInt32(1930545681) } - + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(cachedPageViews!, buffer: buffer, boxed: false)} + break + case .webPagePending(let flags, let id, let url, let date): + if boxed { + buffer.appendInt32(-1328464313) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + serializeInt32(date, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .themes(let hash, let themes): - return ("themes", [("hash", hash as Any), ("themes", themes as Any)]) - case .themesNotModified: - return ("themesNotModified", []) + case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes): + return ("webPage", [("flags", flags as Any), ("id", id as Any), ("url", url as Any), ("displayUrl", displayUrl as Any), ("hash", hash as Any), ("type", type as Any), ("siteName", siteName as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("embedUrl", embedUrl as Any), ("embedType", embedType as Any), ("embedWidth", embedWidth as Any), ("embedHeight", embedHeight as Any), ("duration", duration as Any), ("author", author as Any), ("document", document as Any), ("cachedPage", cachedPage as Any), ("attributes", attributes as Any)]) + case .webPageEmpty(let flags, let id, let url): + return ("webPageEmpty", [("flags", flags as Any), ("id", id as Any), ("url", url as Any)]) + case .webPageNotModified(let flags, let cachedPageViews): + return ("webPageNotModified", [("flags", flags as Any), ("cachedPageViews", cachedPageViews as Any)]) + case .webPagePending(let flags, let id, let url, let date): + return ("webPagePending", [("flags", flags as Any), ("id", id as Any), ("url", url as Any), ("date", date as Any)]) } } - public static func parse_themes(_ reader: BufferReader) -> Themes? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Theme]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Theme.self) + public static func parse_webPage(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } + var _7: String? + if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } + var _8: String? + if Int(_1!) & Int(1 << 2) != 0 {_8 = parseString(reader) } + var _9: String? + if Int(_1!) & Int(1 << 3) != 0 {_9 = parseString(reader) } + var _10: Api.Photo? + if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.Photo + } } + var _11: String? + if Int(_1!) & Int(1 << 5) != 0 {_11 = parseString(reader) } + var _12: String? + if Int(_1!) & Int(1 << 5) != 0 {_12 = parseString(reader) } + var _13: Int32? + if Int(_1!) & Int(1 << 6) != 0 {_13 = reader.readInt32() } + var _14: Int32? + if Int(_1!) & Int(1 << 6) != 0 {_14 = reader.readInt32() } + var _15: Int32? + if Int(_1!) & Int(1 << 7) != 0 {_15 = reader.readInt32() } + var _16: String? + if Int(_1!) & Int(1 << 8) != 0 {_16 = parseString(reader) } + var _17: Api.Document? + if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { + _17 = Api.parse(reader, signature: signature) as? Api.Document + } } + var _18: Api.Page? + if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { + _18 = Api.parse(reader, signature: signature) as? Api.Page + } } + var _19: [Api.WebPageAttribute]? + if Int(_1!) & Int(1 << 12) != 0 {if let _ = reader.readInt32() { + _19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebPageAttribute.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 5) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 5) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 6) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil + let _c15 = (Int(_1!) & Int(1 << 7) == 0) || _15 != nil + let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 12) == 0) || _19 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { + return Api.WebPage.webPage(flags: _1!, id: _2!, url: _3!, displayUrl: _4!, hash: _5!, type: _6, siteName: _7, title: _8, description: _9, photo: _10, embedUrl: _11, embedType: _12, embedWidth: _13, embedHeight: _14, duration: _15, author: _16, document: _17, cachedPage: _18, attributes: _19) + } + else { + return nil } + } + public static func parse_webPageEmpty(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.WebPage.webPageEmpty(flags: _1!, id: _2!, url: _3) + } + else { + return nil + } + } + public static func parse_webPageNotModified(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil if _c1 && _c2 { - return Api.account.Themes.themes(hash: _1!, themes: _2!) + return Api.WebPage.webPageNotModified(flags: _1!, cachedPageViews: _2) } else { return nil } } - public static func parse_themesNotModified(_ reader: BufferReader) -> Themes? { - return Api.account.Themes.themesNotModified + public static func parse_webPagePending(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.WebPage.webPagePending(flags: _1!, id: _2!, url: _3, date: _4!) + } + else { + return nil + } } } diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 56090bb9940..189331107c9 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -1,35 +1,113 @@ -public extension Api.account { - enum TmpPassword: TypeConstructorDescription { - case tmpPassword(tmpPassword: Buffer, validUntil: Int32) +public extension Api { + indirect enum WebPageAttribute: TypeConstructorDescription { + case webPageAttributeStickerSet(flags: Int32, stickers: [Api.Document]) + case webPageAttributeStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) + case webPageAttributeTheme(flags: Int32, documents: [Api.Document]?, settings: Api.ThemeSettings?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .tmpPassword(let tmpPassword, let validUntil): + case .webPageAttributeStickerSet(let flags, let stickers): + if boxed { + buffer.appendInt32(1355547603) + } + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers.count)) + for item in stickers { + item.serialize(buffer, true) + } + break + case .webPageAttributeStory(let flags, let peer, let id, let story): if boxed { - buffer.appendInt32(-614138572) + buffer.appendInt32(781501415) } - serializeBytes(tmpPassword, buffer: buffer, boxed: false) - serializeInt32(validUntil, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {story!.serialize(buffer, true)} + break + case .webPageAttributeTheme(let flags, let documents, let settings): + if boxed { + buffer.appendInt32(1421174295) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(documents!.count)) + for item in documents! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 1) != 0 {settings!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .tmpPassword(let tmpPassword, let validUntil): - return ("tmpPassword", [("tmpPassword", tmpPassword as Any), ("validUntil", validUntil as Any)]) + case .webPageAttributeStickerSet(let flags, let stickers): + return ("webPageAttributeStickerSet", [("flags", flags as Any), ("stickers", stickers as Any)]) + case .webPageAttributeStory(let flags, let peer, let id, let story): + return ("webPageAttributeStory", [("flags", flags as Any), ("peer", peer as Any), ("id", id as Any), ("story", story as Any)]) + case .webPageAttributeTheme(let flags, let documents, let settings): + return ("webPageAttributeTheme", [("flags", flags as Any), ("documents", documents as Any), ("settings", settings as Any)]) } } - public static func parse_tmpPassword(_ reader: BufferReader) -> TmpPassword? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Int32? - _2 = reader.readInt32() + public static func parse_webPageAttributeStickerSet(_ reader: BufferReader) -> WebPageAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Document]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.account.TmpPassword.tmpPassword(tmpPassword: _1!, validUntil: _2!) + return Api.WebPageAttribute.webPageAttributeStickerSet(flags: _1!, stickers: _2!) + } + else { + return nil + } + } + public static func parse_webPageAttributeStory(_ reader: BufferReader) -> WebPageAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.StoryItem? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StoryItem + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.WebPageAttribute.webPageAttributeStory(flags: _1!, peer: _2!, id: _3!, story: _4) + } + else { + return nil + } + } + public static func parse_webPageAttributeTheme(_ reader: BufferReader) -> WebPageAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Document]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } } + var _3: Api.ThemeSettings? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.ThemeSettings + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.WebPageAttribute.webPageAttributeTheme(flags: _1!, documents: _2, settings: _3) } else { return nil @@ -38,77 +116,112 @@ public extension Api.account { } } -public extension Api.account { - enum WallPapers: TypeConstructorDescription { - case wallPapers(hash: Int64, wallpapers: [Api.WallPaper]) - case wallPapersNotModified +public extension Api { + enum WebViewMessageSent: TypeConstructorDescription { + case webViewMessageSent(flags: Int32, msgId: Api.InputBotInlineMessageID?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .wallPapers(let hash, let wallpapers): + case .webViewMessageSent(let flags, let msgId): if boxed { - buffer.appendInt32(-842824308) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(wallpapers.count)) - for item in wallpapers { - item.serialize(buffer, true) + buffer.appendInt32(211046684) } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {msgId!.serialize(buffer, true)} break - case .wallPapersNotModified: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .webViewMessageSent(let flags, let msgId): + return ("webViewMessageSent", [("flags", flags as Any), ("msgId", msgId as Any)]) + } + } + + public static func parse_webViewMessageSent(_ reader: BufferReader) -> WebViewMessageSent? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputBotInlineMessageID? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.WebViewMessageSent.webViewMessageSent(flags: _1!, msgId: _2) + } + else { + return nil + } + } + + } +} +public extension Api { + enum WebViewResult: TypeConstructorDescription { + case webViewResultUrl(queryId: Int64, url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webViewResultUrl(let queryId, let url): if boxed { - buffer.appendInt32(471437699) + buffer.appendInt32(202659196) } - + serializeInt64(queryId, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .wallPapers(let hash, let wallpapers): - return ("wallPapers", [("hash", hash as Any), ("wallpapers", wallpapers as Any)]) - case .wallPapersNotModified: - return ("wallPapersNotModified", []) + case .webViewResultUrl(let queryId, let url): + return ("webViewResultUrl", [("queryId", queryId as Any), ("url", url as Any)]) } } - public static func parse_wallPapers(_ reader: BufferReader) -> WallPapers? { + public static func parse_webViewResultUrl(_ reader: BufferReader) -> WebViewResult? { var _1: Int64? _1 = reader.readInt64() - var _2: [Api.WallPaper]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WallPaper.self) - } + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.account.WallPapers.wallPapers(hash: _1!, wallpapers: _2!) + return Api.WebViewResult.webViewResultUrl(queryId: _1!, url: _2!) } else { return nil } } - public static func parse_wallPapersNotModified(_ reader: BufferReader) -> WallPapers? { - return Api.account.WallPapers.wallPapersNotModified - } } } public extension Api.account { - enum WebAuthorizations: TypeConstructorDescription { - case webAuthorizations(authorizations: [Api.WebAuthorization], users: [Api.User]) + enum AuthorizationForm: TypeConstructorDescription { + case authorizationForm(flags: Int32, requiredTypes: [Api.SecureRequiredType], values: [Api.SecureValue], errors: [Api.SecureValueError], users: [Api.User], privacyPolicyUrl: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .webAuthorizations(let authorizations, let users): + case .authorizationForm(let flags, let requiredTypes, let values, let errors, let users, let privacyPolicyUrl): if boxed { - buffer.appendInt32(-313079300) + buffer.appendInt32(-1389486888) } + serializeInt32(flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(authorizations.count)) - for item in authorizations { + buffer.appendInt32(Int32(requiredTypes.count)) + for item in requiredTypes { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(values.count)) + for item in values { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(errors.count)) + for item in errors { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -116,30 +229,47 @@ public extension Api.account { for item in users { item.serialize(buffer, true) } + if Int(flags) & Int(1 << 0) != 0 {serializeString(privacyPolicyUrl!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .webAuthorizations(let authorizations, let users): - return ("webAuthorizations", [("authorizations", authorizations as Any), ("users", users as Any)]) + case .authorizationForm(let flags, let requiredTypes, let values, let errors, let users, let privacyPolicyUrl): + return ("authorizationForm", [("flags", flags as Any), ("requiredTypes", requiredTypes as Any), ("values", values as Any), ("errors", errors as Any), ("users", users as Any), ("privacyPolicyUrl", privacyPolicyUrl as Any)]) } } - public static func parse_webAuthorizations(_ reader: BufferReader) -> WebAuthorizations? { - var _1: [Api.WebAuthorization]? + public static func parse_authorizationForm(_ reader: BufferReader) -> AuthorizationForm? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.SecureRequiredType]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebAuthorization.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureRequiredType.self) } - var _2: [Api.User]? + var _3: [Api.SecureValue]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) + } + var _4: [Api.SecureValueError]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValueError.self) } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.WebAuthorizations.webAuthorizations(authorizations: _1!, users: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.account.AuthorizationForm.authorizationForm(flags: _1!, requiredTypes: _2!, values: _3!, errors: _4!, users: _5!, privacyPolicyUrl: _6) } else { return nil @@ -148,78 +278,44 @@ public extension Api.account { } } -public extension Api.auth { - enum Authorization: TypeConstructorDescription { - case authorization(flags: Int32, otherwiseReloginDays: Int32?, tmpSessions: Int32?, futureAuthToken: Buffer?, user: Api.User) - case authorizationSignUpRequired(flags: Int32, termsOfService: Api.help.TermsOfService?) +public extension Api.account { + enum Authorizations: TypeConstructorDescription { + case authorizations(authorizationTtlDays: Int32, authorizations: [Api.Authorization]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .authorization(let flags, let otherwiseReloginDays, let tmpSessions, let futureAuthToken, let user): + case .authorizations(let authorizationTtlDays, let authorizations): if boxed { - buffer.appendInt32(782418132) + buffer.appendInt32(1275039392) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(otherwiseReloginDays!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(tmpSessions!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeBytes(futureAuthToken!, buffer: buffer, boxed: false)} - user.serialize(buffer, true) - break - case .authorizationSignUpRequired(let flags, let termsOfService): - if boxed { - buffer.appendInt32(1148485274) + serializeInt32(authorizationTtlDays, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(authorizations.count)) + for item in authorizations { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {termsOfService!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .authorization(let flags, let otherwiseReloginDays, let tmpSessions, let futureAuthToken, let user): - return ("authorization", [("flags", flags as Any), ("otherwiseReloginDays", otherwiseReloginDays as Any), ("tmpSessions", tmpSessions as Any), ("futureAuthToken", futureAuthToken as Any), ("user", user as Any)]) - case .authorizationSignUpRequired(let flags, let termsOfService): - return ("authorizationSignUpRequired", [("flags", flags as Any), ("termsOfService", termsOfService as Any)]) + case .authorizations(let authorizationTtlDays, let authorizations): + return ("authorizations", [("authorizationTtlDays", authorizationTtlDays as Any), ("authorizations", authorizations as Any)]) } } - public static func parse_authorization(_ reader: BufferReader) -> Authorization? { + public static func parse_authorizations(_ reader: BufferReader) -> Authorizations? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: Buffer? - if Int(_1!) & Int(1 << 2) != 0 {_4 = parseBytes(reader) } - var _5: Api.User? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.User - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.Authorization.authorization(flags: _1!, otherwiseReloginDays: _2, tmpSessions: _3, futureAuthToken: _4, user: _5!) - } - else { - return nil + var _2: [Api.Authorization]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Authorization.self) } - } - public static func parse_authorizationSignUpRequired(_ reader: BufferReader) -> Authorization? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.help.TermsOfService? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.help.TermsOfService - } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c2 = _2 != nil if _c1 && _c2 { - return Api.auth.Authorization.authorizationSignUpRequired(flags: _1!, termsOfService: _2) + return Api.account.Authorizations.authorizations(authorizationTtlDays: _1!, authorizations: _2!) } else { return nil @@ -228,114 +324,128 @@ public extension Api.auth { } } -public extension Api.auth { - enum CodeType: TypeConstructorDescription { - case codeTypeCall - case codeTypeFlashCall - case codeTypeFragmentSms - case codeTypeMissedCall - case codeTypeSms +public extension Api.account { + enum AutoDownloadSettings: TypeConstructorDescription { + case autoDownloadSettings(low: Api.AutoDownloadSettings, medium: Api.AutoDownloadSettings, high: Api.AutoDownloadSettings) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .codeTypeCall: - if boxed { - buffer.appendInt32(1948046307) - } - - break - case .codeTypeFlashCall: - if boxed { - buffer.appendInt32(577556219) - } - - break - case .codeTypeFragmentSms: - if boxed { - buffer.appendInt32(116234636) - } - - break - case .codeTypeMissedCall: - if boxed { - buffer.appendInt32(-702884114) - } - - break - case .codeTypeSms: + case .autoDownloadSettings(let low, let medium, let high): if boxed { - buffer.appendInt32(1923290508) + buffer.appendInt32(1674235686) } - + low.serialize(buffer, true) + medium.serialize(buffer, true) + high.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .codeTypeCall: - return ("codeTypeCall", []) - case .codeTypeFlashCall: - return ("codeTypeFlashCall", []) - case .codeTypeFragmentSms: - return ("codeTypeFragmentSms", []) - case .codeTypeMissedCall: - return ("codeTypeMissedCall", []) - case .codeTypeSms: - return ("codeTypeSms", []) + case .autoDownloadSettings(let low, let medium, let high): + return ("autoDownloadSettings", [("low", low as Any), ("medium", medium as Any), ("high", high as Any)]) } } - public static func parse_codeTypeCall(_ reader: BufferReader) -> CodeType? { - return Api.auth.CodeType.codeTypeCall - } - public static func parse_codeTypeFlashCall(_ reader: BufferReader) -> CodeType? { - return Api.auth.CodeType.codeTypeFlashCall - } - public static func parse_codeTypeFragmentSms(_ reader: BufferReader) -> CodeType? { - return Api.auth.CodeType.codeTypeFragmentSms - } - public static func parse_codeTypeMissedCall(_ reader: BufferReader) -> CodeType? { - return Api.auth.CodeType.codeTypeMissedCall - } - public static func parse_codeTypeSms(_ reader: BufferReader) -> CodeType? { - return Api.auth.CodeType.codeTypeSms + public static func parse_autoDownloadSettings(_ reader: BufferReader) -> AutoDownloadSettings? { + var _1: Api.AutoDownloadSettings? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings + } + var _2: Api.AutoDownloadSettings? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings + } + var _3: Api.AutoDownloadSettings? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.account.AutoDownloadSettings.autoDownloadSettings(low: _1!, medium: _2!, high: _3!) + } + else { + return nil + } } } } -public extension Api.auth { - enum ExportedAuthorization: TypeConstructorDescription { - case exportedAuthorization(id: Int64, bytes: Buffer) +public extension Api.account { + enum AutoSaveSettings: TypeConstructorDescription { + case autoSaveSettings(usersSettings: Api.AutoSaveSettings, chatsSettings: Api.AutoSaveSettings, broadcastsSettings: Api.AutoSaveSettings, exceptions: [Api.AutoSaveException], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedAuthorization(let id, let bytes): + case .autoSaveSettings(let usersSettings, let chatsSettings, let broadcastsSettings, let exceptions, let chats, let users): if boxed { - buffer.appendInt32(-1271602504) + buffer.appendInt32(1279133341) + } + usersSettings.serialize(buffer, true) + chatsSettings.serialize(buffer, true) + broadcastsSettings.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(exceptions.count)) + for item in exceptions { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeInt64(id, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .exportedAuthorization(let id, let bytes): - return ("exportedAuthorization", [("id", id as Any), ("bytes", bytes as Any)]) + case .autoSaveSettings(let usersSettings, let chatsSettings, let broadcastsSettings, let exceptions, let chats, let users): + return ("autoSaveSettings", [("usersSettings", usersSettings as Any), ("chatsSettings", chatsSettings as Any), ("broadcastsSettings", broadcastsSettings as Any), ("exceptions", exceptions as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_exportedAuthorization(_ reader: BufferReader) -> ExportedAuthorization? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Buffer? - _2 = parseBytes(reader) + public static func parse_autoSaveSettings(_ reader: BufferReader) -> AutoSaveSettings? { + var _1: Api.AutoSaveSettings? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings + } + var _2: Api.AutoSaveSettings? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings + } + var _3: Api.AutoSaveSettings? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings + } + var _4: [Api.AutoSaveException]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AutoSaveException.self) + } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.ExportedAuthorization.exportedAuthorization(id: _1!, bytes: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.account.AutoSaveSettings.autoSaveSettings(usersSettings: _1!, chatsSettings: _2!, broadcastsSettings: _3!, exceptions: _4!, chats: _5!, users: _6!) } else { return nil @@ -344,38 +454,60 @@ public extension Api.auth { } } -public extension Api.auth { - enum LoggedOut: TypeConstructorDescription { - case loggedOut(flags: Int32, futureAuthToken: Buffer?) +public extension Api.account { + enum BusinessChatLinks: TypeConstructorDescription { + case businessChatLinks(links: [Api.BusinessChatLink], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .loggedOut(let flags, let futureAuthToken): + case .businessChatLinks(let links, let chats, let users): if boxed { - buffer.appendInt32(-1012759713) + buffer.appendInt32(-331111727) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(links.count)) + for item in links { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeBytes(futureAuthToken!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .loggedOut(let flags, let futureAuthToken): - return ("loggedOut", [("flags", flags as Any), ("futureAuthToken", futureAuthToken as Any)]) + case .businessChatLinks(let links, let chats, let users): + return ("businessChatLinks", [("links", links as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_loggedOut(_ reader: BufferReader) -> LoggedOut? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) } + public static func parse_businessChatLinks(_ reader: BufferReader) -> BusinessChatLinks? { + var _1: [Api.BusinessChatLink]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BusinessChatLink.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.LoggedOut.loggedOut(flags: _1!, futureAuthToken: _2) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.account.BusinessChatLinks.businessChatLinks(links: _1!, chats: _2!, users: _3!) } else { return nil @@ -384,84 +516,86 @@ public extension Api.auth { } } -public extension Api.auth { - enum LoginToken: TypeConstructorDescription { - case loginToken(expires: Int32, token: Buffer) - case loginTokenMigrateTo(dcId: Int32, token: Buffer) - case loginTokenSuccess(authorization: Api.auth.Authorization) +public extension Api.account { + enum ConnectedBots: TypeConstructorDescription { + case connectedBots(connectedBots: [Api.ConnectedBot], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .loginToken(let expires, let token): + case .connectedBots(let connectedBots, let users): if boxed { - buffer.appendInt32(1654593920) + buffer.appendInt32(400029819) } - serializeInt32(expires, buffer: buffer, boxed: false) - serializeBytes(token, buffer: buffer, boxed: false) - break - case .loginTokenMigrateTo(let dcId, let token): - if boxed { - buffer.appendInt32(110008598) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(connectedBots.count)) + for item in connectedBots { + item.serialize(buffer, true) } - serializeInt32(dcId, buffer: buffer, boxed: false) - serializeBytes(token, buffer: buffer, boxed: false) - break - case .loginTokenSuccess(let authorization): - if boxed { - buffer.appendInt32(957176926) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - authorization.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .loginToken(let expires, let token): - return ("loginToken", [("expires", expires as Any), ("token", token as Any)]) - case .loginTokenMigrateTo(let dcId, let token): - return ("loginTokenMigrateTo", [("dcId", dcId as Any), ("token", token as Any)]) - case .loginTokenSuccess(let authorization): - return ("loginTokenSuccess", [("authorization", authorization as Any)]) + case .connectedBots(let connectedBots, let users): + return ("connectedBots", [("connectedBots", connectedBots as Any), ("users", users as Any)]) } } - public static func parse_loginToken(_ reader: BufferReader) -> LoginToken? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) + public static func parse_connectedBots(_ reader: BufferReader) -> ConnectedBots? { + var _1: [Api.ConnectedBot]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ConnectedBot.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.auth.LoginToken.loginToken(expires: _1!, token: _2!) + return Api.account.ConnectedBots.connectedBots(connectedBots: _1!, users: _2!) } else { return nil } } - public static func parse_loginTokenMigrateTo(_ reader: BufferReader) -> LoginToken? { + + } +} +public extension Api.account { + enum ContentSettings: TypeConstructorDescription { + case contentSettings(flags: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .contentSettings(let flags): + if boxed { + buffer.appendInt32(1474462241) + } + serializeInt32(flags, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .contentSettings(let flags): + return ("contentSettings", [("flags", flags as Any)]) + } + } + + public static func parse_contentSettings(_ reader: BufferReader) -> ContentSettings? { var _1: Int32? _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.LoginToken.loginTokenMigrateTo(dcId: _1!, token: _2!) - } - else { - return nil - } - } - public static func parse_loginTokenSuccess(_ reader: BufferReader) -> LoginToken? { - var _1: Api.auth.Authorization? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } let _c1 = _1 != nil if _c1 { - return Api.auth.LoginToken.loginTokenSuccess(authorization: _1!) + return Api.account.ContentSettings.contentSettings(flags: _1!) } else { return nil @@ -470,34 +604,60 @@ public extension Api.auth { } } -public extension Api.auth { - enum PasswordRecovery: TypeConstructorDescription { - case passwordRecovery(emailPattern: String) +public extension Api.account { + enum EmailVerified: TypeConstructorDescription { + case emailVerified(email: String) + case emailVerifiedLogin(email: String, sentCode: Api.auth.SentCode) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .passwordRecovery(let emailPattern): + case .emailVerified(let email): if boxed { - buffer.appendInt32(326715557) + buffer.appendInt32(731303195) } - serializeString(emailPattern, buffer: buffer, boxed: false) + serializeString(email, buffer: buffer, boxed: false) + break + case .emailVerifiedLogin(let email, let sentCode): + if boxed { + buffer.appendInt32(-507835039) + } + serializeString(email, buffer: buffer, boxed: false) + sentCode.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .passwordRecovery(let emailPattern): - return ("passwordRecovery", [("emailPattern", emailPattern as Any)]) + case .emailVerified(let email): + return ("emailVerified", [("email", email as Any)]) + case .emailVerifiedLogin(let email, let sentCode): + return ("emailVerifiedLogin", [("email", email as Any), ("sentCode", sentCode as Any)]) } } - public static func parse_passwordRecovery(_ reader: BufferReader) -> PasswordRecovery? { + public static func parse_emailVerified(_ reader: BufferReader) -> EmailVerified? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.auth.PasswordRecovery.passwordRecovery(emailPattern: _1!) + return Api.account.EmailVerified.emailVerified(email: _1!) + } + else { + return nil + } + } + public static func parse_emailVerifiedLogin(_ reader: BufferReader) -> EmailVerified? { + var _1: String? + _1 = parseString(reader) + var _2: Api.auth.SentCode? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.EmailVerified.emailVerifiedLogin(email: _1!, sentCode: _2!) } else { return nil @@ -506,76 +666,138 @@ public extension Api.auth { } } -public extension Api.auth { - enum SentCode: TypeConstructorDescription { - case sentCode(flags: Int32, type: Api.auth.SentCodeType, phoneCodeHash: String, nextType: Api.auth.CodeType?, timeout: Int32?) - case sentCodeSuccess(authorization: Api.auth.Authorization) +public extension Api.account { + enum EmojiStatuses: TypeConstructorDescription { + case emojiStatuses(hash: Int64, statuses: [Api.EmojiStatus]) + case emojiStatusesNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sentCode(let flags, let type, let phoneCodeHash, let nextType, let timeout): + case .emojiStatuses(let hash, let statuses): if boxed { - buffer.appendInt32(1577067778) + buffer.appendInt32(-1866176559) + } + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(statuses.count)) + for item in statuses { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) - type.serialize(buffer, true) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {nextType!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)} break - case .sentCodeSuccess(let authorization): + case .emojiStatusesNotModified: if boxed { - buffer.appendInt32(596704836) + buffer.appendInt32(-796072379) } - authorization.serialize(buffer, true) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sentCode(let flags, let type, let phoneCodeHash, let nextType, let timeout): - return ("sentCode", [("flags", flags as Any), ("type", type as Any), ("phoneCodeHash", phoneCodeHash as Any), ("nextType", nextType as Any), ("timeout", timeout as Any)]) - case .sentCodeSuccess(let authorization): - return ("sentCodeSuccess", [("authorization", authorization as Any)]) + case .emojiStatuses(let hash, let statuses): + return ("emojiStatuses", [("hash", hash as Any), ("statuses", statuses as Any)]) + case .emojiStatusesNotModified: + return ("emojiStatusesNotModified", []) } } - public static func parse_sentCode(_ reader: BufferReader) -> SentCode? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.auth.SentCodeType? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.auth.SentCodeType + public static func parse_emojiStatuses(_ reader: BufferReader) -> EmojiStatuses? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.EmojiStatus]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiStatus.self) } - var _3: String? - _3 = parseString(reader) - var _4: Api.auth.CodeType? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.auth.CodeType - } } - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.SentCode.sentCode(flags: _1!, type: _2!, phoneCodeHash: _3!, nextType: _4, timeout: _5) + if _c1 && _c2 { + return Api.account.EmojiStatuses.emojiStatuses(hash: _1!, statuses: _2!) } else { return nil } } - public static func parse_sentCodeSuccess(_ reader: BufferReader) -> SentCode? { - var _1: Api.auth.Authorization? + public static func parse_emojiStatusesNotModified(_ reader: BufferReader) -> EmojiStatuses? { + return Api.account.EmojiStatuses.emojiStatusesNotModified + } + + } +} +public extension Api.account { + enum Password: TypeConstructorDescription { + case password(flags: Int32, currentAlgo: Api.PasswordKdfAlgo?, srpB: Buffer?, srpId: Int64?, hint: String?, emailUnconfirmedPattern: String?, newAlgo: Api.PasswordKdfAlgo, newSecureAlgo: Api.SecurePasswordKdfAlgo, secureRandom: Buffer, pendingResetDate: Int32?, loginEmailPattern: String?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .password(let flags, let currentAlgo, let srpB, let srpId, let hint, let emailUnconfirmedPattern, let newAlgo, let newSecureAlgo, let secureRandom, let pendingResetDate, let loginEmailPattern): + if boxed { + buffer.appendInt32(-1787080453) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {currentAlgo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeBytes(srpB!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt64(srpId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeString(hint!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeString(emailUnconfirmedPattern!, buffer: buffer, boxed: false)} + newAlgo.serialize(buffer, true) + newSecureAlgo.serialize(buffer, true) + serializeBytes(secureRandom, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 5) != 0 {serializeInt32(pendingResetDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {serializeString(loginEmailPattern!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .password(let flags, let currentAlgo, let srpB, let srpId, let hint, let emailUnconfirmedPattern, let newAlgo, let newSecureAlgo, let secureRandom, let pendingResetDate, let loginEmailPattern): + return ("password", [("flags", flags as Any), ("currentAlgo", currentAlgo as Any), ("srpB", srpB as Any), ("srpId", srpId as Any), ("hint", hint as Any), ("emailUnconfirmedPattern", emailUnconfirmedPattern as Any), ("newAlgo", newAlgo as Any), ("newSecureAlgo", newSecureAlgo as Any), ("secureRandom", secureRandom as Any), ("pendingResetDate", pendingResetDate as Any), ("loginEmailPattern", loginEmailPattern as Any)]) + } + } + + public static func parse_password(_ reader: BufferReader) -> Password? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.PasswordKdfAlgo? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo + } } + var _3: Buffer? + if Int(_1!) & Int(1 << 2) != 0 {_3 = parseBytes(reader) } + var _4: Int64? + if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt64() } + var _5: String? + if Int(_1!) & Int(1 << 3) != 0 {_5 = parseString(reader) } + var _6: String? + if Int(_1!) & Int(1 << 4) != 0 {_6 = parseString(reader) } + var _7: Api.PasswordKdfAlgo? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization + _7 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo } + var _8: Api.SecurePasswordKdfAlgo? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.SecurePasswordKdfAlgo + } + var _9: Buffer? + _9 = parseBytes(reader) + var _10: Int32? + if Int(_1!) & Int(1 << 5) != 0 {_10 = reader.readInt32() } + var _11: String? + if Int(_1!) & Int(1 << 6) != 0 {_11 = parseString(reader) } let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCode.sentCodeSuccess(authorization: _1!) + let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = (Int(_1!) & Int(1 << 5) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 6) == 0) || _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.account.Password.password(flags: _1!, currentAlgo: _2, srpB: _3, srpId: _4, hint: _5, emailUnconfirmedPattern: _6, newAlgo: _7!, newSecureAlgo: _8!, secureRandom: _9!, pendingResetDate: _10, loginEmailPattern: _11) } else { return nil @@ -584,224 +806,450 @@ public extension Api.auth { } } -public extension Api.auth { - enum SentCodeType: TypeConstructorDescription { - case sentCodeTypeApp(length: Int32) - case sentCodeTypeCall(length: Int32) - case sentCodeTypeEmailCode(flags: Int32, emailPattern: String, length: Int32, resetAvailablePeriod: Int32?, resetPendingDate: Int32?) - case sentCodeTypeFirebaseSms(flags: Int32, nonce: Buffer?, receipt: String?, pushTimeout: Int32?, length: Int32) - case sentCodeTypeFlashCall(pattern: String) - case sentCodeTypeFragmentSms(url: String, length: Int32) - case sentCodeTypeMissedCall(prefix: String, length: Int32) - case sentCodeTypeSetUpEmailRequired(flags: Int32) - case sentCodeTypeSms(length: Int32) - case sentCodeTypeSmsPhrase(flags: Int32, beginning: String?) - case sentCodeTypeSmsWord(flags: Int32, beginning: String?) +public extension Api.account { + enum PasswordInputSettings: TypeConstructorDescription { + case passwordInputSettings(flags: Int32, newAlgo: Api.PasswordKdfAlgo?, newPasswordHash: Buffer?, hint: String?, email: String?, newSecureSettings: Api.SecureSecretSettings?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sentCodeTypeApp(let length): + case .passwordInputSettings(let flags, let newAlgo, let newPasswordHash, let hint, let email, let newSecureSettings): if boxed { - buffer.appendInt32(1035688326) - } - serializeInt32(length, buffer: buffer, boxed: false) - break - case .sentCodeTypeCall(let length): - if boxed { - buffer.appendInt32(1398007207) - } - serializeInt32(length, buffer: buffer, boxed: false) - break - case .sentCodeTypeEmailCode(let flags, let emailPattern, let length, let resetAvailablePeriod, let resetPendingDate): - if boxed { - buffer.appendInt32(-196020837) + buffer.appendInt32(-1036572727) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(emailPattern, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(resetAvailablePeriod!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(resetPendingDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {newAlgo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {serializeBytes(newPasswordHash!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeString(hint!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(email!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {newSecureSettings!.serialize(buffer, true)} break - case .sentCodeTypeFirebaseSms(let flags, let nonce, let receipt, let pushTimeout, let length): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .passwordInputSettings(let flags, let newAlgo, let newPasswordHash, let hint, let email, let newSecureSettings): + return ("passwordInputSettings", [("flags", flags as Any), ("newAlgo", newAlgo as Any), ("newPasswordHash", newPasswordHash as Any), ("hint", hint as Any), ("email", email as Any), ("newSecureSettings", newSecureSettings as Any)]) + } + } + + public static func parse_passwordInputSettings(_ reader: BufferReader) -> PasswordInputSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.PasswordKdfAlgo? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo + } } + var _3: Buffer? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseBytes(reader) } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + var _5: String? + if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } + var _6: Api.SecureSecretSettings? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.SecureSecretSettings + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.account.PasswordInputSettings.passwordInputSettings(flags: _1!, newAlgo: _2, newPasswordHash: _3, hint: _4, email: _5, newSecureSettings: _6) + } + else { + return nil + } + } + + } +} +public extension Api.account { + enum PasswordSettings: TypeConstructorDescription { + case passwordSettings(flags: Int32, email: String?, secureSettings: Api.SecureSecretSettings?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .passwordSettings(let flags, let email, let secureSettings): if boxed { - buffer.appendInt32(-444918734) + buffer.appendInt32(-1705233435) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeBytes(nonce!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(receipt!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(pushTimeout!, buffer: buffer, boxed: false)} - serializeInt32(length, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(email!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {secureSettings!.serialize(buffer, true)} break - case .sentCodeTypeFlashCall(let pattern): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .passwordSettings(let flags, let email, let secureSettings): + return ("passwordSettings", [("flags", flags as Any), ("email", email as Any), ("secureSettings", secureSettings as Any)]) + } + } + + public static func parse_passwordSettings(_ reader: BufferReader) -> PasswordSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + var _3: Api.SecureSecretSettings? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.SecureSecretSettings + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.account.PasswordSettings.passwordSettings(flags: _1!, email: _2, secureSettings: _3) + } + else { + return nil + } + } + + } +} +public extension Api.account { + enum PrivacyRules: TypeConstructorDescription { + case privacyRules(rules: [Api.PrivacyRule], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .privacyRules(let rules, let chats, let users): if boxed { - buffer.appendInt32(-1425815847) + buffer.appendInt32(1352683077) } - serializeString(pattern, buffer: buffer, boxed: false) - break - case .sentCodeTypeFragmentSms(let url, let length): - if boxed { - buffer.appendInt32(-648651719) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(rules.count)) + for item in rules { + item.serialize(buffer, true) } - serializeString(url, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .sentCodeTypeMissedCall(let prefix, let length): - if boxed { - buffer.appendInt32(-2113903484) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) } - serializeString(prefix, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .sentCodeTypeSetUpEmailRequired(let flags): - if boxed { - buffer.appendInt32(-1521934870) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) break - case .sentCodeTypeSms(let length): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .privacyRules(let rules, let chats, let users): + return ("privacyRules", [("rules", rules as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_privacyRules(_ reader: BufferReader) -> PrivacyRules? { + var _1: [Api.PrivacyRule]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.account.PrivacyRules.privacyRules(rules: _1!, chats: _2!, users: _3!) + } + else { + return nil + } + } + + } +} +public extension Api.account { + enum ResetPasswordResult: TypeConstructorDescription { + case resetPasswordFailedWait(retryDate: Int32) + case resetPasswordOk + case resetPasswordRequestedWait(untilDate: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .resetPasswordFailedWait(let retryDate): if boxed { - buffer.appendInt32(-1073693790) + buffer.appendInt32(-478701471) } - serializeInt32(length, buffer: buffer, boxed: false) + serializeInt32(retryDate, buffer: buffer, boxed: false) break - case .sentCodeTypeSmsPhrase(let flags, let beginning): + case .resetPasswordOk: if boxed { - buffer.appendInt32(-1284008785) + buffer.appendInt32(-383330754) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(beginning!, buffer: buffer, boxed: false)} + break - case .sentCodeTypeSmsWord(let flags, let beginning): + case .resetPasswordRequestedWait(let untilDate): if boxed { - buffer.appendInt32(-1542017919) + buffer.appendInt32(-370148227) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(beginning!, buffer: buffer, boxed: false)} + serializeInt32(untilDate, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sentCodeTypeApp(let length): - return ("sentCodeTypeApp", [("length", length as Any)]) - case .sentCodeTypeCall(let length): - return ("sentCodeTypeCall", [("length", length as Any)]) - case .sentCodeTypeEmailCode(let flags, let emailPattern, let length, let resetAvailablePeriod, let resetPendingDate): - return ("sentCodeTypeEmailCode", [("flags", flags as Any), ("emailPattern", emailPattern as Any), ("length", length as Any), ("resetAvailablePeriod", resetAvailablePeriod as Any), ("resetPendingDate", resetPendingDate as Any)]) - case .sentCodeTypeFirebaseSms(let flags, let nonce, let receipt, let pushTimeout, let length): - return ("sentCodeTypeFirebaseSms", [("flags", flags as Any), ("nonce", nonce as Any), ("receipt", receipt as Any), ("pushTimeout", pushTimeout as Any), ("length", length as Any)]) - case .sentCodeTypeFlashCall(let pattern): - return ("sentCodeTypeFlashCall", [("pattern", pattern as Any)]) - case .sentCodeTypeFragmentSms(let url, let length): - return ("sentCodeTypeFragmentSms", [("url", url as Any), ("length", length as Any)]) - case .sentCodeTypeMissedCall(let prefix, let length): - return ("sentCodeTypeMissedCall", [("prefix", prefix as Any), ("length", length as Any)]) - case .sentCodeTypeSetUpEmailRequired(let flags): - return ("sentCodeTypeSetUpEmailRequired", [("flags", flags as Any)]) - case .sentCodeTypeSms(let length): - return ("sentCodeTypeSms", [("length", length as Any)]) - case .sentCodeTypeSmsPhrase(let flags, let beginning): - return ("sentCodeTypeSmsPhrase", [("flags", flags as Any), ("beginning", beginning as Any)]) - case .sentCodeTypeSmsWord(let flags, let beginning): - return ("sentCodeTypeSmsWord", [("flags", flags as Any), ("beginning", beginning as Any)]) - } - } - - public static func parse_sentCodeTypeApp(_ reader: BufferReader) -> SentCodeType? { + case .resetPasswordFailedWait(let retryDate): + return ("resetPasswordFailedWait", [("retryDate", retryDate as Any)]) + case .resetPasswordOk: + return ("resetPasswordOk", []) + case .resetPasswordRequestedWait(let untilDate): + return ("resetPasswordRequestedWait", [("untilDate", untilDate as Any)]) + } + } + + public static func parse_resetPasswordFailedWait(_ reader: BufferReader) -> ResetPasswordResult? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { - return Api.auth.SentCodeType.sentCodeTypeApp(length: _1!) + return Api.account.ResetPasswordResult.resetPasswordFailedWait(retryDate: _1!) } else { return nil } } - public static func parse_sentCodeTypeCall(_ reader: BufferReader) -> SentCodeType? { + public static func parse_resetPasswordOk(_ reader: BufferReader) -> ResetPasswordResult? { + return Api.account.ResetPasswordResult.resetPasswordOk + } + public static func parse_resetPasswordRequestedWait(_ reader: BufferReader) -> ResetPasswordResult? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { - return Api.auth.SentCodeType.sentCodeTypeCall(length: _1!) + return Api.account.ResetPasswordResult.resetPasswordRequestedWait(untilDate: _1!) } else { return nil } } - public static func parse_sentCodeTypeEmailCode(_ reader: BufferReader) -> SentCodeType? { + + } +} +public extension Api.account { + enum ResolvedBusinessChatLinks: TypeConstructorDescription { + case resolvedBusinessChatLinks(flags: Int32, peer: Api.Peer, message: String, entities: [Api.MessageEntity]?, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .resolvedBusinessChatLinks(let flags, let peer, let message, let entities, let chats, let users): + if boxed { + buffer.appendInt32(-1708937439) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .resolvedBusinessChatLinks(let flags, let peer, let message, let entities, let chats, let users): + return ("resolvedBusinessChatLinks", [("flags", flags as Any), ("peer", peer as Any), ("message", message as Any), ("entities", entities as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_resolvedBusinessChatLinks(_ reader: BufferReader) -> ResolvedBusinessChatLinks? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() } - var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.SentCodeType.sentCodeTypeEmailCode(flags: _1!, emailPattern: _2!, length: _3!, resetAvailablePeriod: _4, resetPendingDate: _5) - } - else { - return nil + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer } - } - public static func parse_sentCodeTypeFirebaseSms(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) } var _3: String? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } - var _5: Int32? - _5 = reader.readInt32() + _3 = parseString(reader) + var _4: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, receipt: _3, pushTimeout: _4, length: _5!) + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.account.ResolvedBusinessChatLinks.resolvedBusinessChatLinks(flags: _1!, peer: _2!, message: _3!, entities: _4, chats: _5!, users: _6!) } else { return nil } } - public static func parse_sentCodeTypeFlashCall(_ reader: BufferReader) -> SentCodeType? { - var _1: String? - _1 = parseString(reader) + + } +} +public extension Api.account { + enum SavedRingtone: TypeConstructorDescription { + case savedRingtone + case savedRingtoneConverted(document: Api.Document) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedRingtone: + if boxed { + buffer.appendInt32(-1222230163) + } + + break + case .savedRingtoneConverted(let document): + if boxed { + buffer.appendInt32(523271863) + } + document.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .savedRingtone: + return ("savedRingtone", []) + case .savedRingtoneConverted(let document): + return ("savedRingtoneConverted", [("document", document as Any)]) + } + } + + public static func parse_savedRingtone(_ reader: BufferReader) -> SavedRingtone? { + return Api.account.SavedRingtone.savedRingtone + } + public static func parse_savedRingtoneConverted(_ reader: BufferReader) -> SavedRingtone? { + var _1: Api.Document? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Document + } let _c1 = _1 != nil if _c1 { - return Api.auth.SentCodeType.sentCodeTypeFlashCall(pattern: _1!) + return Api.account.SavedRingtone.savedRingtoneConverted(document: _1!) } else { return nil } } - public static func parse_sentCodeTypeFragmentSms(_ reader: BufferReader) -> SentCodeType? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() + + } +} +public extension Api.account { + enum SavedRingtones: TypeConstructorDescription { + case savedRingtones(hash: Int64, ringtones: [Api.Document]) + case savedRingtonesNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedRingtones(let hash, let ringtones): + if boxed { + buffer.appendInt32(-1041683259) + } + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(ringtones.count)) + for item in ringtones { + item.serialize(buffer, true) + } + break + case .savedRingtonesNotModified: + if boxed { + buffer.appendInt32(-67704655) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .savedRingtones(let hash, let ringtones): + return ("savedRingtones", [("hash", hash as Any), ("ringtones", ringtones as Any)]) + case .savedRingtonesNotModified: + return ("savedRingtonesNotModified", []) + } + } + + public static func parse_savedRingtones(_ reader: BufferReader) -> SavedRingtones? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Document]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeFragmentSms(url: _1!, length: _2!) + return Api.account.SavedRingtones.savedRingtones(hash: _1!, ringtones: _2!) } else { return nil } } - public static func parse_sentCodeTypeMissedCall(_ reader: BufferReader) -> SentCodeType? { + public static func parse_savedRingtonesNotModified(_ reader: BufferReader) -> SavedRingtones? { + return Api.account.SavedRingtones.savedRingtonesNotModified + } + + } +} +public extension Api.account { + enum SentEmailCode: TypeConstructorDescription { + case sentEmailCode(emailPattern: String, length: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sentEmailCode(let emailPattern, let length): + if boxed { + buffer.appendInt32(-2128640689) + } + serializeString(emailPattern, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .sentEmailCode(let emailPattern, let length): + return ("sentEmailCode", [("emailPattern", emailPattern as Any), ("length", length as Any)]) + } + } + + public static func parse_sentEmailCode(_ reader: BufferReader) -> SentEmailCode? { var _1: String? _1 = parseString(reader) var _2: Int32? @@ -809,57 +1257,7 @@ public extension Api.auth { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeMissedCall(prefix: _1!, length: _2!) - } - else { - return nil - } - } - public static func parse_sentCodeTypeSetUpEmailRequired(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeSetUpEmailRequired(flags: _1!) - } - else { - return nil - } - } - public static func parse_sentCodeTypeSms(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeSms(length: _1!) - } - else { - return nil - } - } - public static func parse_sentCodeTypeSmsPhrase(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeSmsPhrase(flags: _1!, beginning: _2) - } - else { - return nil - } - } - public static func parse_sentCodeTypeSmsWord(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeSmsWord(flags: _1!, beginning: _2) + return Api.account.SentEmailCode.sentEmailCode(emailPattern: _1!, length: _2!) } else { return nil @@ -868,42 +1266,34 @@ public extension Api.auth { } } -public extension Api.bots { - enum BotInfo: TypeConstructorDescription { - case botInfo(name: String, about: String, description: String) +public extension Api.account { + enum Takeout: TypeConstructorDescription { + case takeout(id: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .botInfo(let name, let about, let description): + case .takeout(let id): if boxed { - buffer.appendInt32(-391678544) + buffer.appendInt32(1304052993) } - serializeString(name, buffer: buffer, boxed: false) - serializeString(about, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .botInfo(let name, let about, let description): - return ("botInfo", [("name", name as Any), ("about", about as Any), ("description", description as Any)]) + case .takeout(let id): + return ("takeout", [("id", id as Any)]) } } - public static func parse_botInfo(_ reader: BufferReader) -> BotInfo? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) + public static func parse_takeout(_ reader: BufferReader) -> Takeout? { + var _1: Int64? + _1 = reader.readInt64() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.bots.BotInfo.botInfo(name: _1!, about: _2!, description: _3!) + if _c1 { + return Api.account.Takeout.takeout(id: _1!) } else { return nil @@ -912,65 +1302,61 @@ public extension Api.bots { } } -public extension Api.channels { - enum AdminLogResults: TypeConstructorDescription { - case adminLogResults(events: [Api.ChannelAdminLogEvent], chats: [Api.Chat], users: [Api.User]) +public extension Api.account { + enum Themes: TypeConstructorDescription { + case themes(hash: Int64, themes: [Api.Theme]) + case themesNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .adminLogResults(let events, let chats, let users): + case .themes(let hash, let themes): if boxed { - buffer.appendInt32(-309659827) + buffer.appendInt32(-1707242387) } + serializeInt64(hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(events.count)) - for item in events { + buffer.appendInt32(Int32(themes.count)) + for item in themes { item.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + break + case .themesNotModified: + if boxed { + buffer.appendInt32(-199313886) } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .adminLogResults(let events, let chats, let users): - return ("adminLogResults", [("events", events as Any), ("chats", chats as Any), ("users", users as Any)]) + case .themes(let hash, let themes): + return ("themes", [("hash", hash as Any), ("themes", themes as Any)]) + case .themesNotModified: + return ("themesNotModified", []) } } - public static func parse_adminLogResults(_ reader: BufferReader) -> AdminLogResults? { - var _1: [Api.ChannelAdminLogEvent]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChannelAdminLogEvent.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? + public static func parse_themes(_ reader: BufferReader) -> Themes? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Theme]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Theme.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.channels.AdminLogResults.adminLogResults(events: _1!, chats: _2!, users: _3!) + if _c1 && _c2 { + return Api.account.Themes.themes(hash: _1!, themes: _2!) } else { return nil } } + public static func parse_themesNotModified(_ reader: BufferReader) -> Themes? { + return Api.account.Themes.themesNotModified + } } } diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index 04914ae0cb9..a8f5ef4aa74 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -1,53 +1,35 @@ -public extension Api.channels { - enum ChannelParticipant: TypeConstructorDescription { - case channelParticipant(participant: Api.ChannelParticipant, chats: [Api.Chat], users: [Api.User]) +public extension Api.account { + enum TmpPassword: TypeConstructorDescription { + case tmpPassword(tmpPassword: Buffer, validUntil: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .channelParticipant(let participant, let chats, let users): + case .tmpPassword(let tmpPassword, let validUntil): if boxed { - buffer.appendInt32(-541588713) - } - participant.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(-614138572) } + serializeBytes(tmpPassword, buffer: buffer, boxed: false) + serializeInt32(validUntil, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .channelParticipant(let participant, let chats, let users): - return ("channelParticipant", [("participant", participant as Any), ("chats", chats as Any), ("users", users as Any)]) + case .tmpPassword(let tmpPassword, let validUntil): + return ("tmpPassword", [("tmpPassword", tmpPassword as Any), ("validUntil", validUntil as Any)]) } } - public static func parse_channelParticipant(_ reader: BufferReader) -> ChannelParticipant? { - var _1: Api.ChannelParticipant? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } + public static func parse_tmpPassword(_ reader: BufferReader) -> TmpPassword? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.channels.ChannelParticipant.channelParticipant(participant: _1!, chats: _2!, users: _3!) + if _c1 && _c2 { + return Api.account.TmpPassword.tmpPassword(tmpPassword: _1!, validUntil: _2!) } else { return nil @@ -56,37 +38,27 @@ public extension Api.channels { } } -public extension Api.channels { - enum ChannelParticipants: TypeConstructorDescription { - case channelParticipants(count: Int32, participants: [Api.ChannelParticipant], chats: [Api.Chat], users: [Api.User]) - case channelParticipantsNotModified +public extension Api.account { + enum WallPapers: TypeConstructorDescription { + case wallPapers(hash: Int64, wallpapers: [Api.WallPaper]) + case wallPapersNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .channelParticipants(let count, let participants, let chats, let users): + case .wallPapers(let hash, let wallpapers): if boxed { - buffer.appendInt32(-1699676497) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(participants.count)) - for item in participants { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + buffer.appendInt32(-842824308) } + serializeInt64(hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { + buffer.appendInt32(Int32(wallpapers.count)) + for item in wallpapers { item.serialize(buffer, true) } break - case .channelParticipantsNotModified: + case .wallPapersNotModified: if boxed { - buffer.appendInt32(-266911767) + buffer.appendInt32(471437699) } break @@ -95,63 +67,48 @@ public extension Api.channels { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .channelParticipants(let count, let participants, let chats, let users): - return ("channelParticipants", [("count", count as Any), ("participants", participants as Any), ("chats", chats as Any), ("users", users as Any)]) - case .channelParticipantsNotModified: - return ("channelParticipantsNotModified", []) + case .wallPapers(let hash, let wallpapers): + return ("wallPapers", [("hash", hash as Any), ("wallpapers", wallpapers as Any)]) + case .wallPapersNotModified: + return ("wallPapersNotModified", []) } } - public static func parse_channelParticipants(_ reader: BufferReader) -> ChannelParticipants? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.ChannelParticipant]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChannelParticipant.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? + public static func parse_wallPapers(_ reader: BufferReader) -> WallPapers? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.WallPaper]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WallPaper.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.channels.ChannelParticipants.channelParticipants(count: _1!, participants: _2!, chats: _3!, users: _4!) + if _c1 && _c2 { + return Api.account.WallPapers.wallPapers(hash: _1!, wallpapers: _2!) } else { return nil } } - public static func parse_channelParticipantsNotModified(_ reader: BufferReader) -> ChannelParticipants? { - return Api.channels.ChannelParticipants.channelParticipantsNotModified + public static func parse_wallPapersNotModified(_ reader: BufferReader) -> WallPapers? { + return Api.account.WallPapers.wallPapersNotModified } } } -public extension Api.channels { - enum SendAsPeers: TypeConstructorDescription { - case sendAsPeers(peers: [Api.SendAsPeer], chats: [Api.Chat], users: [Api.User]) +public extension Api.account { + enum WebAuthorizations: TypeConstructorDescription { + case webAuthorizations(authorizations: [Api.WebAuthorization], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sendAsPeers(let peers, let chats, let users): + case .webAuthorizations(let authorizations, let users): if boxed { - buffer.appendInt32(-191450938) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) + buffer.appendInt32(-313079300) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(authorizations.count)) + for item in authorizations { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -165,29 +122,24 @@ public extension Api.channels { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sendAsPeers(let peers, let chats, let users): - return ("sendAsPeers", [("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)]) + case .webAuthorizations(let authorizations, let users): + return ("webAuthorizations", [("authorizations", authorizations as Any), ("users", users as Any)]) } } - public static func parse_sendAsPeers(_ reader: BufferReader) -> SendAsPeers? { - var _1: [Api.SendAsPeer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SendAsPeer.self) - } - var _2: [Api.Chat]? + public static func parse_webAuthorizations(_ reader: BufferReader) -> WebAuthorizations? { + var _1: [Api.WebAuthorization]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebAuthorization.self) } - var _3: [Api.User]? + var _2: [Api.User]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.channels.SendAsPeers.sendAsPeers(peers: _1!, chats: _2!, users: _3!) + if _c1 && _c2 { + return Api.account.WebAuthorizations.webAuthorizations(authorizations: _1!, users: _2!) } else { return nil @@ -196,264 +148,194 @@ public extension Api.channels { } } -public extension Api.channels { - enum SponsoredMessageReportResult: TypeConstructorDescription { - case sponsoredMessageReportResultAdsHidden - case sponsoredMessageReportResultChooseOption(title: String, options: [Api.SponsoredMessageReportOption]) - case sponsoredMessageReportResultReported +public extension Api.auth { + enum Authorization: TypeConstructorDescription { + case authorization(flags: Int32, otherwiseReloginDays: Int32?, tmpSessions: Int32?, futureAuthToken: Buffer?, user: Api.User) + case authorizationSignUpRequired(flags: Int32, termsOfService: Api.help.TermsOfService?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sponsoredMessageReportResultAdsHidden: - if boxed { - buffer.appendInt32(1044107055) - } - - break - case .sponsoredMessageReportResultChooseOption(let title, let options): + case .authorization(let flags, let otherwiseReloginDays, let tmpSessions, let futureAuthToken, let user): if boxed { - buffer.appendInt32(-2073059774) - } - serializeString(title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(options.count)) - for item in options { - item.serialize(buffer, true) + buffer.appendInt32(782418132) } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(otherwiseReloginDays!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(tmpSessions!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeBytes(futureAuthToken!, buffer: buffer, boxed: false)} + user.serialize(buffer, true) break - case .sponsoredMessageReportResultReported: + case .authorizationSignUpRequired(let flags, let termsOfService): if boxed { - buffer.appendInt32(-1384544183) + buffer.appendInt32(1148485274) } - + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {termsOfService!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sponsoredMessageReportResultAdsHidden: - return ("sponsoredMessageReportResultAdsHidden", []) - case .sponsoredMessageReportResultChooseOption(let title, let options): - return ("sponsoredMessageReportResultChooseOption", [("title", title as Any), ("options", options as Any)]) - case .sponsoredMessageReportResultReported: - return ("sponsoredMessageReportResultReported", []) + case .authorization(let flags, let otherwiseReloginDays, let tmpSessions, let futureAuthToken, let user): + return ("authorization", [("flags", flags as Any), ("otherwiseReloginDays", otherwiseReloginDays as Any), ("tmpSessions", tmpSessions as Any), ("futureAuthToken", futureAuthToken as Any), ("user", user as Any)]) + case .authorizationSignUpRequired(let flags, let termsOfService): + return ("authorizationSignUpRequired", [("flags", flags as Any), ("termsOfService", termsOfService as Any)]) } } - public static func parse_sponsoredMessageReportResultAdsHidden(_ reader: BufferReader) -> SponsoredMessageReportResult? { - return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultAdsHidden - } - public static func parse_sponsoredMessageReportResultChooseOption(_ reader: BufferReader) -> SponsoredMessageReportResult? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.SponsoredMessageReportOption]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessageReportOption.self) + public static func parse_authorization(_ reader: BufferReader) -> Authorization? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: Buffer? + if Int(_1!) & Int(1 << 2) != 0 {_4 = parseBytes(reader) } + var _5: Api.User? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.User } let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultChooseOption(title: _1!, options: _2!) + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.auth.Authorization.authorization(flags: _1!, otherwiseReloginDays: _2, tmpSessions: _3, futureAuthToken: _4, user: _5!) } else { return nil } } - public static func parse_sponsoredMessageReportResultReported(_ reader: BufferReader) -> SponsoredMessageReportResult? { - return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultReported + public static func parse_authorizationSignUpRequired(_ reader: BufferReader) -> Authorization? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.help.TermsOfService? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.help.TermsOfService + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.auth.Authorization.authorizationSignUpRequired(flags: _1!, termsOfService: _2) + } + else { + return nil + } } } } -public extension Api.chatlists { - enum ChatlistInvite: TypeConstructorDescription { - case chatlistInvite(flags: Int32, title: String, emoticon: String?, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) - case chatlistInviteAlready(filterId: Int32, missingPeers: [Api.Peer], alreadyPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) +public extension Api.auth { + enum CodeType: TypeConstructorDescription { + case codeTypeCall + case codeTypeFlashCall + case codeTypeFragmentSms + case codeTypeMissedCall + case codeTypeSms public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatlistInvite(let flags, let title, let emoticon, let peers, let chats, let users): + case .codeTypeCall: if boxed { - buffer.appendInt32(500007837) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(1948046307) } + break - case .chatlistInviteAlready(let filterId, let missingPeers, let alreadyPeers, let chats, let users): + case .codeTypeFlashCall: if boxed { - buffer.appendInt32(-91752871) - } - serializeInt32(filterId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(missingPeers.count)) - for item in missingPeers { - item.serialize(buffer, true) + buffer.appendInt32(577556219) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(alreadyPeers.count)) - for item in alreadyPeers { - item.serialize(buffer, true) + + break + case .codeTypeFragmentSms: + if boxed { + buffer.appendInt32(116234636) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + + break + case .codeTypeMissedCall: + if boxed { + buffer.appendInt32(-702884114) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + + break + case .codeTypeSms: + if boxed { + buffer.appendInt32(1923290508) } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chatlistInvite(let flags, let title, let emoticon, let peers, let chats, let users): - return ("chatlistInvite", [("flags", flags as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)]) - case .chatlistInviteAlready(let filterId, let missingPeers, let alreadyPeers, let chats, let users): - return ("chatlistInviteAlready", [("filterId", filterId as Any), ("missingPeers", missingPeers as Any), ("alreadyPeers", alreadyPeers as Any), ("chats", chats as Any), ("users", users as Any)]) + case .codeTypeCall: + return ("codeTypeCall", []) + case .codeTypeFlashCall: + return ("codeTypeFlashCall", []) + case .codeTypeFragmentSms: + return ("codeTypeFragmentSms", []) + case .codeTypeMissedCall: + return ("codeTypeMissedCall", []) + case .codeTypeSms: + return ("codeTypeSms", []) } } - public static func parse_chatlistInvite(_ reader: BufferReader) -> ChatlistInvite? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: [Api.Peer]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.chatlists.ChatlistInvite.chatlistInvite(flags: _1!, title: _2!, emoticon: _3, peers: _4!, chats: _5!, users: _6!) - } - else { - return nil - } + public static func parse_codeTypeCall(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeCall } - public static func parse_chatlistInviteAlready(_ reader: BufferReader) -> ChatlistInvite? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Peer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _3: [Api.Peer]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.chatlists.ChatlistInvite.chatlistInviteAlready(filterId: _1!, missingPeers: _2!, alreadyPeers: _3!, chats: _4!, users: _5!) - } - else { - return nil - } + public static func parse_codeTypeFlashCall(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeFlashCall + } + public static func parse_codeTypeFragmentSms(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeFragmentSms + } + public static func parse_codeTypeMissedCall(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeMissedCall + } + public static func parse_codeTypeSms(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeSms } } } -public extension Api.chatlists { - enum ChatlistUpdates: TypeConstructorDescription { - case chatlistUpdates(missingPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) +public extension Api.auth { + enum ExportedAuthorization: TypeConstructorDescription { + case exportedAuthorization(id: Int64, bytes: Buffer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatlistUpdates(let missingPeers, let chats, let users): + case .exportedAuthorization(let id, let bytes): if boxed { - buffer.appendInt32(-1816295539) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(missingPeers.count)) - for item in missingPeers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(-1271602504) } + serializeInt64(id, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chatlistUpdates(let missingPeers, let chats, let users): - return ("chatlistUpdates", [("missingPeers", missingPeers as Any), ("chats", chats as Any), ("users", users as Any)]) + case .exportedAuthorization(let id, let bytes): + return ("exportedAuthorization", [("id", id as Any), ("bytes", bytes as Any)]) } } - public static func parse_chatlistUpdates(_ reader: BufferReader) -> ChatlistUpdates? { - var _1: [Api.Peer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } + public static func parse_exportedAuthorization(_ reader: BufferReader) -> ExportedAuthorization? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.chatlists.ChatlistUpdates.chatlistUpdates(missingPeers: _1!, chats: _2!, users: _3!) + if _c1 && _c2 { + return Api.auth.ExportedAuthorization.exportedAuthorization(id: _1!, bytes: _2!) } else { return nil @@ -462,42 +344,38 @@ public extension Api.chatlists { } } -public extension Api.chatlists { - enum ExportedChatlistInvite: TypeConstructorDescription { - case exportedChatlistInvite(filter: Api.DialogFilter, invite: Api.ExportedChatlistInvite) +public extension Api.auth { + enum LoggedOut: TypeConstructorDescription { + case loggedOut(flags: Int32, futureAuthToken: Buffer?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedChatlistInvite(let filter, let invite): + case .loggedOut(let flags, let futureAuthToken): if boxed { - buffer.appendInt32(283567014) + buffer.appendInt32(-1012759713) } - filter.serialize(buffer, true) - invite.serialize(buffer, true) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeBytes(futureAuthToken!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .exportedChatlistInvite(let filter, let invite): - return ("exportedChatlistInvite", [("filter", filter as Any), ("invite", invite as Any)]) + case .loggedOut(let flags, let futureAuthToken): + return ("loggedOut", [("flags", flags as Any), ("futureAuthToken", futureAuthToken as Any)]) } } - public static func parse_exportedChatlistInvite(_ reader: BufferReader) -> ExportedChatlistInvite? { - var _1: Api.DialogFilter? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.DialogFilter - } - var _2: Api.ExportedChatlistInvite? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.ExportedChatlistInvite - } + public static func parse_loggedOut(_ reader: BufferReader) -> LoggedOut? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) } let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil if _c1 && _c2 { - return Api.chatlists.ExportedChatlistInvite.exportedChatlistInvite(filter: _1!, invite: _2!) + return Api.auth.LoggedOut.loggedOut(flags: _1!, futureAuthToken: _2) } else { return nil @@ -506,60 +384,84 @@ public extension Api.chatlists { } } -public extension Api.chatlists { - enum ExportedInvites: TypeConstructorDescription { - case exportedInvites(invites: [Api.ExportedChatlistInvite], chats: [Api.Chat], users: [Api.User]) +public extension Api.auth { + enum LoginToken: TypeConstructorDescription { + case loginToken(expires: Int32, token: Buffer) + case loginTokenMigrateTo(dcId: Int32, token: Buffer) + case loginTokenSuccess(authorization: Api.auth.Authorization) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedInvites(let invites, let chats, let users): + case .loginToken(let expires, let token): if boxed { - buffer.appendInt32(279670215) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(invites.count)) - for item in invites { - item.serialize(buffer, true) + buffer.appendInt32(1654593920) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + serializeInt32(expires, buffer: buffer, boxed: false) + serializeBytes(token, buffer: buffer, boxed: false) + break + case .loginTokenMigrateTo(let dcId, let token): + if boxed { + buffer.appendInt32(110008598) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeBytes(token, buffer: buffer, boxed: false) + break + case .loginTokenSuccess(let authorization): + if boxed { + buffer.appendInt32(957176926) } + authorization.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .exportedInvites(let invites, let chats, let users): - return ("exportedInvites", [("invites", invites as Any), ("chats", chats as Any), ("users", users as Any)]) + case .loginToken(let expires, let token): + return ("loginToken", [("expires", expires as Any), ("token", token as Any)]) + case .loginTokenMigrateTo(let dcId, let token): + return ("loginTokenMigrateTo", [("dcId", dcId as Any), ("token", token as Any)]) + case .loginTokenSuccess(let authorization): + return ("loginTokenSuccess", [("authorization", authorization as Any)]) } } - public static func parse_exportedInvites(_ reader: BufferReader) -> ExportedInvites? { - var _1: [Api.ExportedChatlistInvite]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedChatlistInvite.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + public static func parse_loginToken(_ reader: BufferReader) -> LoginToken? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.auth.LoginToken.loginToken(expires: _1!, token: _2!) } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + else { + return nil } + } + public static func parse_loginTokenMigrateTo(_ reader: BufferReader) -> LoginToken? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.chatlists.ExportedInvites.exportedInvites(invites: _1!, chats: _2!, users: _3!) + if _c1 && _c2 { + return Api.auth.LoginToken.loginTokenMigrateTo(dcId: _1!, token: _2!) + } + else { + return nil + } + } + public static func parse_loginTokenSuccess(_ reader: BufferReader) -> LoginToken? { + var _1: Api.auth.Authorization? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + let _c1 = _1 != nil + if _c1 { + return Api.auth.LoginToken.loginTokenSuccess(authorization: _1!) } else { return nil @@ -568,574 +470,34 @@ public extension Api.chatlists { } } -public extension Api.contacts { - enum Blocked: TypeConstructorDescription { - case blocked(blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User]) - case blockedSlice(count: Int32, blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User]) +public extension Api.auth { + enum PasswordRecovery: TypeConstructorDescription { + case passwordRecovery(emailPattern: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .blocked(let blocked, let chats, let users): + case .passwordRecovery(let emailPattern): if boxed { - buffer.appendInt32(182326673) + buffer.appendInt32(326715557) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(blocked.count)) - for item in blocked { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .blockedSlice(let count, let blocked, let chats, let users): - if boxed { - buffer.appendInt32(-513392236) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(blocked.count)) - for item in blocked { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .blocked(let blocked, let chats, let users): - return ("blocked", [("blocked", blocked as Any), ("chats", chats as Any), ("users", users as Any)]) - case .blockedSlice(let count, let blocked, let chats, let users): - return ("blockedSlice", [("count", count as Any), ("blocked", blocked as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_blocked(_ reader: BufferReader) -> Blocked? { - var _1: [Api.PeerBlocked]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.Blocked.blocked(blocked: _1!, chats: _2!, users: _3!) - } - else { - return nil - } - } - public static func parse_blockedSlice(_ reader: BufferReader) -> Blocked? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.PeerBlocked]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.contacts.Blocked.blockedSlice(count: _1!, blocked: _2!, chats: _3!, users: _4!) - } - else { - return nil - } - } - - } -} -public extension Api.contacts { - enum ContactBirthdays: TypeConstructorDescription { - case contactBirthdays(contacts: [Api.ContactBirthday], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .contactBirthdays(let contacts, let users): - if boxed { - buffer.appendInt32(290452237) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(contacts.count)) - for item in contacts { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .contactBirthdays(let contacts, let users): - return ("contactBirthdays", [("contacts", contacts as Any), ("users", users as Any)]) - } - } - - public static func parse_contactBirthdays(_ reader: BufferReader) -> ContactBirthdays? { - var _1: [Api.ContactBirthday]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ContactBirthday.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.contacts.ContactBirthdays.contactBirthdays(contacts: _1!, users: _2!) - } - else { - return nil - } - } - - } -} -public extension Api.contacts { - enum Contacts: TypeConstructorDescription { - case contacts(contacts: [Api.Contact], savedCount: Int32, users: [Api.User]) - case contactsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .contacts(let contacts, let savedCount, let users): - if boxed { - buffer.appendInt32(-353862078) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(contacts.count)) - for item in contacts { - item.serialize(buffer, true) - } - serializeInt32(savedCount, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .contactsNotModified: - if boxed { - buffer.appendInt32(-1219778094) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .contacts(let contacts, let savedCount, let users): - return ("contacts", [("contacts", contacts as Any), ("savedCount", savedCount as Any), ("users", users as Any)]) - case .contactsNotModified: - return ("contactsNotModified", []) - } - } - - public static func parse_contacts(_ reader: BufferReader) -> Contacts? { - var _1: [Api.Contact]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Contact.self) - } - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.Contacts.contacts(contacts: _1!, savedCount: _2!, users: _3!) - } - else { - return nil - } - } - public static func parse_contactsNotModified(_ reader: BufferReader) -> Contacts? { - return Api.contacts.Contacts.contactsNotModified - } - - } -} -public extension Api.contacts { - enum Found: TypeConstructorDescription { - case found(myResults: [Api.Peer], results: [Api.Peer], chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .found(let myResults, let results, let chats, let users): - if boxed { - buffer.appendInt32(-1290580579) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(myResults.count)) - for item in myResults { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(results.count)) - for item in results { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .found(let myResults, let results, let chats, let users): - return ("found", [("myResults", myResults as Any), ("results", results as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_found(_ reader: BufferReader) -> Found? { - var _1: [Api.Peer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _2: [Api.Peer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.contacts.Found.found(myResults: _1!, results: _2!, chats: _3!, users: _4!) - } - else { - return nil - } - } - - } -} -public extension Api.contacts { - enum ImportedContacts: TypeConstructorDescription { - case importedContacts(imported: [Api.ImportedContact], popularInvites: [Api.PopularContact], retryContacts: [Int64], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .importedContacts(let imported, let popularInvites, let retryContacts, let users): - if boxed { - buffer.appendInt32(2010127419) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(imported.count)) - for item in imported { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(popularInvites.count)) - for item in popularInvites { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(retryContacts.count)) - for item in retryContacts { - serializeInt64(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .importedContacts(let imported, let popularInvites, let retryContacts, let users): - return ("importedContacts", [("imported", imported as Any), ("popularInvites", popularInvites as Any), ("retryContacts", retryContacts as Any), ("users", users as Any)]) - } - } - - public static func parse_importedContacts(_ reader: BufferReader) -> ImportedContacts? { - var _1: [Api.ImportedContact]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ImportedContact.self) - } - var _2: [Api.PopularContact]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PopularContact.self) - } - var _3: [Int64]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.contacts.ImportedContacts.importedContacts(imported: _1!, popularInvites: _2!, retryContacts: _3!, users: _4!) - } - else { - return nil - } - } - - } -} -public extension Api.contacts { - enum ResolvedPeer: TypeConstructorDescription { - case resolvedPeer(peer: Api.Peer, chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .resolvedPeer(let peer, let chats, let users): - if boxed { - buffer.appendInt32(2131196633) - } - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .resolvedPeer(let peer, let chats, let users): - return ("resolvedPeer", [("peer", peer as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_resolvedPeer(_ reader: BufferReader) -> ResolvedPeer? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.ResolvedPeer.resolvedPeer(peer: _1!, chats: _2!, users: _3!) - } - else { - return nil - } - } - - } -} -public extension Api.contacts { - enum TopPeers: TypeConstructorDescription { - case topPeers(categories: [Api.TopPeerCategoryPeers], chats: [Api.Chat], users: [Api.User]) - case topPeersDisabled - case topPeersNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .topPeers(let categories, let chats, let users): - if boxed { - buffer.appendInt32(1891070632) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(categories.count)) - for item in categories { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .topPeersDisabled: - if boxed { - buffer.appendInt32(-1255369827) - } - - break - case .topPeersNotModified: - if boxed { - buffer.appendInt32(-567906571) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .topPeers(let categories, let chats, let users): - return ("topPeers", [("categories", categories as Any), ("chats", chats as Any), ("users", users as Any)]) - case .topPeersDisabled: - return ("topPeersDisabled", []) - case .topPeersNotModified: - return ("topPeersNotModified", []) - } - } - - public static func parse_topPeers(_ reader: BufferReader) -> TopPeers? { - var _1: [Api.TopPeerCategoryPeers]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeerCategoryPeers.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.TopPeers.topPeers(categories: _1!, chats: _2!, users: _3!) - } - else { - return nil - } - } - public static func parse_topPeersDisabled(_ reader: BufferReader) -> TopPeers? { - return Api.contacts.TopPeers.topPeersDisabled - } - public static func parse_topPeersNotModified(_ reader: BufferReader) -> TopPeers? { - return Api.contacts.TopPeers.topPeersNotModified - } - - } -} -public extension Api.fragment { - enum CollectibleInfo: TypeConstructorDescription { - case collectibleInfo(purchaseDate: Int32, currency: String, amount: Int64, cryptoCurrency: String, cryptoAmount: Int64, url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .collectibleInfo(let purchaseDate, let currency, let amount, let cryptoCurrency, let cryptoAmount, let url): - if boxed { - buffer.appendInt32(1857945489) - } - serializeInt32(purchaseDate, buffer: buffer, boxed: false) - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(amount, buffer: buffer, boxed: false) - serializeString(cryptoCurrency, buffer: buffer, boxed: false) - serializeInt64(cryptoAmount, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) + serializeString(emailPattern, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .collectibleInfo(let purchaseDate, let currency, let amount, let cryptoCurrency, let cryptoAmount, let url): - return ("collectibleInfo", [("purchaseDate", purchaseDate as Any), ("currency", currency as Any), ("amount", amount as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("url", url as Any)]) + case .passwordRecovery(let emailPattern): + return ("passwordRecovery", [("emailPattern", emailPattern as Any)]) } } - public static func parse_collectibleInfo(_ reader: BufferReader) -> CollectibleInfo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() - var _6: String? - _6 = parseString(reader) + public static func parse_passwordRecovery(_ reader: BufferReader) -> PasswordRecovery? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.fragment.CollectibleInfo.collectibleInfo(purchaseDate: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, url: _6!) + if _c1 { + return Api.auth.PasswordRecovery.passwordRecovery(emailPattern: _1!) } else { return nil @@ -1144,254 +506,408 @@ public extension Api.fragment { } } -public extension Api.help { - enum AppConfig: TypeConstructorDescription { - case appConfig(hash: Int32, config: Api.JSONValue) - case appConfigNotModified +public extension Api.auth { + enum SentCode: TypeConstructorDescription { + case sentCode(flags: Int32, type: Api.auth.SentCodeType, phoneCodeHash: String, nextType: Api.auth.CodeType?, timeout: Int32?) + case sentCodeSuccess(authorization: Api.auth.Authorization) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .appConfig(let hash, let config): + case .sentCode(let flags, let type, let phoneCodeHash, let nextType, let timeout): if boxed { - buffer.appendInt32(-585598930) + buffer.appendInt32(1577067778) } - serializeInt32(hash, buffer: buffer, boxed: false) - config.serialize(buffer, true) + serializeInt32(flags, buffer: buffer, boxed: false) + type.serialize(buffer, true) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {nextType!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)} break - case .appConfigNotModified: + case .sentCodeSuccess(let authorization): if boxed { - buffer.appendInt32(2094949405) + buffer.appendInt32(596704836) } - + authorization.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .appConfig(let hash, let config): - return ("appConfig", [("hash", hash as Any), ("config", config as Any)]) - case .appConfigNotModified: - return ("appConfigNotModified", []) + case .sentCode(let flags, let type, let phoneCodeHash, let nextType, let timeout): + return ("sentCode", [("flags", flags as Any), ("type", type as Any), ("phoneCodeHash", phoneCodeHash as Any), ("nextType", nextType as Any), ("timeout", timeout as Any)]) + case .sentCodeSuccess(let authorization): + return ("sentCodeSuccess", [("authorization", authorization as Any)]) } } - public static func parse_appConfig(_ reader: BufferReader) -> AppConfig? { + public static func parse_sentCode(_ reader: BufferReader) -> SentCode? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.JSONValue? + var _2: Api.auth.SentCodeType? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.JSONValue + _2 = Api.parse(reader, signature: signature) as? Api.auth.SentCodeType } + var _3: String? + _3 = parseString(reader) + var _4: Api.auth.CodeType? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.auth.CodeType + } } + var _5: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.AppConfig.appConfig(hash: _1!, config: _2!) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.auth.SentCode.sentCode(flags: _1!, type: _2!, phoneCodeHash: _3!, nextType: _4, timeout: _5) } else { return nil } } - public static func parse_appConfigNotModified(_ reader: BufferReader) -> AppConfig? { - return Api.help.AppConfig.appConfigNotModified + public static func parse_sentCodeSuccess(_ reader: BufferReader) -> SentCode? { + var _1: Api.auth.Authorization? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCode.sentCodeSuccess(authorization: _1!) + } + else { + return nil + } } } } -public extension Api.help { - enum AppUpdate: TypeConstructorDescription { - case appUpdate(flags: Int32, id: Int32, version: String, text: String, entities: [Api.MessageEntity], document: Api.Document?, url: String?, sticker: Api.Document?) - case noAppUpdate +public extension Api.auth { + enum SentCodeType: TypeConstructorDescription { + case sentCodeTypeApp(length: Int32) + case sentCodeTypeCall(length: Int32) + case sentCodeTypeEmailCode(flags: Int32, emailPattern: String, length: Int32, resetAvailablePeriod: Int32?, resetPendingDate: Int32?) + case sentCodeTypeFirebaseSms(flags: Int32, nonce: Buffer?, playIntegrityNonce: Buffer?, receipt: String?, pushTimeout: Int32?, length: Int32) + case sentCodeTypeFlashCall(pattern: String) + case sentCodeTypeFragmentSms(url: String, length: Int32) + case sentCodeTypeMissedCall(prefix: String, length: Int32) + case sentCodeTypeSetUpEmailRequired(flags: Int32) + case sentCodeTypeSms(length: Int32) + case sentCodeTypeSmsPhrase(flags: Int32, beginning: String?) + case sentCodeTypeSmsWord(flags: Int32, beginning: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .appUpdate(let flags, let id, let version, let text, let entities, let document, let url, let sticker): + case .sentCodeTypeApp(let length): + if boxed { + buffer.appendInt32(1035688326) + } + serializeInt32(length, buffer: buffer, boxed: false) + break + case .sentCodeTypeCall(let length): + if boxed { + buffer.appendInt32(1398007207) + } + serializeInt32(length, buffer: buffer, boxed: false) + break + case .sentCodeTypeEmailCode(let flags, let emailPattern, let length, let resetAvailablePeriod, let resetPendingDate): if boxed { - buffer.appendInt32(-860107216) + buffer.appendInt32(-196020837) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeString(version, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities.count)) - for item in entities { - item.serialize(buffer, true) + serializeString(emailPattern, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(resetAvailablePeriod!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(resetPendingDate!, buffer: buffer, boxed: false)} + break + case .sentCodeTypeFirebaseSms(let flags, let nonce, let playIntegrityNonce, let receipt, let pushTimeout, let length): + if boxed { + buffer.appendInt32(331943703) } - if Int(flags) & Int(1 << 1) != 0 {document!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {sticker!.serialize(buffer, true)} + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeBytes(nonce!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeBytes(playIntegrityNonce!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(receipt!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(pushTimeout!, buffer: buffer, boxed: false)} + serializeInt32(length, buffer: buffer, boxed: false) break - case .noAppUpdate: + case .sentCodeTypeFlashCall(let pattern): if boxed { - buffer.appendInt32(-1000708810) + buffer.appendInt32(-1425815847) } - + serializeString(pattern, buffer: buffer, boxed: false) + break + case .sentCodeTypeFragmentSms(let url, let length): + if boxed { + buffer.appendInt32(-648651719) + } + serializeString(url, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .sentCodeTypeMissedCall(let prefix, let length): + if boxed { + buffer.appendInt32(-2113903484) + } + serializeString(prefix, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .sentCodeTypeSetUpEmailRequired(let flags): + if boxed { + buffer.appendInt32(-1521934870) + } + serializeInt32(flags, buffer: buffer, boxed: false) + break + case .sentCodeTypeSms(let length): + if boxed { + buffer.appendInt32(-1073693790) + } + serializeInt32(length, buffer: buffer, boxed: false) + break + case .sentCodeTypeSmsPhrase(let flags, let beginning): + if boxed { + buffer.appendInt32(-1284008785) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(beginning!, buffer: buffer, boxed: false)} + break + case .sentCodeTypeSmsWord(let flags, let beginning): + if boxed { + buffer.appendInt32(-1542017919) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(beginning!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .appUpdate(let flags, let id, let version, let text, let entities, let document, let url, let sticker): - return ("appUpdate", [("flags", flags as Any), ("id", id as Any), ("version", version as Any), ("text", text as Any), ("entities", entities as Any), ("document", document as Any), ("url", url as Any), ("sticker", sticker as Any)]) - case .noAppUpdate: - return ("noAppUpdate", []) - } - } - - public static func parse_appUpdate(_ reader: BufferReader) -> AppUpdate? { + case .sentCodeTypeApp(let length): + return ("sentCodeTypeApp", [("length", length as Any)]) + case .sentCodeTypeCall(let length): + return ("sentCodeTypeCall", [("length", length as Any)]) + case .sentCodeTypeEmailCode(let flags, let emailPattern, let length, let resetAvailablePeriod, let resetPendingDate): + return ("sentCodeTypeEmailCode", [("flags", flags as Any), ("emailPattern", emailPattern as Any), ("length", length as Any), ("resetAvailablePeriod", resetAvailablePeriod as Any), ("resetPendingDate", resetPendingDate as Any)]) + case .sentCodeTypeFirebaseSms(let flags, let nonce, let playIntegrityNonce, let receipt, let pushTimeout, let length): + return ("sentCodeTypeFirebaseSms", [("flags", flags as Any), ("nonce", nonce as Any), ("playIntegrityNonce", playIntegrityNonce as Any), ("receipt", receipt as Any), ("pushTimeout", pushTimeout as Any), ("length", length as Any)]) + case .sentCodeTypeFlashCall(let pattern): + return ("sentCodeTypeFlashCall", [("pattern", pattern as Any)]) + case .sentCodeTypeFragmentSms(let url, let length): + return ("sentCodeTypeFragmentSms", [("url", url as Any), ("length", length as Any)]) + case .sentCodeTypeMissedCall(let prefix, let length): + return ("sentCodeTypeMissedCall", [("prefix", prefix as Any), ("length", length as Any)]) + case .sentCodeTypeSetUpEmailRequired(let flags): + return ("sentCodeTypeSetUpEmailRequired", [("flags", flags as Any)]) + case .sentCodeTypeSms(let length): + return ("sentCodeTypeSms", [("length", length as Any)]) + case .sentCodeTypeSmsPhrase(let flags, let beginning): + return ("sentCodeTypeSmsPhrase", [("flags", flags as Any), ("beginning", beginning as Any)]) + case .sentCodeTypeSmsWord(let flags, let beginning): + return ("sentCodeTypeSmsWord", [("flags", flags as Any), ("beginning", beginning as Any)]) + } + } + + public static func parse_sentCodeTypeApp(_ reader: BufferReader) -> SentCodeType? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCodeType.sentCodeTypeApp(length: _1!) } - var _6: Api.Document? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Document - } } - var _7: String? - if Int(_1!) & Int(1 << 2) != 0 {_7 = parseString(reader) } - var _8: Api.Document? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Document - } } + else { + return nil + } + } + public static func parse_sentCodeTypeCall(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCodeType.sentCodeTypeCall(length: _1!) + } + else { + return nil + } + } + public static func parse_sentCodeTypeEmailCode(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() } + var _5: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.help.AppUpdate.appUpdate(flags: _1!, id: _2!, version: _3!, text: _4!, entities: _5!, document: _6, url: _7, sticker: _8) + let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.auth.SentCodeType.sentCodeTypeEmailCode(flags: _1!, emailPattern: _2!, length: _3!, resetAvailablePeriod: _4, resetPendingDate: _5) } else { return nil } } - public static func parse_noAppUpdate(_ reader: BufferReader) -> AppUpdate? { - return Api.help.AppUpdate.noAppUpdate + public static func parse_sentCodeTypeFirebaseSms(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) } + var _3: Buffer? + if Int(_1!) & Int(1 << 2) != 0 {_3 = parseBytes(reader) } + var _4: String? + if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } + var _5: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, playIntegrityNonce: _3, receipt: _4, pushTimeout: _5, length: _6!) + } + else { + return nil + } } - - } -} -public extension Api.help { - enum CountriesList: TypeConstructorDescription { - case countriesList(countries: [Api.help.Country], hash: Int32) - case countriesListNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .countriesList(let countries, let hash): - if boxed { - buffer.appendInt32(-2016381538) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(countries.count)) - for item in countries { - item.serialize(buffer, true) - } - serializeInt32(hash, buffer: buffer, boxed: false) - break - case .countriesListNotModified: - if boxed { - buffer.appendInt32(-1815339214) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .countriesList(let countries, let hash): - return ("countriesList", [("countries", countries as Any), ("hash", hash as Any)]) - case .countriesListNotModified: - return ("countriesListNotModified", []) - } - } - - public static func parse_countriesList(_ reader: BufferReader) -> CountriesList? { - var _1: [Api.help.Country]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.Country.self) + public static func parse_sentCodeTypeFlashCall(_ reader: BufferReader) -> SentCodeType? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCodeType.sentCodeTypeFlashCall(pattern: _1!) + } + else { + return nil + } + } + public static func parse_sentCodeTypeFragmentSms(_ reader: BufferReader) -> SentCodeType? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.auth.SentCodeType.sentCodeTypeFragmentSms(url: _1!, length: _2!) } + else { + return nil + } + } + public static func parse_sentCodeTypeMissedCall(_ reader: BufferReader) -> SentCodeType? { + var _1: String? + _1 = parseString(reader) var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.help.CountriesList.countriesList(countries: _1!, hash: _2!) + return Api.auth.SentCodeType.sentCodeTypeMissedCall(prefix: _1!, length: _2!) + } + else { + return nil + } + } + public static func parse_sentCodeTypeSetUpEmailRequired(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCodeType.sentCodeTypeSetUpEmailRequired(flags: _1!) + } + else { + return nil + } + } + public static func parse_sentCodeTypeSms(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCodeType.sentCodeTypeSms(length: _1!) + } + else { + return nil + } + } + public static func parse_sentCodeTypeSmsPhrase(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.auth.SentCodeType.sentCodeTypeSmsPhrase(flags: _1!, beginning: _2) } else { return nil } } - public static func parse_countriesListNotModified(_ reader: BufferReader) -> CountriesList? { - return Api.help.CountriesList.countriesListNotModified + public static func parse_sentCodeTypeSmsWord(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.auth.SentCodeType.sentCodeTypeSmsWord(flags: _1!, beginning: _2) + } + else { + return nil + } } } } -public extension Api.help { - enum Country: TypeConstructorDescription { - case country(flags: Int32, iso2: String, defaultName: String, name: String?, countryCodes: [Api.help.CountryCode]) +public extension Api.bots { + enum BotInfo: TypeConstructorDescription { + case botInfo(name: String, about: String, description: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .country(let flags, let iso2, let defaultName, let name, let countryCodes): + case .botInfo(let name, let about, let description): if boxed { - buffer.appendInt32(-1014526429) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(iso2, buffer: buffer, boxed: false) - serializeString(defaultName, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(name!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(countryCodes.count)) - for item in countryCodes { - item.serialize(buffer, true) + buffer.appendInt32(-391678544) } + serializeString(name, buffer: buffer, boxed: false) + serializeString(about, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .country(let flags, let iso2, let defaultName, let name, let countryCodes): - return ("country", [("flags", flags as Any), ("iso2", iso2 as Any), ("defaultName", defaultName as Any), ("name", name as Any), ("countryCodes", countryCodes as Any)]) + case .botInfo(let name, let about, let description): + return ("botInfo", [("name", name as Any), ("about", about as Any), ("description", description as Any)]) } } - public static func parse_country(_ reader: BufferReader) -> Country? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_botInfo(_ reader: BufferReader) -> BotInfo? { + var _1: String? + _1 = parseString(reader) var _2: String? _2 = parseString(reader) var _3: String? _3 = parseString(reader) - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - var _5: [Api.help.CountryCode]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.CountryCode.self) - } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.help.Country.country(flags: _1!, iso2: _2!, defaultName: _3!, name: _4, countryCodes: _5!) + if _c1 && _c2 && _c3 { + return Api.bots.BotInfo.botInfo(name: _1!, about: _2!, description: _3!) } else { return nil @@ -1400,58 +916,60 @@ public extension Api.help { } } -public extension Api.help { - enum CountryCode: TypeConstructorDescription { - case countryCode(flags: Int32, countryCode: String, prefixes: [String]?, patterns: [String]?) +public extension Api.channels { + enum AdminLogResults: TypeConstructorDescription { + case adminLogResults(events: [Api.ChannelAdminLogEvent], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .countryCode(let flags, let countryCode, let prefixes, let patterns): + case .adminLogResults(let events, let chats, let users): if boxed { - buffer.appendInt32(1107543535) + buffer.appendInt32(-309659827) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(events.count)) + for item in events { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(countryCode, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(prefixes!.count)) - for item in prefixes! { - serializeString(item, buffer: buffer, boxed: false) - }} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(patterns!.count)) - for item in patterns! { - serializeString(item, buffer: buffer, boxed: false) - }} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .countryCode(let flags, let countryCode, let prefixes, let patterns): - return ("countryCode", [("flags", flags as Any), ("countryCode", countryCode as Any), ("prefixes", prefixes as Any), ("patterns", patterns as Any)]) + case .adminLogResults(let events, let chats, let users): + return ("adminLogResults", [("events", events as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_countryCode(_ reader: BufferReader) -> CountryCode? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: [String]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } } - var _4: [String]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } } + public static func parse_adminLogResults(_ reader: BufferReader) -> AdminLogResults? { + var _1: [Api.ChannelAdminLogEvent]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChannelAdminLogEvent.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.help.CountryCode.countryCode(flags: _1!, countryCode: _2!, prefixes: _3, patterns: _4) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.channels.AdminLogResults.adminLogResults(events: _1!, chats: _2!, users: _3!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 79877efba2f..04914ae0cb9 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1,118 +1,92 @@ -public extension Api.help { - enum DeepLinkInfo: TypeConstructorDescription { - case deepLinkInfo(flags: Int32, message: String, entities: [Api.MessageEntity]?) - case deepLinkInfoEmpty +public extension Api.channels { + enum ChannelParticipant: TypeConstructorDescription { + case channelParticipant(participant: Api.ChannelParticipant, chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .deepLinkInfo(let flags, let message, let entities): + case .channelParticipant(let participant, let chats, let users): if boxed { - buffer.appendInt32(1783556146) + buffer.appendInt32(-541588713) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { + participant.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) - }} - break - case .deepLinkInfoEmpty: - if boxed { - buffer.appendInt32(1722786150) } - break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .deepLinkInfo(let flags, let message, let entities): - return ("deepLinkInfo", [("flags", flags as Any), ("message", message as Any), ("entities", entities as Any)]) - case .deepLinkInfoEmpty: - return ("deepLinkInfoEmpty", []) + case .channelParticipant(let participant, let chats, let users): + return ("channelParticipant", [("participant", participant as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_deepLinkInfo(_ reader: BufferReader) -> DeepLinkInfo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } + public static func parse_channelParticipant(_ reader: BufferReader) -> ChannelParticipant? { + var _1: Api.ChannelParticipant? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.help.DeepLinkInfo.deepLinkInfo(flags: _1!, message: _2!, entities: _3) + return Api.channels.ChannelParticipant.channelParticipant(participant: _1!, chats: _2!, users: _3!) } else { return nil } } - public static func parse_deepLinkInfoEmpty(_ reader: BufferReader) -> DeepLinkInfo? { - return Api.help.DeepLinkInfo.deepLinkInfoEmpty - } } } -public extension Api.help { - enum InviteText: TypeConstructorDescription { - case inviteText(message: String) +public extension Api.channels { + enum ChannelParticipants: TypeConstructorDescription { + case channelParticipants(count: Int32, participants: [Api.ChannelParticipant], chats: [Api.Chat], users: [Api.User]) + case channelParticipantsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .inviteText(let message): + case .channelParticipants(let count, let participants, let chats, let users): if boxed { - buffer.appendInt32(415997816) + buffer.appendInt32(-1699676497) } - serializeString(message, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inviteText(let message): - return ("inviteText", [("message", message as Any)]) - } - } - - public static func parse_inviteText(_ reader: BufferReader) -> InviteText? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.help.InviteText.inviteText(message: _1!) - } - else { - return nil - } - } - - } -} -public extension Api.help { - enum PassportConfig: TypeConstructorDescription { - case passportConfig(hash: Int32, countriesLangs: Api.DataJSON) - case passportConfigNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .passportConfig(let hash, let countriesLangs): - if boxed { - buffer.appendInt32(-1600596305) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(participants.count)) + for item in participants { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeInt32(hash, buffer: buffer, boxed: false) - countriesLangs.serialize(buffer, true) break - case .passportConfigNotModified: + case .channelParticipantsNotModified: if boxed { - buffer.appendInt32(-1078332329) + buffer.appendInt32(-266911767) } break @@ -121,130 +95,69 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .passportConfig(let hash, let countriesLangs): - return ("passportConfig", [("hash", hash as Any), ("countriesLangs", countriesLangs as Any)]) - case .passportConfigNotModified: - return ("passportConfigNotModified", []) + case .channelParticipants(let count, let participants, let chats, let users): + return ("channelParticipants", [("count", count as Any), ("participants", participants as Any), ("chats", chats as Any), ("users", users as Any)]) + case .channelParticipantsNotModified: + return ("channelParticipantsNotModified", []) } } - public static func parse_passportConfig(_ reader: BufferReader) -> PassportConfig? { + public static func parse_channelParticipants(_ reader: BufferReader) -> ChannelParticipants? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.DataJSON? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + var _2: [Api.ChannelParticipant]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChannelParticipant.self) } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.PassportConfig.passportConfig(hash: _1!, countriesLangs: _2!) + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - else { - return nil + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - } - public static func parse_passportConfigNotModified(_ reader: BufferReader) -> PassportConfig? { - return Api.help.PassportConfig.passportConfigNotModified - } - - } -} -public extension Api.help { - enum PeerColorOption: TypeConstructorDescription { - case peerColorOption(flags: Int32, colorId: Int32, colors: Api.help.PeerColorSet?, darkColors: Api.help.PeerColorSet?, channelMinLevel: Int32?, groupMinLevel: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel, let groupMinLevel): - if boxed { - buffer.appendInt32(-1377014082) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(colorId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {colors!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {darkColors!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(channelMinLevel!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(groupMinLevel!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel, let groupMinLevel): - return ("peerColorOption", [("flags", flags as Any), ("colorId", colorId as Any), ("colors", colors as Any), ("darkColors", darkColors as Any), ("channelMinLevel", channelMinLevel as Any), ("groupMinLevel", groupMinLevel as Any)]) - } - } - - public static func parse_peerColorOption(_ reader: BufferReader) -> PeerColorOption? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.help.PeerColorSet? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.help.PeerColorSet - } } - var _4: Api.help.PeerColorSet? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.help.PeerColorSet - } } - var _5: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() } - var _6: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_6 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.help.PeerColorOption.peerColorOption(flags: _1!, colorId: _2!, colors: _3, darkColors: _4, channelMinLevel: _5, groupMinLevel: _6) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.channels.ChannelParticipants.channelParticipants(count: _1!, participants: _2!, chats: _3!, users: _4!) } else { return nil } } + public static func parse_channelParticipantsNotModified(_ reader: BufferReader) -> ChannelParticipants? { + return Api.channels.ChannelParticipants.channelParticipantsNotModified + } } } -public extension Api.help { - enum PeerColorSet: TypeConstructorDescription { - case peerColorProfileSet(paletteColors: [Int32], bgColors: [Int32], storyColors: [Int32]) - case peerColorSet(colors: [Int32]) +public extension Api.channels { + enum SendAsPeers: TypeConstructorDescription { + case sendAsPeers(peers: [Api.SendAsPeer], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerColorProfileSet(let paletteColors, let bgColors, let storyColors): + case .sendAsPeers(let peers, let chats, let users): if boxed { - buffer.appendInt32(1987928555) + buffer.appendInt32(-191450938) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(paletteColors.count)) - for item in paletteColors { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(bgColors.count)) - for item in bgColors { - serializeInt32(item, buffer: buffer, boxed: false) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(storyColors.count)) - for item in storyColors { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .peerColorSet(let colors): - if boxed { - buffer.appendInt32(639736408) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(colors.count)) - for item in colors { - serializeInt32(item, buffer: buffer, boxed: false) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } break } @@ -252,44 +165,29 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .peerColorProfileSet(let paletteColors, let bgColors, let storyColors): - return ("peerColorProfileSet", [("paletteColors", paletteColors as Any), ("bgColors", bgColors as Any), ("storyColors", storyColors as Any)]) - case .peerColorSet(let colors): - return ("peerColorSet", [("colors", colors as Any)]) + case .sendAsPeers(let peers, let chats, let users): + return ("sendAsPeers", [("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_peerColorProfileSet(_ reader: BufferReader) -> PeerColorSet? { - var _1: [Int32]? + public static func parse_sendAsPeers(_ reader: BufferReader) -> SendAsPeers? { + var _1: [Api.SendAsPeer]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SendAsPeer.self) } - var _2: [Int32]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _3: [Int32]? + var _3: [Api.User]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.help.PeerColorSet.peerColorProfileSet(paletteColors: _1!, bgColors: _2!, storyColors: _3!) - } - else { - return nil - } - } - public static func parse_peerColorSet(_ reader: BufferReader) -> PeerColorSet? { - var _1: [Int32]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.help.PeerColorSet.peerColorSet(colors: _1!) + return Api.channels.SendAsPeers.sendAsPeers(peers: _1!, chats: _2!, users: _3!) } else { return nil @@ -298,27 +196,34 @@ public extension Api.help { } } -public extension Api.help { - enum PeerColors: TypeConstructorDescription { - case peerColors(hash: Int32, colors: [Api.help.PeerColorOption]) - case peerColorsNotModified +public extension Api.channels { + enum SponsoredMessageReportResult: TypeConstructorDescription { + case sponsoredMessageReportResultAdsHidden + case sponsoredMessageReportResultChooseOption(title: String, options: [Api.SponsoredMessageReportOption]) + case sponsoredMessageReportResultReported public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerColors(let hash, let colors): + case .sponsoredMessageReportResultAdsHidden: if boxed { - buffer.appendInt32(16313608) + buffer.appendInt32(1044107055) } - serializeInt32(hash, buffer: buffer, boxed: false) + + break + case .sponsoredMessageReportResultChooseOption(let title, let options): + if boxed { + buffer.appendInt32(-2073059774) + } + serializeString(title, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(colors.count)) - for item in colors { + buffer.appendInt32(Int32(options.count)) + for item in options { item.serialize(buffer, true) } break - case .peerColorsNotModified: + case .sponsoredMessageReportResultReported: if boxed { - buffer.appendInt32(732034510) + buffer.appendInt32(-1384544183) } break @@ -327,64 +232,88 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .peerColors(let hash, let colors): - return ("peerColors", [("hash", hash as Any), ("colors", colors as Any)]) - case .peerColorsNotModified: - return ("peerColorsNotModified", []) + case .sponsoredMessageReportResultAdsHidden: + return ("sponsoredMessageReportResultAdsHidden", []) + case .sponsoredMessageReportResultChooseOption(let title, let options): + return ("sponsoredMessageReportResultChooseOption", [("title", title as Any), ("options", options as Any)]) + case .sponsoredMessageReportResultReported: + return ("sponsoredMessageReportResultReported", []) } } - public static func parse_peerColors(_ reader: BufferReader) -> PeerColors? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.help.PeerColorOption]? + public static func parse_sponsoredMessageReportResultAdsHidden(_ reader: BufferReader) -> SponsoredMessageReportResult? { + return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultAdsHidden + } + public static func parse_sponsoredMessageReportResultChooseOption(_ reader: BufferReader) -> SponsoredMessageReportResult? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.SponsoredMessageReportOption]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.PeerColorOption.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessageReportOption.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.help.PeerColors.peerColors(hash: _1!, colors: _2!) + return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultChooseOption(title: _1!, options: _2!) } else { return nil } } - public static func parse_peerColorsNotModified(_ reader: BufferReader) -> PeerColors? { - return Api.help.PeerColors.peerColorsNotModified + public static func parse_sponsoredMessageReportResultReported(_ reader: BufferReader) -> SponsoredMessageReportResult? { + return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultReported } } } -public extension Api.help { - enum PremiumPromo: TypeConstructorDescription { - case premiumPromo(statusText: String, statusEntities: [Api.MessageEntity], videoSections: [String], videos: [Api.Document], periodOptions: [Api.PremiumSubscriptionOption], users: [Api.User]) +public extension Api.chatlists { + enum ChatlistInvite: TypeConstructorDescription { + case chatlistInvite(flags: Int32, title: String, emoticon: String?, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) + case chatlistInviteAlready(filterId: Int32, missingPeers: [Api.Peer], alreadyPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users): + case .chatlistInvite(let flags, let title, let emoticon, let peers, let chats, let users): if boxed { - buffer.appendInt32(1395946908) + buffer.appendInt32(500007837) } - serializeString(statusText, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) - buffer.appendInt32(Int32(statusEntities.count)) - for item in statusEntities { + buffer.appendInt32(Int32(peers.count)) + for item in peers { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(videoSections.count)) - for item in videoSections { - serializeString(item, buffer: buffer, boxed: false) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(videos.count)) - for item in videos { + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .chatlistInviteAlready(let filterId, let missingPeers, let alreadyPeers, let chats, let users): + if boxed { + buffer.appendInt32(-91752871) + } + serializeInt32(filterId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(missingPeers.count)) + for item in missingPeers { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(periodOptions.count)) - for item in periodOptions { + buffer.appendInt32(Int32(alreadyPeers.count)) + for item in alreadyPeers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -398,42 +327,71 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users): - return ("premiumPromo", [("statusText", statusText as Any), ("statusEntities", statusEntities as Any), ("videoSections", videoSections as Any), ("videos", videos as Any), ("periodOptions", periodOptions as Any), ("users", users as Any)]) + case .chatlistInvite(let flags, let title, let emoticon, let peers, let chats, let users): + return ("chatlistInvite", [("flags", flags as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)]) + case .chatlistInviteAlready(let filterId, let missingPeers, let alreadyPeers, let chats, let users): + return ("chatlistInviteAlready", [("filterId", filterId as Any), ("missingPeers", missingPeers as Any), ("alreadyPeers", alreadyPeers as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_premiumPromo(_ reader: BufferReader) -> PremiumPromo? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.MessageEntity]? + public static func parse_chatlistInvite(_ reader: BufferReader) -> ChatlistInvite? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: [Api.Peer]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } - var _3: [String]? + var _5: [Api.Chat]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.chatlists.ChatlistInvite.chatlistInvite(flags: _1!, title: _2!, emoticon: _3, peers: _4!, chats: _5!, users: _6!) + } + else { + return nil } - var _4: [Api.Document]? + } + public static func parse_chatlistInviteAlready(_ reader: BufferReader) -> ChatlistInvite? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Peer]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } - var _5: [Api.PremiumSubscriptionOption]? + var _3: [Api.Peer]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumSubscriptionOption.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } - var _6: [Api.User]? + var _4: [Api.Chat]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!) + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.chatlists.ChatlistInvite.chatlistInviteAlready(filterId: _1!, missingPeers: _2!, alreadyPeers: _3!, chats: _4!, users: _5!) } else { return nil @@ -442,20 +400,21 @@ public extension Api.help { } } -public extension Api.help { - enum PromoData: TypeConstructorDescription { - case promoData(flags: Int32, expires: Int32, peer: Api.Peer, chats: [Api.Chat], users: [Api.User], psaType: String?, psaMessage: String?) - case promoDataEmpty(expires: Int32) +public extension Api.chatlists { + enum ChatlistUpdates: TypeConstructorDescription { + case chatlistUpdates(missingPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .promoData(let flags, let expires, let peer, let chats, let users, let psaType, let psaMessage): + case .chatlistUpdates(let missingPeers, let chats, let users): if boxed { - buffer.appendInt32(-1942390465) + buffer.appendInt32(-1816295539) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(missingPeers.count)) + for item in missingPeers { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(expires, buffer: buffer, boxed: false) - peer.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -466,68 +425,79 @@ public extension Api.help { for item in users { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 1) != 0 {serializeString(psaType!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(psaMessage!, buffer: buffer, boxed: false)} - break - case .promoDataEmpty(let expires): - if boxed { - buffer.appendInt32(-1728664459) - } - serializeInt32(expires, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .promoData(let flags, let expires, let peer, let chats, let users, let psaType, let psaMessage): - return ("promoData", [("flags", flags as Any), ("expires", expires as Any), ("peer", peer as Any), ("chats", chats as Any), ("users", users as Any), ("psaType", psaType as Any), ("psaMessage", psaMessage as Any)]) - case .promoDataEmpty(let expires): - return ("promoDataEmpty", [("expires", expires as Any)]) + case .chatlistUpdates(let missingPeers, let chats, let users): + return ("chatlistUpdates", [("missingPeers", missingPeers as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_promoData(_ reader: BufferReader) -> PromoData? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Peer? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer + public static func parse_chatlistUpdates(_ reader: BufferReader) -> ChatlistUpdates? { + var _1: [Api.Peer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } - var _4: [Api.Chat]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _5: [Api.User]? + var _3: [Api.User]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _6: String? - if Int(_1!) & Int(1 << 1) != 0 {_6 = parseString(reader) } - var _7: String? - if Int(_1!) & Int(1 << 2) != 0 {_7 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.help.PromoData.promoData(flags: _1!, expires: _2!, peer: _3!, chats: _4!, users: _5!, psaType: _6, psaMessage: _7) + if _c1 && _c2 && _c3 { + return Api.chatlists.ChatlistUpdates.chatlistUpdates(missingPeers: _1!, chats: _2!, users: _3!) } else { return nil } } - public static func parse_promoDataEmpty(_ reader: BufferReader) -> PromoData? { - var _1: Int32? - _1 = reader.readInt32() + + } +} +public extension Api.chatlists { + enum ExportedChatlistInvite: TypeConstructorDescription { + case exportedChatlistInvite(filter: Api.DialogFilter, invite: Api.ExportedChatlistInvite) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedChatlistInvite(let filter, let invite): + if boxed { + buffer.appendInt32(283567014) + } + filter.serialize(buffer, true) + invite.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .exportedChatlistInvite(let filter, let invite): + return ("exportedChatlistInvite", [("filter", filter as Any), ("invite", invite as Any)]) + } + } + + public static func parse_exportedChatlistInvite(_ reader: BufferReader) -> ExportedChatlistInvite? { + var _1: Api.DialogFilter? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.DialogFilter + } + var _2: Api.ExportedChatlistInvite? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ExportedChatlistInvite + } let _c1 = _1 != nil - if _c1 { - return Api.help.PromoData.promoDataEmpty(expires: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.chatlists.ExportedChatlistInvite.exportedChatlistInvite(filter: _1!, invite: _2!) } else { return nil @@ -536,19 +506,19 @@ public extension Api.help { } } -public extension Api.help { - enum RecentMeUrls: TypeConstructorDescription { - case recentMeUrls(urls: [Api.RecentMeUrl], chats: [Api.Chat], users: [Api.User]) +public extension Api.chatlists { + enum ExportedInvites: TypeConstructorDescription { + case exportedInvites(invites: [Api.ExportedChatlistInvite], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .recentMeUrls(let urls, let chats, let users): + case .exportedInvites(let invites, let chats, let users): if boxed { - buffer.appendInt32(235081943) + buffer.appendInt32(279670215) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(urls.count)) - for item in urls { + buffer.appendInt32(Int32(invites.count)) + for item in invites { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -567,15 +537,15 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .recentMeUrls(let urls, let chats, let users): - return ("recentMeUrls", [("urls", urls as Any), ("chats", chats as Any), ("users", users as Any)]) + case .exportedInvites(let invites, let chats, let users): + return ("exportedInvites", [("invites", invites as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_recentMeUrls(_ reader: BufferReader) -> RecentMeUrls? { - var _1: [Api.RecentMeUrl]? + public static func parse_exportedInvites(_ reader: BufferReader) -> ExportedInvites? { + var _1: [Api.ExportedChatlistInvite]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RecentMeUrl.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedChatlistInvite.self) } var _2: [Api.Chat]? if let _ = reader.readInt32() { @@ -589,7 +559,7 @@ public extension Api.help { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.help.RecentMeUrls.recentMeUrls(urls: _1!, chats: _2!, users: _3!) + return Api.chatlists.ExportedInvites.exportedInvites(invites: _1!, chats: _2!, users: _3!) } else { return nil @@ -598,40 +568,110 @@ public extension Api.help { } } -public extension Api.help { - enum Support: TypeConstructorDescription { - case support(phoneNumber: String, user: Api.User) +public extension Api.contacts { + enum Blocked: TypeConstructorDescription { + case blocked(blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User]) + case blockedSlice(count: Int32, blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .support(let phoneNumber, let user): + case .blocked(let blocked, let chats, let users): if boxed { - buffer.appendInt32(398898678) + buffer.appendInt32(182326673) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(blocked.count)) + for item in blocked { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .blockedSlice(let count, let blocked, let chats, let users): + if boxed { + buffer.appendInt32(-513392236) + } + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(blocked.count)) + for item in blocked { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeString(phoneNumber, buffer: buffer, boxed: false) - user.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .support(let phoneNumber, let user): - return ("support", [("phoneNumber", phoneNumber as Any), ("user", user as Any)]) + case .blocked(let blocked, let chats, let users): + return ("blocked", [("blocked", blocked as Any), ("chats", chats as Any), ("users", users as Any)]) + case .blockedSlice(let count, let blocked, let chats, let users): + return ("blockedSlice", [("count", count as Any), ("blocked", blocked as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_support(_ reader: BufferReader) -> Support? { - var _1: String? - _1 = parseString(reader) - var _2: Api.User? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.User + public static func parse_blocked(_ reader: BufferReader) -> Blocked? { + var _1: [Api.PeerBlocked]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.Support.support(phoneNumber: _1!, user: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.contacts.Blocked.blocked(blocked: _1!, chats: _2!, users: _3!) + } + else { + return nil + } + } + public static func parse_blockedSlice(_ reader: BufferReader) -> Blocked? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.PeerBlocked]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.contacts.Blocked.blockedSlice(count: _1!, blocked: _2!, chats: _3!, users: _4!) } else { return nil @@ -640,34 +680,50 @@ public extension Api.help { } } -public extension Api.help { - enum SupportName: TypeConstructorDescription { - case supportName(name: String) +public extension Api.contacts { + enum ContactBirthdays: TypeConstructorDescription { + case contactBirthdays(contacts: [Api.ContactBirthday], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .supportName(let name): + case .contactBirthdays(let contacts, let users): if boxed { - buffer.appendInt32(-1945767479) + buffer.appendInt32(290452237) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(contacts.count)) + for item in contacts { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeString(name, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .supportName(let name): - return ("supportName", [("name", name as Any)]) + case .contactBirthdays(let contacts, let users): + return ("contactBirthdays", [("contacts", contacts as Any), ("users", users as Any)]) } } - public static func parse_supportName(_ reader: BufferReader) -> SupportName? { - var _1: String? - _1 = parseString(reader) + public static func parse_contactBirthdays(_ reader: BufferReader) -> ContactBirthdays? { + var _1: [Api.ContactBirthday]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ContactBirthday.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil - if _c1 { - return Api.help.SupportName.supportName(name: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.contacts.ContactBirthdays.contactBirthdays(contacts: _1!, users: _2!) } else { return nil @@ -676,120 +732,138 @@ public extension Api.help { } } -public extension Api.help { - enum TermsOfService: TypeConstructorDescription { - case termsOfService(flags: Int32, id: Api.DataJSON, text: String, entities: [Api.MessageEntity], minAgeConfirm: Int32?) +public extension Api.contacts { + enum Contacts: TypeConstructorDescription { + case contacts(contacts: [Api.Contact], savedCount: Int32, users: [Api.User]) + case contactsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .termsOfService(let flags, let id, let text, let entities, let minAgeConfirm): + case .contacts(let contacts, let savedCount, let users): if boxed { - buffer.appendInt32(2013922064) + buffer.appendInt32(-353862078) } - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - serializeString(text, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities.count)) - for item in entities { + buffer.appendInt32(Int32(contacts.count)) + for item in contacts { + item.serialize(buffer, true) + } + serializeInt32(savedCount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(minAgeConfirm!, buffer: buffer, boxed: false)} + break + case .contactsNotModified: + if boxed { + buffer.appendInt32(-1219778094) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .termsOfService(let flags, let id, let text, let entities, let minAgeConfirm): - return ("termsOfService", [("flags", flags as Any), ("id", id as Any), ("text", text as Any), ("entities", entities as Any), ("minAgeConfirm", minAgeConfirm as Any)]) + case .contacts(let contacts, let savedCount, let users): + return ("contacts", [("contacts", contacts as Any), ("savedCount", savedCount as Any), ("users", users as Any)]) + case .contactsNotModified: + return ("contactsNotModified", []) } } - public static func parse_termsOfService(_ reader: BufferReader) -> TermsOfService? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.DataJSON? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + public static func parse_contacts(_ reader: BufferReader) -> Contacts? { + var _1: [Api.Contact]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Contact.self) } - var _3: String? - _3 = parseString(reader) - var _4: [Api.MessageEntity]? + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.User]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.help.TermsOfService.termsOfService(flags: _1!, id: _2!, text: _3!, entities: _4!, minAgeConfirm: _5) + if _c1 && _c2 && _c3 { + return Api.contacts.Contacts.contacts(contacts: _1!, savedCount: _2!, users: _3!) } else { return nil } } + public static func parse_contactsNotModified(_ reader: BufferReader) -> Contacts? { + return Api.contacts.Contacts.contactsNotModified + } } } -public extension Api.help { - enum TermsOfServiceUpdate: TypeConstructorDescription { - case termsOfServiceUpdate(expires: Int32, termsOfService: Api.help.TermsOfService) - case termsOfServiceUpdateEmpty(expires: Int32) +public extension Api.contacts { + enum Found: TypeConstructorDescription { + case found(myResults: [Api.Peer], results: [Api.Peer], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .termsOfServiceUpdate(let expires, let termsOfService): + case .found(let myResults, let results, let chats, let users): if boxed { - buffer.appendInt32(686618977) + buffer.appendInt32(-1290580579) } - serializeInt32(expires, buffer: buffer, boxed: false) - termsOfService.serialize(buffer, true) - break - case .termsOfServiceUpdateEmpty(let expires): - if boxed { - buffer.appendInt32(-483352705) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(myResults.count)) + for item in myResults { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(results.count)) + for item in results { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeInt32(expires, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .termsOfServiceUpdate(let expires, let termsOfService): - return ("termsOfServiceUpdate", [("expires", expires as Any), ("termsOfService", termsOfService as Any)]) - case .termsOfServiceUpdateEmpty(let expires): - return ("termsOfServiceUpdateEmpty", [("expires", expires as Any)]) + case .found(let myResults, let results, let chats, let users): + return ("found", [("myResults", myResults as Any), ("results", results as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_termsOfServiceUpdate(_ reader: BufferReader) -> TermsOfServiceUpdate? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.help.TermsOfService? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.help.TermsOfService + public static func parse_found(_ reader: BufferReader) -> Found? { + var _1: [Api.Peer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.TermsOfServiceUpdate.termsOfServiceUpdate(expires: _1!, termsOfService: _2!) + var _2: [Api.Peer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } - else { - return nil + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - } - public static func parse_termsOfServiceUpdateEmpty(_ reader: BufferReader) -> TermsOfServiceUpdate? { - var _1: Int32? - _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.help.TermsOfServiceUpdate.termsOfServiceUpdateEmpty(expires: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.contacts.Found.found(myResults: _1!, results: _2!, chats: _3!, users: _4!) } else { return nil @@ -798,220 +872,270 @@ public extension Api.help { } } -public extension Api.help { - enum TimezonesList: TypeConstructorDescription { - case timezonesList(timezones: [Api.Timezone], hash: Int32) - case timezonesListNotModified +public extension Api.contacts { + enum ImportedContacts: TypeConstructorDescription { + case importedContacts(imported: [Api.ImportedContact], popularInvites: [Api.PopularContact], retryContacts: [Int64], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .timezonesList(let timezones, let hash): + case .importedContacts(let imported, let popularInvites, let retryContacts, let users): if boxed { - buffer.appendInt32(2071260529) + buffer.appendInt32(2010127419) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(timezones.count)) - for item in timezones { + buffer.appendInt32(Int32(imported.count)) + for item in imported { item.serialize(buffer, true) } - serializeInt32(hash, buffer: buffer, boxed: false) - break - case .timezonesListNotModified: - if boxed { - buffer.appendInt32(-1761146676) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(popularInvites.count)) + for item in popularInvites { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(retryContacts.count)) + for item in retryContacts { + serializeInt64(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .timezonesList(let timezones, let hash): - return ("timezonesList", [("timezones", timezones as Any), ("hash", hash as Any)]) - case .timezonesListNotModified: - return ("timezonesListNotModified", []) + case .importedContacts(let imported, let popularInvites, let retryContacts, let users): + return ("importedContacts", [("imported", imported as Any), ("popularInvites", popularInvites as Any), ("retryContacts", retryContacts as Any), ("users", users as Any)]) } } - public static func parse_timezonesList(_ reader: BufferReader) -> TimezonesList? { - var _1: [Api.Timezone]? + public static func parse_importedContacts(_ reader: BufferReader) -> ImportedContacts? { + var _1: [Api.ImportedContact]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Timezone.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ImportedContact.self) + } + var _2: [Api.PopularContact]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PopularContact.self) + } + var _3: [Int64]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _2: Int32? - _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.TimezonesList.timezonesList(timezones: _1!, hash: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.contacts.ImportedContacts.importedContacts(imported: _1!, popularInvites: _2!, retryContacts: _3!, users: _4!) } else { return nil } } - public static func parse_timezonesListNotModified(_ reader: BufferReader) -> TimezonesList? { - return Api.help.TimezonesList.timezonesListNotModified - } } } -public extension Api.help { - enum UserInfo: TypeConstructorDescription { - case userInfo(message: String, entities: [Api.MessageEntity], author: String, date: Int32) - case userInfoEmpty +public extension Api.contacts { + enum ResolvedPeer: TypeConstructorDescription { + case resolvedPeer(peer: Api.Peer, chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .userInfo(let message, let entities, let author, let date): + case .resolvedPeer(let peer, let chats, let users): if boxed { - buffer.appendInt32(32192344) + buffer.appendInt32(2131196633) } - serializeString(message, buffer: buffer, boxed: false) + peer.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities.count)) - for item in entities { + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } - serializeString(author, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - break - case .userInfoEmpty: - if boxed { - buffer.appendInt32(-206688531) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .userInfo(let message, let entities, let author, let date): - return ("userInfo", [("message", message as Any), ("entities", entities as Any), ("author", author as Any), ("date", date as Any)]) - case .userInfoEmpty: - return ("userInfoEmpty", []) + case .resolvedPeer(let peer, let chats, let users): + return ("resolvedPeer", [("peer", peer as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_userInfo(_ reader: BufferReader) -> UserInfo? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.MessageEntity]? + public static func parse_resolvedPeer(_ reader: BufferReader) -> ResolvedPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: [Api.Chat]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _3: String? - _3 = parseString(reader) - var _4: Int32? - _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.help.UserInfo.userInfo(message: _1!, entities: _2!, author: _3!, date: _4!) + if _c1 && _c2 && _c3 { + return Api.contacts.ResolvedPeer.resolvedPeer(peer: _1!, chats: _2!, users: _3!) } else { return nil } } - public static func parse_userInfoEmpty(_ reader: BufferReader) -> UserInfo? { - return Api.help.UserInfo.userInfoEmpty - } } } -public extension Api.messages { - enum AffectedFoundMessages: TypeConstructorDescription { - case affectedFoundMessages(pts: Int32, ptsCount: Int32, offset: Int32, messages: [Int32]) +public extension Api.contacts { + enum TopPeers: TypeConstructorDescription { + case topPeers(categories: [Api.TopPeerCategoryPeers], chats: [Api.Chat], users: [Api.User]) + case topPeersDisabled + case topPeersNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .affectedFoundMessages(let pts, let ptsCount, let offset, let messages): + case .topPeers(let categories, let chats, let users): if boxed { - buffer.appendInt32(-275956116) + buffer.appendInt32(1891070632) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(categories.count)) + for item in categories { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .topPeersDisabled: + if boxed { + buffer.appendInt32(-1255369827) + } + + break + case .topPeersNotModified: + if boxed { + buffer.appendInt32(-567906571) } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .affectedFoundMessages(let pts, let ptsCount, let offset, let messages): - return ("affectedFoundMessages", [("pts", pts as Any), ("ptsCount", ptsCount as Any), ("offset", offset as Any), ("messages", messages as Any)]) + case .topPeers(let categories, let chats, let users): + return ("topPeers", [("categories", categories as Any), ("chats", chats as Any), ("users", users as Any)]) + case .topPeersDisabled: + return ("topPeersDisabled", []) + case .topPeersNotModified: + return ("topPeersNotModified", []) } } - public static func parse_affectedFoundMessages(_ reader: BufferReader) -> AffectedFoundMessages? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Int32]? + public static func parse_topPeers(_ reader: BufferReader) -> TopPeers? { + var _1: [Api.TopPeerCategoryPeers]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeerCategoryPeers.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.AffectedFoundMessages.affectedFoundMessages(pts: _1!, ptsCount: _2!, offset: _3!, messages: _4!) + if _c1 && _c2 && _c3 { + return Api.contacts.TopPeers.topPeers(categories: _1!, chats: _2!, users: _3!) } else { return nil } } + public static func parse_topPeersDisabled(_ reader: BufferReader) -> TopPeers? { + return Api.contacts.TopPeers.topPeersDisabled + } + public static func parse_topPeersNotModified(_ reader: BufferReader) -> TopPeers? { + return Api.contacts.TopPeers.topPeersNotModified + } } } -public extension Api.messages { - enum AffectedHistory: TypeConstructorDescription { - case affectedHistory(pts: Int32, ptsCount: Int32, offset: Int32) +public extension Api.fragment { + enum CollectibleInfo: TypeConstructorDescription { + case collectibleInfo(purchaseDate: Int32, currency: String, amount: Int64, cryptoCurrency: String, cryptoAmount: Int64, url: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .affectedHistory(let pts, let ptsCount, let offset): + case .collectibleInfo(let purchaseDate, let currency, let amount, let cryptoCurrency, let cryptoAmount, let url): if boxed { - buffer.appendInt32(-1269012015) + buffer.appendInt32(1857945489) } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(purchaseDate, buffer: buffer, boxed: false) + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + serializeString(cryptoCurrency, buffer: buffer, boxed: false) + serializeInt64(cryptoAmount, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .affectedHistory(let pts, let ptsCount, let offset): - return ("affectedHistory", [("pts", pts as Any), ("ptsCount", ptsCount as Any), ("offset", offset as Any)]) + case .collectibleInfo(let purchaseDate, let currency, let amount, let cryptoCurrency, let cryptoAmount, let url): + return ("collectibleInfo", [("purchaseDate", purchaseDate as Any), ("currency", currency as Any), ("amount", amount as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("url", url as Any)]) } } - public static func parse_affectedHistory(_ reader: BufferReader) -> AffectedHistory? { + public static func parse_collectibleInfo(_ reader: BufferReader) -> CollectibleInfo? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + var _6: String? + _6 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.AffectedHistory.affectedHistory(pts: _1!, ptsCount: _2!, offset: _3!) + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.fragment.CollectibleInfo.collectibleInfo(purchaseDate: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, url: _6!) } else { return nil @@ -1020,67 +1144,87 @@ public extension Api.messages { } } -public extension Api.messages { - enum AffectedMessages: TypeConstructorDescription { - case affectedMessages(pts: Int32, ptsCount: Int32) +public extension Api.help { + enum AppConfig: TypeConstructorDescription { + case appConfig(hash: Int32, config: Api.JSONValue) + case appConfigNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .affectedMessages(let pts, let ptsCount): + case .appConfig(let hash, let config): + if boxed { + buffer.appendInt32(-585598930) + } + serializeInt32(hash, buffer: buffer, boxed: false) + config.serialize(buffer, true) + break + case .appConfigNotModified: if boxed { - buffer.appendInt32(-2066640507) + buffer.appendInt32(2094949405) } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .affectedMessages(let pts, let ptsCount): - return ("affectedMessages", [("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .appConfig(let hash, let config): + return ("appConfig", [("hash", hash as Any), ("config", config as Any)]) + case .appConfigNotModified: + return ("appConfigNotModified", []) } } - public static func parse_affectedMessages(_ reader: BufferReader) -> AffectedMessages? { + public static func parse_appConfig(_ reader: BufferReader) -> AppConfig? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + var _2: Api.JSONValue? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.JSONValue + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.AffectedMessages.affectedMessages(pts: _1!, ptsCount: _2!) + return Api.help.AppConfig.appConfig(hash: _1!, config: _2!) } else { return nil } } + public static func parse_appConfigNotModified(_ reader: BufferReader) -> AppConfig? { + return Api.help.AppConfig.appConfigNotModified + } } } -public extension Api.messages { - enum AllStickers: TypeConstructorDescription { - case allStickers(hash: Int64, sets: [Api.StickerSet]) - case allStickersNotModified +public extension Api.help { + enum AppUpdate: TypeConstructorDescription { + case appUpdate(flags: Int32, id: Int32, version: String, text: String, entities: [Api.MessageEntity], document: Api.Document?, url: String?, sticker: Api.Document?) + case noAppUpdate public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .allStickers(let hash, let sets): + case .appUpdate(let flags, let id, let version, let text, let entities, let document, let url, let sticker): if boxed { - buffer.appendInt32(-843329861) + buffer.appendInt32(-860107216) } - serializeInt64(hash, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeString(version, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sets.count)) - for item in sets { + buffer.appendInt32(Int32(entities.count)) + for item in entities { item.serialize(buffer, true) } + if Int(flags) & Int(1 << 1) != 0 {document!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {sticker!.serialize(buffer, true)} break - case .allStickersNotModified: + case .noAppUpdate: if boxed { - buffer.appendInt32(-395967805) + buffer.appendInt32(-1000708810) } break @@ -1089,173 +1233,225 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .allStickers(let hash, let sets): - return ("allStickers", [("hash", hash as Any), ("sets", sets as Any)]) - case .allStickersNotModified: - return ("allStickersNotModified", []) + case .appUpdate(let flags, let id, let version, let text, let entities, let document, let url, let sticker): + return ("appUpdate", [("flags", flags as Any), ("id", id as Any), ("version", version as Any), ("text", text as Any), ("entities", entities as Any), ("document", document as Any), ("url", url as Any), ("sticker", sticker as Any)]) + case .noAppUpdate: + return ("noAppUpdate", []) } } - public static func parse_allStickers(_ reader: BufferReader) -> AllStickers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StickerSet]? + public static func parse_appUpdate(_ reader: BufferReader) -> AppUpdate? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: [Api.MessageEntity]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSet.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } + var _6: Api.Document? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Document + } } + var _7: String? + if Int(_1!) & Int(1 << 2) != 0 {_7 = parseString(reader) } + var _8: Api.Document? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Document + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.AllStickers.allStickers(hash: _1!, sets: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.help.AppUpdate.appUpdate(flags: _1!, id: _2!, version: _3!, text: _4!, entities: _5!, document: _6, url: _7, sticker: _8) } else { return nil } } - public static func parse_allStickersNotModified(_ reader: BufferReader) -> AllStickers? { - return Api.messages.AllStickers.allStickersNotModified + public static func parse_noAppUpdate(_ reader: BufferReader) -> AppUpdate? { + return Api.help.AppUpdate.noAppUpdate } } } -public extension Api.messages { - enum ArchivedStickers: TypeConstructorDescription { - case archivedStickers(count: Int32, sets: [Api.StickerSetCovered]) +public extension Api.help { + enum CountriesList: TypeConstructorDescription { + case countriesList(countries: [Api.help.Country], hash: Int32) + case countriesListNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .archivedStickers(let count, let sets): + case .countriesList(let countries, let hash): if boxed { - buffer.appendInt32(1338747336) + buffer.appendInt32(-2016381538) } - serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sets.count)) - for item in sets { + buffer.appendInt32(Int32(countries.count)) + for item in countries { item.serialize(buffer, true) } + serializeInt32(hash, buffer: buffer, boxed: false) + break + case .countriesListNotModified: + if boxed { + buffer.appendInt32(-1815339214) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .archivedStickers(let count, let sets): - return ("archivedStickers", [("count", count as Any), ("sets", sets as Any)]) + case .countriesList(let countries, let hash): + return ("countriesList", [("countries", countries as Any), ("hash", hash as Any)]) + case .countriesListNotModified: + return ("countriesListNotModified", []) } } - public static func parse_archivedStickers(_ reader: BufferReader) -> ArchivedStickers? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.StickerSetCovered]? + public static func parse_countriesList(_ reader: BufferReader) -> CountriesList? { + var _1: [Api.help.Country]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.Country.self) } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.ArchivedStickers.archivedStickers(count: _1!, sets: _2!) + return Api.help.CountriesList.countriesList(countries: _1!, hash: _2!) } else { return nil } } + public static func parse_countriesListNotModified(_ reader: BufferReader) -> CountriesList? { + return Api.help.CountriesList.countriesListNotModified + } } } -public extension Api.messages { - enum AvailableReactions: TypeConstructorDescription { - case availableReactions(hash: Int32, reactions: [Api.AvailableReaction]) - case availableReactionsNotModified +public extension Api.help { + enum Country: TypeConstructorDescription { + case country(flags: Int32, iso2: String, defaultName: String, name: String?, countryCodes: [Api.help.CountryCode]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .availableReactions(let hash, let reactions): + case .country(let flags, let iso2, let defaultName, let name, let countryCodes): if boxed { - buffer.appendInt32(1989032621) + buffer.appendInt32(-1014526429) } - serializeInt32(hash, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(iso2, buffer: buffer, boxed: false) + serializeString(defaultName, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(name!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reactions.count)) - for item in reactions { + buffer.appendInt32(Int32(countryCodes.count)) + for item in countryCodes { item.serialize(buffer, true) } - break - case .availableReactionsNotModified: - if boxed { - buffer.appendInt32(-1626924713) - } - break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .availableReactions(let hash, let reactions): - return ("availableReactions", [("hash", hash as Any), ("reactions", reactions as Any)]) - case .availableReactionsNotModified: - return ("availableReactionsNotModified", []) + case .country(let flags, let iso2, let defaultName, let name, let countryCodes): + return ("country", [("flags", flags as Any), ("iso2", iso2 as Any), ("defaultName", defaultName as Any), ("name", name as Any), ("countryCodes", countryCodes as Any)]) } } - public static func parse_availableReactions(_ reader: BufferReader) -> AvailableReactions? { + public static func parse_country(_ reader: BufferReader) -> Country? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.AvailableReaction]? + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } + var _5: [Api.help.CountryCode]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AvailableReaction.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.CountryCode.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.AvailableReactions.availableReactions(hash: _1!, reactions: _2!) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.help.Country.country(flags: _1!, iso2: _2!, defaultName: _3!, name: _4, countryCodes: _5!) } else { return nil } } - public static func parse_availableReactionsNotModified(_ reader: BufferReader) -> AvailableReactions? { - return Api.messages.AvailableReactions.availableReactionsNotModified - } } } -public extension Api.messages { - enum BotApp: TypeConstructorDescription { - case botApp(flags: Int32, app: Api.BotApp) +public extension Api.help { + enum CountryCode: TypeConstructorDescription { + case countryCode(flags: Int32, countryCode: String, prefixes: [String]?, patterns: [String]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .botApp(let flags, let app): + case .countryCode(let flags, let countryCode, let prefixes, let patterns): if boxed { - buffer.appendInt32(-347034123) + buffer.appendInt32(1107543535) } serializeInt32(flags, buffer: buffer, boxed: false) - app.serialize(buffer, true) + serializeString(countryCode, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(prefixes!.count)) + for item in prefixes! { + serializeString(item, buffer: buffer, boxed: false) + }} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(patterns!.count)) + for item in patterns! { + serializeString(item, buffer: buffer, boxed: false) + }} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .botApp(let flags, let app): - return ("botApp", [("flags", flags as Any), ("app", app as Any)]) + case .countryCode(let flags, let countryCode, let prefixes, let patterns): + return ("countryCode", [("flags", flags as Any), ("countryCode", countryCode as Any), ("prefixes", prefixes as Any), ("patterns", patterns as Any)]) } } - public static func parse_botApp(_ reader: BufferReader) -> BotApp? { + public static func parse_countryCode(_ reader: BufferReader) -> CountryCode? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.BotApp? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BotApp - } + var _2: String? + _2 = parseString(reader) + var _3: [String]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } } + var _4: [String]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.BotApp.botApp(flags: _1!, app: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.help.CountryCode.countryCode(flags: _1!, countryCode: _2!, prefixes: _3, patterns: _4) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 331ac6b496a..3ec0b4d752c 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -1,3 +1,53 @@ +public extension Api { + enum BusinessIntro: TypeConstructorDescription { + case businessIntro(flags: Int32, title: String, description: String, sticker: Api.Document?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .businessIntro(let flags, let title, let description, let sticker): + if boxed { + buffer.appendInt32(1510606445) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {sticker!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .businessIntro(let flags, let title, let description, let sticker): + return ("businessIntro", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("sticker", sticker as Any)]) + } + } + + public static func parse_businessIntro(_ reader: BufferReader) -> BusinessIntro? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Api.Document? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Document + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.BusinessIntro.businessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4) + } + else { + return nil + } + } + + } +} public extension Api { enum BusinessLocation: TypeConstructorDescription { case businessLocation(flags: Int32, geoPoint: Api.GeoPoint?, address: String) diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index 6475bda11f7..fd1655b7137 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -1,175 +1,93 @@ -public extension Api.messages { - enum BotCallbackAnswer: TypeConstructorDescription { - case botCallbackAnswer(flags: Int32, message: String?, url: String?, cacheTime: Int32) +public extension Api.help { + enum DeepLinkInfo: TypeConstructorDescription { + case deepLinkInfo(flags: Int32, message: String, entities: [Api.MessageEntity]?) + case deepLinkInfoEmpty public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .botCallbackAnswer(let flags, let message, let url, let cacheTime): + case .deepLinkInfo(let flags, let message, let entities): if boxed { - buffer.appendInt32(911761060) + buffer.appendInt32(1783556146) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(message!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - serializeInt32(cacheTime, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .botCallbackAnswer(let flags, let message, let url, let cacheTime): - return ("botCallbackAnswer", [("flags", flags as Any), ("message", message as Any), ("url", url as Any), ("cacheTime", cacheTime as Any)]) - } - } - - public static func parse_botCallbackAnswer(_ reader: BufferReader) -> BotCallbackAnswer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - var _3: String? - if Int(_1!) & Int(1 << 2) != 0 {_3 = parseString(reader) } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.BotCallbackAnswer.botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum BotResults: TypeConstructorDescription { - case botResults(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, switchWebview: Api.InlineBotWebView?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botResults(let flags, let queryId, let nextOffset, let switchPm, let switchWebview, let results, let cacheTime, let users): + case .deepLinkInfoEmpty: if boxed { - buffer.appendInt32(-534646026) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {switchPm!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {switchWebview!.serialize(buffer, true)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(results.count)) - for item in results { - item.serialize(buffer, true) - } - serializeInt32(cacheTime, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(1722786150) } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .botResults(let flags, let queryId, let nextOffset, let switchPm, let switchWebview, let results, let cacheTime, let users): - return ("botResults", [("flags", flags as Any), ("queryId", queryId as Any), ("nextOffset", nextOffset as Any), ("switchPm", switchPm as Any), ("switchWebview", switchWebview as Any), ("results", results as Any), ("cacheTime", cacheTime as Any), ("users", users as Any)]) + case .deepLinkInfo(let flags, let message, let entities): + return ("deepLinkInfo", [("flags", flags as Any), ("message", message as Any), ("entities", entities as Any)]) + case .deepLinkInfoEmpty: + return ("deepLinkInfoEmpty", []) } } - public static func parse_botResults(_ reader: BufferReader) -> BotResults? { + public static func parse_deepLinkInfo(_ reader: BufferReader) -> DeepLinkInfo? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } - var _4: Api.InlineBotSwitchPM? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.InlineBotSwitchPM - } } - var _5: Api.InlineBotWebView? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.InlineBotWebView + var _2: String? + _2 = parseString(reader) + var _3: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } - var _6: [Api.BotInlineResult]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInlineResult.self) - } - var _7: Int32? - _7 = reader.readInt32() - var _8: [Api.User]? - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.messages.BotResults.botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, switchWebview: _5, results: _6!, cacheTime: _7!, users: _8!) + if _c1 && _c2 && _c3 { + return Api.help.DeepLinkInfo.deepLinkInfo(flags: _1!, message: _2!, entities: _3) } else { return nil } } + public static func parse_deepLinkInfoEmpty(_ reader: BufferReader) -> DeepLinkInfo? { + return Api.help.DeepLinkInfo.deepLinkInfoEmpty + } } } -public extension Api.messages { - enum ChatAdminsWithInvites: TypeConstructorDescription { - case chatAdminsWithInvites(admins: [Api.ChatAdminWithInvites], users: [Api.User]) +public extension Api.help { + enum InviteText: TypeConstructorDescription { + case inviteText(message: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatAdminsWithInvites(let admins, let users): + case .inviteText(let message): if boxed { - buffer.appendInt32(-1231326505) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(admins.count)) - for item in admins { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(415997816) } + serializeString(message, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chatAdminsWithInvites(let admins, let users): - return ("chatAdminsWithInvites", [("admins", admins as Any), ("users", users as Any)]) + case .inviteText(let message): + return ("inviteText", [("message", message as Any)]) } } - public static func parse_chatAdminsWithInvites(_ reader: BufferReader) -> ChatAdminsWithInvites? { - var _1: [Api.ChatAdminWithInvites]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatAdminWithInvites.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } + public static func parse_inviteText(_ reader: BufferReader) -> InviteText? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.ChatAdminsWithInvites.chatAdminsWithInvites(admins: _1!, users: _2!) + if _c1 { + return Api.help.InviteText.inviteText(message: _1!) } else { return nil @@ -178,112 +96,112 @@ public extension Api.messages { } } -public extension Api.messages { - enum ChatFull: TypeConstructorDescription { - case chatFull(fullChat: Api.ChatFull, chats: [Api.Chat], users: [Api.User]) +public extension Api.help { + enum PassportConfig: TypeConstructorDescription { + case passportConfig(hash: Int32, countriesLangs: Api.DataJSON) + case passportConfigNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatFull(let fullChat, let chats, let users): + case .passportConfig(let hash, let countriesLangs): if boxed { - buffer.appendInt32(-438840932) - } - fullChat.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + buffer.appendInt32(-1600596305) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + serializeInt32(hash, buffer: buffer, boxed: false) + countriesLangs.serialize(buffer, true) + break + case .passportConfigNotModified: + if boxed { + buffer.appendInt32(-1078332329) } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chatFull(let fullChat, let chats, let users): - return ("chatFull", [("fullChat", fullChat as Any), ("chats", chats as Any), ("users", users as Any)]) + case .passportConfig(let hash, let countriesLangs): + return ("passportConfig", [("hash", hash as Any), ("countriesLangs", countriesLangs as Any)]) + case .passportConfigNotModified: + return ("passportConfigNotModified", []) } } - public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? { - var _1: Api.ChatFull? + public static func parse_passportConfig(_ reader: BufferReader) -> PassportConfig? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DataJSON? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ChatFull - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ChatFull.chatFull(fullChat: _1!, chats: _2!, users: _3!) + if _c1 && _c2 { + return Api.help.PassportConfig.passportConfig(hash: _1!, countriesLangs: _2!) } else { return nil } } + public static func parse_passportConfigNotModified(_ reader: BufferReader) -> PassportConfig? { + return Api.help.PassportConfig.passportConfigNotModified + } } } -public extension Api.messages { - enum ChatInviteImporters: TypeConstructorDescription { - case chatInviteImporters(count: Int32, importers: [Api.ChatInviteImporter], users: [Api.User]) +public extension Api.help { + enum PeerColorOption: TypeConstructorDescription { + case peerColorOption(flags: Int32, colorId: Int32, colors: Api.help.PeerColorSet?, darkColors: Api.help.PeerColorSet?, channelMinLevel: Int32?, groupMinLevel: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatInviteImporters(let count, let importers, let users): + case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel, let groupMinLevel): if boxed { - buffer.appendInt32(-2118733814) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(importers.count)) - for item in importers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(-1377014082) } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(colorId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {colors!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {darkColors!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(channelMinLevel!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(groupMinLevel!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chatInviteImporters(let count, let importers, let users): - return ("chatInviteImporters", [("count", count as Any), ("importers", importers as Any), ("users", users as Any)]) + case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel, let groupMinLevel): + return ("peerColorOption", [("flags", flags as Any), ("colorId", colorId as Any), ("colors", colors as Any), ("darkColors", darkColors as Any), ("channelMinLevel", channelMinLevel as Any), ("groupMinLevel", groupMinLevel as Any)]) } } - public static func parse_chatInviteImporters(_ reader: BufferReader) -> ChatInviteImporters? { + public static func parse_peerColorOption(_ reader: BufferReader) -> PeerColorOption? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.ChatInviteImporter]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatInviteImporter.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.help.PeerColorSet? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.help.PeerColorSet + } } + var _4: Api.help.PeerColorSet? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.help.PeerColorSet + } } + var _5: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_6 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ChatInviteImporters.chatInviteImporters(count: _1!, importers: _2!, users: _3!) + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.help.PeerColorOption.peerColorOption(flags: _1!, colorId: _2!, colors: _3, darkColors: _4, channelMinLevel: _5, groupMinLevel: _6) } else { return nil @@ -292,32 +210,41 @@ public extension Api.messages { } } -public extension Api.messages { - enum Chats: TypeConstructorDescription { - case chats(chats: [Api.Chat]) - case chatsSlice(count: Int32, chats: [Api.Chat]) +public extension Api.help { + enum PeerColorSet: TypeConstructorDescription { + case peerColorProfileSet(paletteColors: [Int32], bgColors: [Int32], storyColors: [Int32]) + case peerColorSet(colors: [Int32]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chats(let chats): + case .peerColorProfileSet(let paletteColors, let bgColors, let storyColors): if boxed { - buffer.appendInt32(1694474197) + buffer.appendInt32(1987928555) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + buffer.appendInt32(Int32(paletteColors.count)) + for item in paletteColors { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(bgColors.count)) + for item in bgColors { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(storyColors.count)) + for item in storyColors { + serializeInt32(item, buffer: buffer, boxed: false) } break - case .chatsSlice(let count, let chats): + case .peerColorSet(let colors): if boxed { - buffer.appendInt32(-1663561404) + buffer.appendInt32(639736408) } - serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + buffer.appendInt32(Int32(colors.count)) + for item in colors { + serializeInt32(item, buffer: buffer, boxed: false) } break } @@ -325,73 +252,44 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chats(let chats): - return ("chats", [("chats", chats as Any)]) - case .chatsSlice(let count, let chats): - return ("chatsSlice", [("count", count as Any), ("chats", chats as Any)]) + case .peerColorProfileSet(let paletteColors, let bgColors, let storyColors): + return ("peerColorProfileSet", [("paletteColors", paletteColors as Any), ("bgColors", bgColors as Any), ("storyColors", storyColors as Any)]) + case .peerColorSet(let colors): + return ("peerColorSet", [("colors", colors as Any)]) } } - public static func parse_chats(_ reader: BufferReader) -> Chats? { - var _1: [Api.Chat]? + public static func parse_peerColorProfileSet(_ reader: BufferReader) -> PeerColorSet? { + var _1: [Int32]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.messages.Chats.chats(chats: _1!) + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } - else { - return nil + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } - } - public static func parse_chatsSlice(_ reader: BufferReader) -> Chats? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Chat]? + var _3: [Int32]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.Chats.chatsSlice(count: _1!, chats: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.help.PeerColorSet.peerColorProfileSet(paletteColors: _1!, bgColors: _2!, storyColors: _3!) } else { return nil } } - - } -} -public extension Api.messages { - enum CheckedHistoryImportPeer: TypeConstructorDescription { - case checkedHistoryImportPeer(confirmText: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .checkedHistoryImportPeer(let confirmText): - if boxed { - buffer.appendInt32(-1571952873) - } - serializeString(confirmText, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .checkedHistoryImportPeer(let confirmText): - return ("checkedHistoryImportPeer", [("confirmText", confirmText as Any)]) - } - } - - public static func parse_checkedHistoryImportPeer(_ reader: BufferReader) -> CheckedHistoryImportPeer? { - var _1: String? - _1 = parseString(reader) + public static func parse_peerColorSet(_ reader: BufferReader) -> PeerColorSet? { + var _1: [Int32]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } let _c1 = _1 != nil if _c1 { - return Api.messages.CheckedHistoryImportPeer.checkedHistoryImportPeer(confirmText: _1!) + return Api.help.PeerColorSet.peerColorSet(colors: _1!) } else { return nil @@ -400,88 +298,98 @@ public extension Api.messages { } } -public extension Api.messages { - enum DhConfig: TypeConstructorDescription { - case dhConfig(g: Int32, p: Buffer, version: Int32, random: Buffer) - case dhConfigNotModified(random: Buffer) +public extension Api.help { + enum PeerColors: TypeConstructorDescription { + case peerColors(hash: Int32, colors: [Api.help.PeerColorOption]) + case peerColorsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .dhConfig(let g, let p, let version, let random): + case .peerColors(let hash, let colors): if boxed { - buffer.appendInt32(740433629) + buffer.appendInt32(16313608) + } + serializeInt32(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(colors.count)) + for item in colors { + item.serialize(buffer, true) } - serializeInt32(g, buffer: buffer, boxed: false) - serializeBytes(p, buffer: buffer, boxed: false) - serializeInt32(version, buffer: buffer, boxed: false) - serializeBytes(random, buffer: buffer, boxed: false) break - case .dhConfigNotModified(let random): + case .peerColorsNotModified: if boxed { - buffer.appendInt32(-1058912715) + buffer.appendInt32(732034510) } - serializeBytes(random, buffer: buffer, boxed: false) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .dhConfig(let g, let p, let version, let random): - return ("dhConfig", [("g", g as Any), ("p", p as Any), ("version", version as Any), ("random", random as Any)]) - case .dhConfigNotModified(let random): - return ("dhConfigNotModified", [("random", random as Any)]) + case .peerColors(let hash, let colors): + return ("peerColors", [("hash", hash as Any), ("colors", colors as Any)]) + case .peerColorsNotModified: + return ("peerColorsNotModified", []) } } - public static func parse_dhConfig(_ reader: BufferReader) -> DhConfig? { + public static func parse_peerColors(_ reader: BufferReader) -> PeerColors? { var _1: Int32? _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Buffer? - _4 = parseBytes(reader) + var _2: [Api.help.PeerColorOption]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.PeerColorOption.self) + } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.DhConfig.dhConfig(g: _1!, p: _2!, version: _3!, random: _4!) + if _c1 && _c2 { + return Api.help.PeerColors.peerColors(hash: _1!, colors: _2!) } else { return nil } } - public static func parse_dhConfigNotModified(_ reader: BufferReader) -> DhConfig? { - var _1: Buffer? - _1 = parseBytes(reader) - let _c1 = _1 != nil - if _c1 { - return Api.messages.DhConfig.dhConfigNotModified(random: _1!) - } - else { - return nil - } + public static func parse_peerColorsNotModified(_ reader: BufferReader) -> PeerColors? { + return Api.help.PeerColors.peerColorsNotModified } } } -public extension Api.messages { - enum DialogFilters: TypeConstructorDescription { - case dialogFilters(flags: Int32, filters: [Api.DialogFilter]) +public extension Api.help { + enum PremiumPromo: TypeConstructorDescription { + case premiumPromo(statusText: String, statusEntities: [Api.MessageEntity], videoSections: [String], videos: [Api.Document], periodOptions: [Api.PremiumSubscriptionOption], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .dialogFilters(let flags, let filters): + case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users): if boxed { - buffer.appendInt32(718878489) + buffer.appendInt32(1395946908) + } + serializeString(statusText, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(statusEntities.count)) + for item in statusEntities { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(videoSections.count)) + for item in videoSections { + serializeString(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(videos.count)) + for item in videos { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(periodOptions.count)) + for item in periodOptions { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(filters.count)) - for item in filters { + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) } break @@ -490,22 +398,42 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .dialogFilters(let flags, let filters): - return ("dialogFilters", [("flags", flags as Any), ("filters", filters as Any)]) + case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users): + return ("premiumPromo", [("statusText", statusText as Any), ("statusEntities", statusEntities as Any), ("videoSections", videoSections as Any), ("videos", videos as Any), ("periodOptions", periodOptions as Any), ("users", users as Any)]) } } - public static func parse_dialogFilters(_ reader: BufferReader) -> DialogFilters? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.DialogFilter]? + public static func parse_premiumPromo(_ reader: BufferReader) -> PremiumPromo? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + var _3: [String]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + var _4: [Api.Document]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + var _5: [Api.PremiumSubscriptionOption]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumSubscriptionOption.self) + } + var _6: [Api.User]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.DialogFilters.dialogFilters(flags: _1!, filters: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!) } else { return nil @@ -514,28 +442,20 @@ public extension Api.messages { } } -public extension Api.messages { - enum Dialogs: TypeConstructorDescription { - case dialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) - case dialogsNotModified(count: Int32) - case dialogsSlice(count: Int32, dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) +public extension Api.help { + enum PromoData: TypeConstructorDescription { + case promoData(flags: Int32, expires: Int32, peer: Api.Peer, chats: [Api.Chat], users: [Api.User], psaType: String?, psaMessage: String?) + case promoDataEmpty(expires: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .dialogs(let dialogs, let messages, let chats, let users): + case .promoData(let flags, let expires, let peer, let chats, let users, let psaType, let psaMessage): if boxed { - buffer.appendInt32(364538944) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dialogs.count)) - for item in dialogs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) + buffer.appendInt32(-1942390465) } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(expires, buffer: buffer, boxed: false) + peer.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -546,118 +466,68 @@ public extension Api.messages { for item in users { item.serialize(buffer, true) } + if Int(flags) & Int(1 << 1) != 0 {serializeString(psaType!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(psaMessage!, buffer: buffer, boxed: false)} break - case .dialogsNotModified(let count): + case .promoDataEmpty(let expires): if boxed { - buffer.appendInt32(-253500010) - } - serializeInt32(count, buffer: buffer, boxed: false) - break - case .dialogsSlice(let count, let dialogs, let messages, let chats, let users): - if boxed { - buffer.appendInt32(1910543603) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dialogs.count)) - for item in dialogs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(-1728664459) } + serializeInt32(expires, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .dialogs(let dialogs, let messages, let chats, let users): - return ("dialogs", [("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) - case .dialogsNotModified(let count): - return ("dialogsNotModified", [("count", count as Any)]) - case .dialogsSlice(let count, let dialogs, let messages, let chats, let users): - return ("dialogsSlice", [("count", count as Any), ("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .promoData(let flags, let expires, let peer, let chats, let users, let psaType, let psaMessage): + return ("promoData", [("flags", flags as Any), ("expires", expires as Any), ("peer", peer as Any), ("chats", chats as Any), ("users", users as Any), ("psaType", psaType as Any), ("psaMessage", psaMessage as Any)]) + case .promoDataEmpty(let expires): + return ("promoDataEmpty", [("expires", expires as Any)]) } } - public static func parse_dialogs(_ reader: BufferReader) -> Dialogs? { - var _1: [Api.Dialog]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) - } - var _2: [Api.Message]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + public static func parse_promoData(_ reader: BufferReader) -> PromoData? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Peer? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer } - var _3: [Api.Chat]? + var _4: [Api.Chat]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _4: [Api.User]? + var _5: [Api.User]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } + var _6: String? + if Int(_1!) & Int(1 << 1) != 0 {_6 = parseString(reader) } + var _7: String? + if Int(_1!) & Int(1 << 2) != 0 {_7 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.Dialogs.dialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!) + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.help.PromoData.promoData(flags: _1!, expires: _2!, peer: _3!, chats: _4!, users: _5!, psaType: _6, psaMessage: _7) } else { return nil } } - public static func parse_dialogsNotModified(_ reader: BufferReader) -> Dialogs? { + public static func parse_promoDataEmpty(_ reader: BufferReader) -> PromoData? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { - return Api.messages.Dialogs.dialogsNotModified(count: _1!) - } - else { - return nil - } - } - public static func parse_dialogsSlice(_ reader: BufferReader) -> Dialogs? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Dialog]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) - } - var _3: [Api.Message]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.Dialogs.dialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!) + return Api.help.PromoData.promoDataEmpty(expires: _1!) } else { return nil @@ -666,26 +536,21 @@ public extension Api.messages { } } -public extension Api.messages { - enum DiscussionMessage: TypeConstructorDescription { - case discussionMessage(flags: Int32, messages: [Api.Message], maxId: Int32?, readInboxMaxId: Int32?, readOutboxMaxId: Int32?, unreadCount: Int32, chats: [Api.Chat], users: [Api.User]) +public extension Api.help { + enum RecentMeUrls: TypeConstructorDescription { + case recentMeUrls(urls: [Api.RecentMeUrl], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .discussionMessage(let flags, let messages, let maxId, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chats, let users): + case .recentMeUrls(let urls, let chats, let users): if boxed { - buffer.appendInt32(-1506535550) + buffer.appendInt32(235081943) } - serializeInt32(flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { + buffer.appendInt32(Int32(urls.count)) + for item in urls { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(maxId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(readInboxMaxId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(readOutboxMaxId!, buffer: buffer, boxed: false)} - serializeInt32(unreadCount, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -702,44 +567,29 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .discussionMessage(let flags, let messages, let maxId, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chats, let users): - return ("discussionMessage", [("flags", flags as Any), ("messages", messages as Any), ("maxId", maxId as Any), ("readInboxMaxId", readInboxMaxId as Any), ("readOutboxMaxId", readOutboxMaxId as Any), ("unreadCount", unreadCount as Any), ("chats", chats as Any), ("users", users as Any)]) + case .recentMeUrls(let urls, let chats, let users): + return ("recentMeUrls", [("urls", urls as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_discussionMessage(_ reader: BufferReader) -> DiscussionMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Message]? + public static func parse_recentMeUrls(_ reader: BufferReader) -> RecentMeUrls? { + var _1: [Api.RecentMeUrl]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RecentMeUrl.self) } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } - var _6: Int32? - _6 = reader.readInt32() - var _7: [Api.Chat]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _8: [Api.User]? + var _3: [Api.User]? if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.messages.DiscussionMessage.discussionMessage(flags: _1!, messages: _2!, maxId: _3, readInboxMaxId: _4, readOutboxMaxId: _5, unreadCount: _6!, chats: _7!, users: _8!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.help.RecentMeUrls.recentMeUrls(urls: _1!, chats: _2!, users: _3!) } else { return nil @@ -748,142 +598,136 @@ public extension Api.messages { } } -public extension Api.messages { - enum EmojiGroups: TypeConstructorDescription { - case emojiGroups(hash: Int32, groups: [Api.EmojiGroup]) - case emojiGroupsNotModified +public extension Api.help { + enum Support: TypeConstructorDescription { + case support(phoneNumber: String, user: Api.User) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .emojiGroups(let hash, let groups): + case .support(let phoneNumber, let user): if boxed { - buffer.appendInt32(-2011186869) - } - serializeInt32(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(groups.count)) - for item in groups { - item.serialize(buffer, true) + buffer.appendInt32(398898678) } - break - case .emojiGroupsNotModified: - if boxed { - buffer.appendInt32(1874111879) - } - + serializeString(phoneNumber, buffer: buffer, boxed: false) + user.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .emojiGroups(let hash, let groups): - return ("emojiGroups", [("hash", hash as Any), ("groups", groups as Any)]) - case .emojiGroupsNotModified: - return ("emojiGroupsNotModified", []) + case .support(let phoneNumber, let user): + return ("support", [("phoneNumber", phoneNumber as Any), ("user", user as Any)]) } } - public static func parse_emojiGroups(_ reader: BufferReader) -> EmojiGroups? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.EmojiGroup]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiGroup.self) + public static func parse_support(_ reader: BufferReader) -> Support? { + var _1: String? + _1 = parseString(reader) + var _2: Api.User? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.User } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.EmojiGroups.emojiGroups(hash: _1!, groups: _2!) + return Api.help.Support.support(phoneNumber: _1!, user: _2!) + } + else { + return nil + } + } + + } +} +public extension Api.help { + enum SupportName: TypeConstructorDescription { + case supportName(name: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .supportName(let name): + if boxed { + buffer.appendInt32(-1945767479) + } + serializeString(name, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .supportName(let name): + return ("supportName", [("name", name as Any)]) + } + } + + public static func parse_supportName(_ reader: BufferReader) -> SupportName? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.help.SupportName.supportName(name: _1!) } else { return nil } } - public static func parse_emojiGroupsNotModified(_ reader: BufferReader) -> EmojiGroups? { - return Api.messages.EmojiGroups.emojiGroupsNotModified - } } } -public extension Api.messages { - enum ExportedChatInvite: TypeConstructorDescription { - case exportedChatInvite(invite: Api.ExportedChatInvite, users: [Api.User]) - case exportedChatInviteReplaced(invite: Api.ExportedChatInvite, newInvite: Api.ExportedChatInvite, users: [Api.User]) +public extension Api.help { + enum TermsOfService: TypeConstructorDescription { + case termsOfService(flags: Int32, id: Api.DataJSON, text: String, entities: [Api.MessageEntity], minAgeConfirm: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedChatInvite(let invite, let users): - if boxed { - buffer.appendInt32(410107472) - } - invite.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .exportedChatInviteReplaced(let invite, let newInvite, let users): + case .termsOfService(let flags, let id, let text, let entities, let minAgeConfirm): if boxed { - buffer.appendInt32(572915951) + buffer.appendInt32(2013922064) } - invite.serialize(buffer, true) - newInvite.serialize(buffer, true) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + serializeString(text, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { + buffer.appendInt32(Int32(entities.count)) + for item in entities { item.serialize(buffer, true) } + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(minAgeConfirm!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .exportedChatInvite(let invite, let users): - return ("exportedChatInvite", [("invite", invite as Any), ("users", users as Any)]) - case .exportedChatInviteReplaced(let invite, let newInvite, let users): - return ("exportedChatInviteReplaced", [("invite", invite as Any), ("newInvite", newInvite as Any), ("users", users as Any)]) + case .termsOfService(let flags, let id, let text, let entities, let minAgeConfirm): + return ("termsOfService", [("flags", flags as Any), ("id", id as Any), ("text", text as Any), ("entities", entities as Any), ("minAgeConfirm", minAgeConfirm as Any)]) } } - public static func parse_exportedChatInvite(_ reader: BufferReader) -> ExportedChatInvite? { - var _1: Api.ExportedChatInvite? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.ExportedChatInvite.exportedChatInvite(invite: _1!, users: _2!) - } - else { - return nil - } - } - public static func parse_exportedChatInviteReplaced(_ reader: BufferReader) -> ExportedChatInvite? { - var _1: Api.ExportedChatInvite? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } - var _2: Api.ExportedChatInvite? + public static func parse_termsOfService(_ reader: BufferReader) -> TermsOfService? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DataJSON? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON } - var _3: [Api.User]? + var _3: String? + _3 = parseString(reader) + var _4: [Api.MessageEntity]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } + var _5: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ExportedChatInvite.exportedChatInviteReplaced(invite: _1!, newInvite: _2!, users: _3!) + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.help.TermsOfService.termsOfService(flags: _1!, id: _2!, text: _3!, entities: _4!, minAgeConfirm: _5) } else { return nil @@ -892,54 +736,60 @@ public extension Api.messages { } } -public extension Api.messages { - enum ExportedChatInvites: TypeConstructorDescription { - case exportedChatInvites(count: Int32, invites: [Api.ExportedChatInvite], users: [Api.User]) +public extension Api.help { + enum TermsOfServiceUpdate: TypeConstructorDescription { + case termsOfServiceUpdate(expires: Int32, termsOfService: Api.help.TermsOfService) + case termsOfServiceUpdateEmpty(expires: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedChatInvites(let count, let invites, let users): + case .termsOfServiceUpdate(let expires, let termsOfService): if boxed { - buffer.appendInt32(-1111085620) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(invites.count)) - for item in invites { - item.serialize(buffer, true) + buffer.appendInt32(686618977) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + serializeInt32(expires, buffer: buffer, boxed: false) + termsOfService.serialize(buffer, true) + break + case .termsOfServiceUpdateEmpty(let expires): + if boxed { + buffer.appendInt32(-483352705) } + serializeInt32(expires, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .exportedChatInvites(let count, let invites, let users): - return ("exportedChatInvites", [("count", count as Any), ("invites", invites as Any), ("users", users as Any)]) + case .termsOfServiceUpdate(let expires, let termsOfService): + return ("termsOfServiceUpdate", [("expires", expires as Any), ("termsOfService", termsOfService as Any)]) + case .termsOfServiceUpdateEmpty(let expires): + return ("termsOfServiceUpdateEmpty", [("expires", expires as Any)]) } } - public static func parse_exportedChatInvites(_ reader: BufferReader) -> ExportedChatInvites? { + public static func parse_termsOfServiceUpdate(_ reader: BufferReader) -> TermsOfServiceUpdate? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.ExportedChatInvite]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedChatInvite.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + var _2: Api.help.TermsOfService? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.help.TermsOfService } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ExportedChatInvites.exportedChatInvites(count: _1!, invites: _2!, users: _3!) + if _c1 && _c2 { + return Api.help.TermsOfServiceUpdate.termsOfServiceUpdate(expires: _1!, termsOfService: _2!) + } + else { + return nil + } + } + public static func parse_termsOfServiceUpdateEmpty(_ reader: BufferReader) -> TermsOfServiceUpdate? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.help.TermsOfServiceUpdate.termsOfServiceUpdateEmpty(expires: _1!) } else { return nil @@ -948,32 +798,27 @@ public extension Api.messages { } } -public extension Api.messages { - enum FavedStickers: TypeConstructorDescription { - case favedStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document]) - case favedStickersNotModified +public extension Api.help { + enum TimezonesList: TypeConstructorDescription { + case timezonesList(timezones: [Api.Timezone], hash: Int32) + case timezonesListNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .favedStickers(let hash, let packs, let stickers): + case .timezonesList(let timezones, let hash): if boxed { - buffer.appendInt32(750063767) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(packs.count)) - for item in packs { - item.serialize(buffer, true) + buffer.appendInt32(2071260529) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers.count)) - for item in stickers { + buffer.appendInt32(Int32(timezones.count)) + for item in timezones { item.serialize(buffer, true) } + serializeInt32(hash, buffer: buffer, boxed: false) break - case .favedStickersNotModified: + case .timezonesListNotModified: if boxed { - buffer.appendInt32(-1634752813) + buffer.appendInt32(-1761146676) } break @@ -982,200 +827,147 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .favedStickers(let hash, let packs, let stickers): - return ("favedStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any)]) - case .favedStickersNotModified: - return ("favedStickersNotModified", []) + case .timezonesList(let timezones, let hash): + return ("timezonesList", [("timezones", timezones as Any), ("hash", hash as Any)]) + case .timezonesListNotModified: + return ("timezonesListNotModified", []) } } - public static func parse_favedStickers(_ reader: BufferReader) -> FavedStickers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StickerPack]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) - } - var _3: [Api.Document]? + public static func parse_timezonesList(_ reader: BufferReader) -> TimezonesList? { + var _1: [Api.Timezone]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Timezone.self) } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.FavedStickers.favedStickers(hash: _1!, packs: _2!, stickers: _3!) + if _c1 && _c2 { + return Api.help.TimezonesList.timezonesList(timezones: _1!, hash: _2!) } else { return nil } } - public static func parse_favedStickersNotModified(_ reader: BufferReader) -> FavedStickers? { - return Api.messages.FavedStickers.favedStickersNotModified + public static func parse_timezonesListNotModified(_ reader: BufferReader) -> TimezonesList? { + return Api.help.TimezonesList.timezonesListNotModified } } } -public extension Api.messages { - enum FeaturedStickers: TypeConstructorDescription { - case featuredStickers(flags: Int32, hash: Int64, count: Int32, sets: [Api.StickerSetCovered], unread: [Int64]) - case featuredStickersNotModified(count: Int32) +public extension Api.help { + enum UserInfo: TypeConstructorDescription { + case userInfo(message: String, entities: [Api.MessageEntity], author: String, date: Int32) + case userInfoEmpty public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .featuredStickers(let flags, let hash, let count, let sets, let unread): + case .userInfo(let message, let entities, let author, let date): if boxed { - buffer.appendInt32(-1103615738) + buffer.appendInt32(32192344) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sets.count)) - for item in sets { + buffer.appendInt32(Int32(entities.count)) + for item in entities { item.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(unread.count)) - for item in unread { - serializeInt64(item, buffer: buffer, boxed: false) - } + serializeString(author, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) break - case .featuredStickersNotModified(let count): + case .userInfoEmpty: if boxed { - buffer.appendInt32(-958657434) + buffer.appendInt32(-206688531) } - serializeInt32(count, buffer: buffer, boxed: false) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .featuredStickers(let flags, let hash, let count, let sets, let unread): - return ("featuredStickers", [("flags", flags as Any), ("hash", hash as Any), ("count", count as Any), ("sets", sets as Any), ("unread", unread as Any)]) - case .featuredStickersNotModified(let count): - return ("featuredStickersNotModified", [("count", count as Any)]) + case .userInfo(let message, let entities, let author, let date): + return ("userInfo", [("message", message as Any), ("entities", entities as Any), ("author", author as Any), ("date", date as Any)]) + case .userInfoEmpty: + return ("userInfoEmpty", []) } } - public static func parse_featuredStickers(_ reader: BufferReader) -> FeaturedStickers? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } - var _5: [Int64]? + public static func parse_userInfo(_ reader: BufferReader) -> UserInfo? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.MessageEntity]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.FeaturedStickers.featuredStickers(flags: _1!, hash: _2!, count: _3!, sets: _4!, unread: _5!) + if _c1 && _c2 && _c3 && _c4 { + return Api.help.UserInfo.userInfo(message: _1!, entities: _2!, author: _3!, date: _4!) } else { return nil } } - public static func parse_featuredStickersNotModified(_ reader: BufferReader) -> FeaturedStickers? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.messages.FeaturedStickers.featuredStickersNotModified(count: _1!) - } - else { - return nil - } + public static func parse_userInfoEmpty(_ reader: BufferReader) -> UserInfo? { + return Api.help.UserInfo.userInfoEmpty } } } public extension Api.messages { - enum ForumTopics: TypeConstructorDescription { - case forumTopics(flags: Int32, count: Int32, topics: [Api.ForumTopic], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], pts: Int32) + enum AffectedFoundMessages: TypeConstructorDescription { + case affectedFoundMessages(pts: Int32, ptsCount: Int32, offset: Int32, messages: [Int32]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .forumTopics(let flags, let count, let topics, let messages, let chats, let users, let pts): + case .affectedFoundMessages(let pts, let ptsCount, let offset, let messages): if boxed { - buffer.appendInt32(913709011) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(topics.count)) - for item in topics { - item.serialize(buffer, true) + buffer.appendInt32(-275956116) } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(messages.count)) for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + serializeInt32(item, buffer: buffer, boxed: false) } - serializeInt32(pts, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .forumTopics(let flags, let count, let topics, let messages, let chats, let users, let pts): - return ("forumTopics", [("flags", flags as Any), ("count", count as Any), ("topics", topics as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any), ("pts", pts as Any)]) + case .affectedFoundMessages(let pts, let ptsCount, let offset, let messages): + return ("affectedFoundMessages", [("pts", pts as Any), ("ptsCount", ptsCount as Any), ("offset", offset as Any), ("messages", messages as Any)]) } } - public static func parse_forumTopics(_ reader: BufferReader) -> ForumTopics? { + public static func parse_affectedFoundMessages(_ reader: BufferReader) -> AffectedFoundMessages? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: [Api.ForumTopic]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) - } - var _4: [Api.Message]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? + var _3: Int32? + _3 = reader.readInt32() + var _4: [Int32]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } - var _7: Int32? - _7 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.messages.ForumTopics.forumTopics(flags: _1!, count: _2!, topics: _3!, messages: _4!, chats: _5!, users: _6!, pts: _7!) + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.AffectedFoundMessages.affectedFoundMessages(pts: _1!, ptsCount: _2!, offset: _3!, messages: _4!) } else { return nil @@ -1185,107 +977,81 @@ public extension Api.messages { } } public extension Api.messages { - enum FoundStickerSets: TypeConstructorDescription { - case foundStickerSets(hash: Int64, sets: [Api.StickerSetCovered]) - case foundStickerSetsNotModified + enum AffectedHistory: TypeConstructorDescription { + case affectedHistory(pts: Int32, ptsCount: Int32, offset: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .foundStickerSets(let hash, let sets): - if boxed { - buffer.appendInt32(-1963942446) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sets.count)) - for item in sets { - item.serialize(buffer, true) - } - break - case .foundStickerSetsNotModified: + case .affectedHistory(let pts, let ptsCount, let offset): if boxed { - buffer.appendInt32(223655517) + buffer.appendInt32(-1269012015) } - + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .foundStickerSets(let hash, let sets): - return ("foundStickerSets", [("hash", hash as Any), ("sets", sets as Any)]) - case .foundStickerSetsNotModified: - return ("foundStickerSetsNotModified", []) + case .affectedHistory(let pts, let ptsCount, let offset): + return ("affectedHistory", [("pts", pts as Any), ("ptsCount", ptsCount as Any), ("offset", offset as Any)]) } } - public static func parse_foundStickerSets(_ reader: BufferReader) -> FoundStickerSets? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } + public static func parse_affectedHistory(_ reader: BufferReader) -> AffectedHistory? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.FoundStickerSets.foundStickerSets(hash: _1!, sets: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.AffectedHistory.affectedHistory(pts: _1!, ptsCount: _2!, offset: _3!) } else { return nil } } - public static func parse_foundStickerSetsNotModified(_ reader: BufferReader) -> FoundStickerSets? { - return Api.messages.FoundStickerSets.foundStickerSetsNotModified - } } } public extension Api.messages { - enum HighScores: TypeConstructorDescription { - case highScores(scores: [Api.HighScore], users: [Api.User]) + enum AffectedMessages: TypeConstructorDescription { + case affectedMessages(pts: Int32, ptsCount: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .highScores(let scores, let users): + case .affectedMessages(let pts, let ptsCount): if boxed { - buffer.appendInt32(-1707344487) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(scores.count)) - for item in scores { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(-2066640507) } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .highScores(let scores, let users): - return ("highScores", [("scores", scores as Any), ("users", users as Any)]) + case .affectedMessages(let pts, let ptsCount): + return ("affectedMessages", [("pts", pts as Any), ("ptsCount", ptsCount as Any)]) } } - public static func parse_highScores(_ reader: BufferReader) -> HighScores? { - var _1: [Api.HighScore]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.HighScore.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } + public static func parse_affectedMessages(_ reader: BufferReader) -> AffectedMessages? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.HighScores.highScores(scores: _1!, users: _2!) + return Api.messages.AffectedMessages.affectedMessages(pts: _1!, ptsCount: _2!) } else { return nil @@ -1295,73 +1061,101 @@ public extension Api.messages { } } public extension Api.messages { - enum HistoryImport: TypeConstructorDescription { - case historyImport(id: Int64) + enum AllStickers: TypeConstructorDescription { + case allStickers(hash: Int64, sets: [Api.StickerSet]) + case allStickersNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .historyImport(let id): + case .allStickers(let hash, let sets): if boxed { - buffer.appendInt32(375566091) + buffer.appendInt32(-843329861) } - serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sets.count)) + for item in sets { + item.serialize(buffer, true) + } + break + case .allStickersNotModified: + if boxed { + buffer.appendInt32(-395967805) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .historyImport(let id): - return ("historyImport", [("id", id as Any)]) + case .allStickers(let hash, let sets): + return ("allStickers", [("hash", hash as Any), ("sets", sets as Any)]) + case .allStickersNotModified: + return ("allStickersNotModified", []) } } - public static func parse_historyImport(_ reader: BufferReader) -> HistoryImport? { + public static func parse_allStickers(_ reader: BufferReader) -> AllStickers? { var _1: Int64? _1 = reader.readInt64() + var _2: [Api.StickerSet]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSet.self) + } let _c1 = _1 != nil - if _c1 { - return Api.messages.HistoryImport.historyImport(id: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.AllStickers.allStickers(hash: _1!, sets: _2!) } else { return nil } } + public static func parse_allStickersNotModified(_ reader: BufferReader) -> AllStickers? { + return Api.messages.AllStickers.allStickersNotModified + } } } public extension Api.messages { - enum HistoryImportParsed: TypeConstructorDescription { - case historyImportParsed(flags: Int32, title: String?) + enum ArchivedStickers: TypeConstructorDescription { + case archivedStickers(count: Int32, sets: [Api.StickerSetCovered]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .historyImportParsed(let flags, let title): + case .archivedStickers(let count, let sets): if boxed { - buffer.appendInt32(1578088377) + buffer.appendInt32(1338747336) + } + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sets.count)) + for item in sets { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeString(title!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .historyImportParsed(let flags, let title): - return ("historyImportParsed", [("flags", flags as Any), ("title", title as Any)]) + case .archivedStickers(let count, let sets): + return ("archivedStickers", [("count", count as Any), ("sets", sets as Any)]) } } - public static func parse_historyImportParsed(_ reader: BufferReader) -> HistoryImportParsed? { + public static func parse_archivedStickers(_ reader: BufferReader) -> ArchivedStickers? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 2) != 0 {_2 = parseString(reader) } + var _2: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil + let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.HistoryImportParsed.historyImportParsed(flags: _1!, title: _2) + return Api.messages.ArchivedStickers.archivedStickers(count: _1!, sets: _2!) } else { return nil @@ -1371,112 +1165,128 @@ public extension Api.messages { } } public extension Api.messages { - enum InactiveChats: TypeConstructorDescription { - case inactiveChats(dates: [Int32], chats: [Api.Chat], users: [Api.User]) + enum AvailableEffects: TypeConstructorDescription { + case availableEffects(hash: Int32, effects: [Api.AvailableEffect], documents: [Api.Document]) + case availableEffectsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .inactiveChats(let dates, let chats, let users): + case .availableEffects(let hash, let effects, let documents): if boxed { - buffer.appendInt32(-1456996667) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dates.count)) - for item in dates { - serializeInt32(item, buffer: buffer, boxed: false) + buffer.appendInt32(-1109696146) } + serializeInt32(hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(effects.count)) + for item in effects { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { + buffer.appendInt32(Int32(documents.count)) + for item in documents { item.serialize(buffer, true) } + break + case .availableEffectsNotModified: + if boxed { + buffer.appendInt32(-772957605) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .inactiveChats(let dates, let chats, let users): - return ("inactiveChats", [("dates", dates as Any), ("chats", chats as Any), ("users", users as Any)]) + case .availableEffects(let hash, let effects, let documents): + return ("availableEffects", [("hash", hash as Any), ("effects", effects as Any), ("documents", documents as Any)]) + case .availableEffectsNotModified: + return ("availableEffectsNotModified", []) } } - public static func parse_inactiveChats(_ reader: BufferReader) -> InactiveChats? { - var _1: [Int32]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _2: [Api.Chat]? + public static func parse_availableEffects(_ reader: BufferReader) -> AvailableEffects? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.AvailableEffect]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AvailableEffect.self) } - var _3: [Api.User]? + var _3: [Api.Document]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.messages.InactiveChats.inactiveChats(dates: _1!, chats: _2!, users: _3!) + return Api.messages.AvailableEffects.availableEffects(hash: _1!, effects: _2!, documents: _3!) } else { return nil } } + public static func parse_availableEffectsNotModified(_ reader: BufferReader) -> AvailableEffects? { + return Api.messages.AvailableEffects.availableEffectsNotModified + } } } public extension Api.messages { - indirect enum InvitedUsers: TypeConstructorDescription { - case invitedUsers(updates: Api.Updates, missingInvitees: [Api.MissingInvitee]) + enum AvailableReactions: TypeConstructorDescription { + case availableReactions(hash: Int32, reactions: [Api.AvailableReaction]) + case availableReactionsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .invitedUsers(let updates, let missingInvitees): + case .availableReactions(let hash, let reactions): if boxed { - buffer.appendInt32(2136862630) + buffer.appendInt32(1989032621) } - updates.serialize(buffer, true) + serializeInt32(hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(missingInvitees.count)) - for item in missingInvitees { + buffer.appendInt32(Int32(reactions.count)) + for item in reactions { item.serialize(buffer, true) } + break + case .availableReactionsNotModified: + if boxed { + buffer.appendInt32(-1626924713) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .invitedUsers(let updates, let missingInvitees): - return ("invitedUsers", [("updates", updates as Any), ("missingInvitees", missingInvitees as Any)]) + case .availableReactions(let hash, let reactions): + return ("availableReactions", [("hash", hash as Any), ("reactions", reactions as Any)]) + case .availableReactionsNotModified: + return ("availableReactionsNotModified", []) } } - public static func parse_invitedUsers(_ reader: BufferReader) -> InvitedUsers? { - var _1: Api.Updates? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Updates - } - var _2: [Api.MissingInvitee]? + public static func parse_availableReactions(_ reader: BufferReader) -> AvailableReactions? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.AvailableReaction]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MissingInvitee.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AvailableReaction.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.InvitedUsers.invitedUsers(updates: _1!, missingInvitees: _2!) + return Api.messages.AvailableReactions.availableReactions(hash: _1!, reactions: _2!) } else { return nil } } + public static func parse_availableReactionsNotModified(_ reader: BufferReader) -> AvailableReactions? { + return Api.messages.AvailableReactions.availableReactionsNotModified + } } } diff --git a/submodules/TelegramApi/Sources/Api31.swift b/submodules/TelegramApi/Sources/Api31.swift index 3f28e123a8c..5b9410024d6 100644 --- a/submodules/TelegramApi/Sources/Api31.swift +++ b/submodules/TelegramApi/Sources/Api31.swift @@ -1,31 +1,37 @@ public extension Api.messages { - enum MessageEditData: TypeConstructorDescription { - case messageEditData(flags: Int32) + enum BotApp: TypeConstructorDescription { + case botApp(flags: Int32, app: Api.BotApp) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageEditData(let flags): + case .botApp(let flags, let app): if boxed { - buffer.appendInt32(649453030) + buffer.appendInt32(-347034123) } serializeInt32(flags, buffer: buffer, boxed: false) + app.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageEditData(let flags): - return ("messageEditData", [("flags", flags as Any)]) + case .botApp(let flags, let app): + return ("botApp", [("flags", flags as Any), ("app", app as Any)]) } } - public static func parse_messageEditData(_ reader: BufferReader) -> MessageEditData? { + public static func parse_botApp(_ reader: BufferReader) -> BotApp? { var _1: Int32? _1 = reader.readInt32() + var _2: Api.BotApp? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.BotApp + } let _c1 = _1 != nil - if _c1 { - return Api.messages.MessageEditData.messageEditData(flags: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.BotApp.botApp(flags: _1!, app: _2!) } else { return nil @@ -35,71 +41,125 @@ public extension Api.messages { } } public extension Api.messages { - enum MessageReactionsList: TypeConstructorDescription { - case messageReactionsList(flags: Int32, count: Int32, reactions: [Api.MessagePeerReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?) + enum BotCallbackAnswer: TypeConstructorDescription { + case botCallbackAnswer(flags: Int32, message: String?, url: String?, cacheTime: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset): + case .botCallbackAnswer(let flags, let message, let url, let cacheTime): if boxed { - buffer.appendInt32(834488621) + buffer.appendInt32(911761060) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reactions.count)) - for item in reactions { - item.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(message!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + serializeInt32(cacheTime, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .botCallbackAnswer(let flags, let message, let url, let cacheTime): + return ("botCallbackAnswer", [("flags", flags as Any), ("message", message as Any), ("url", url as Any), ("cacheTime", cacheTime as Any)]) + } + } + + public static func parse_botCallbackAnswer(_ reader: BufferReader) -> BotCallbackAnswer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + var _3: String? + if Int(_1!) & Int(1 << 2) != 0 {_3 = parseString(reader) } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.BotCallbackAnswer.botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!) + } + else { + return nil + } + } + + } +} +public extension Api.messages { + enum BotResults: TypeConstructorDescription { + case botResults(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, switchWebview: Api.InlineBotWebView?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botResults(let flags, let queryId, let nextOffset, let switchPm, let switchWebview, let results, let cacheTime, let users): + if boxed { + buffer.appendInt32(-534646026) } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {switchPm!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {switchWebview!.serialize(buffer, true)} buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(results.count)) + for item in results { item.serialize(buffer, true) } + serializeInt32(cacheTime, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset): - return ("messageReactionsList", [("flags", flags as Any), ("count", count as Any), ("reactions", reactions as Any), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)]) + case .botResults(let flags, let queryId, let nextOffset, let switchPm, let switchWebview, let results, let cacheTime, let users): + return ("botResults", [("flags", flags as Any), ("queryId", queryId as Any), ("nextOffset", nextOffset as Any), ("switchPm", switchPm as Any), ("switchWebview", switchWebview as Any), ("results", results as Any), ("cacheTime", cacheTime as Any), ("users", users as Any)]) } } - public static func parse_messageReactionsList(_ reader: BufferReader) -> MessageReactionsList? { + public static func parse_botResults(_ reader: BufferReader) -> BotResults? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.MessagePeerReaction]? + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } + var _4: Api.InlineBotSwitchPM? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InlineBotSwitchPM + } } + var _5: Api.InlineBotWebView? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.InlineBotWebView + } } + var _6: [Api.BotInlineResult]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInlineResult.self) } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? + var _7: Int32? + _7 = reader.readInt32() + var _8: [Api.User]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.messages.MessageReactionsList.messageReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6) + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.messages.BotResults.botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, switchWebview: _5, results: _6!, cacheTime: _7!, users: _8!) } else { return nil @@ -109,20 +169,68 @@ public extension Api.messages { } } public extension Api.messages { - enum MessageViews: TypeConstructorDescription { - case messageViews(views: [Api.MessageViews], chats: [Api.Chat], users: [Api.User]) + enum ChatAdminsWithInvites: TypeConstructorDescription { + case chatAdminsWithInvites(admins: [Api.ChatAdminWithInvites], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageViews(let views, let chats, let users): + case .chatAdminsWithInvites(let admins, let users): if boxed { - buffer.appendInt32(-1228606141) + buffer.appendInt32(-1231326505) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(admins.count)) + for item in admins { + item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(views.count)) - for item in views { + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .chatAdminsWithInvites(let admins, let users): + return ("chatAdminsWithInvites", [("admins", admins as Any), ("users", users as Any)]) + } + } + + public static func parse_chatAdminsWithInvites(_ reader: BufferReader) -> ChatAdminsWithInvites? { + var _1: [Api.ChatAdminWithInvites]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatAdminWithInvites.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.ChatAdminsWithInvites.chatAdminsWithInvites(admins: _1!, users: _2!) + } + else { + return nil + } + } + + } +} +public extension Api.messages { + enum ChatFull: TypeConstructorDescription { + case chatFull(fullChat: Api.ChatFull, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .chatFull(let fullChat, let chats, let users): + if boxed { + buffer.appendInt32(-438840932) + } + fullChat.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -139,15 +247,15 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageViews(let views, let chats, let users): - return ("messageViews", [("views", views as Any), ("chats", chats as Any), ("users", users as Any)]) + case .chatFull(let fullChat, let chats, let users): + return ("chatFull", [("fullChat", fullChat as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { - var _1: [Api.MessageViews]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageViews.self) + public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? { + var _1: Api.ChatFull? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ChatFull } var _2: [Api.Chat]? if let _ = reader.readInt32() { @@ -161,7 +269,7 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.messages.MessageViews.messageViews(views: _1!, chats: _2!, users: _3!) + return Api.messages.ChatFull.chatFull(fullChat: _1!, chats: _2!, users: _3!) } else { return nil @@ -171,35 +279,19 @@ public extension Api.messages { } } public extension Api.messages { - enum Messages: TypeConstructorDescription { - case channelMessages(flags: Int32, pts: Int32, count: Int32, offsetIdOffset: Int32?, messages: [Api.Message], topics: [Api.ForumTopic], chats: [Api.Chat], users: [Api.User]) - case messages(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) - case messagesNotModified(count: Int32) - case messagesSlice(flags: Int32, count: Int32, nextRate: Int32?, offsetIdOffset: Int32?, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + enum ChatInviteImporters: TypeConstructorDescription { + case chatInviteImporters(count: Int32, importers: [Api.ChatInviteImporter], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .channelMessages(let flags, let pts, let count, let offsetIdOffset, let messages, let topics, let chats, let users): + case .chatInviteImporters(let count, let importers, let users): if boxed { - buffer.appendInt32(-948520370) + buffer.appendInt32(-2118733814) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(topics.count)) - for item in topics { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(importers.count)) + for item in importers { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -208,176 +300,104 @@ public extension Api.messages { item.serialize(buffer, true) } break - case .messages(let messages, let chats, let users): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .chatInviteImporters(let count, let importers, let users): + return ("chatInviteImporters", [("count", count as Any), ("importers", importers as Any), ("users", users as Any)]) + } + } + + public static func parse_chatInviteImporters(_ reader: BufferReader) -> ChatInviteImporters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.ChatInviteImporter]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatInviteImporter.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.ChatInviteImporters.chatInviteImporters(count: _1!, importers: _2!, users: _3!) + } + else { + return nil + } + } + + } +} +public extension Api.messages { + enum Chats: TypeConstructorDescription { + case chats(chats: [Api.Chat]) + case chatsSlice(count: Int32, chats: [Api.Chat]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .chats(let chats): if boxed { - buffer.appendInt32(-1938715001) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) + buffer.appendInt32(1694474197) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { item.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } break - case .messagesNotModified(let count): + case .chatsSlice(let count, let chats): if boxed { - buffer.appendInt32(1951620897) + buffer.appendInt32(-1663561404) } serializeInt32(count, buffer: buffer, boxed: false) - break - case .messagesSlice(let flags, let count, let nextRate, let offsetIdOffset, let messages, let chats, let users): - if boxed { - buffer.appendInt32(978610270) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextRate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { item.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .channelMessages(let flags, let pts, let count, let offsetIdOffset, let messages, let topics, let chats, let users): - return ("channelMessages", [("flags", flags as Any), ("pts", pts as Any), ("count", count as Any), ("offsetIdOffset", offsetIdOffset as Any), ("messages", messages as Any), ("topics", topics as Any), ("chats", chats as Any), ("users", users as Any)]) - case .messages(let messages, let chats, let users): - return ("messages", [("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) - case .messagesNotModified(let count): - return ("messagesNotModified", [("count", count as Any)]) - case .messagesSlice(let flags, let count, let nextRate, let offsetIdOffset, let messages, let chats, let users): - return ("messagesSlice", [("flags", flags as Any), ("count", count as Any), ("nextRate", nextRate as Any), ("offsetIdOffset", offsetIdOffset as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .chats(let chats): + return ("chats", [("chats", chats as Any)]) + case .chatsSlice(let count, let chats): + return ("chatsSlice", [("count", count as Any), ("chats", chats as Any)]) } } - public static func parse_channelMessages(_ reader: BufferReader) -> Messages? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } - var _5: [Api.Message]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _6: [Api.ForumTopic]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) - } - var _7: [Api.Chat]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _8: [Api.User]? - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.messages.Messages.channelMessages(flags: _1!, pts: _2!, count: _3!, offsetIdOffset: _4, messages: _5!, topics: _6!, chats: _7!, users: _8!) - } - else { - return nil - } - } - public static func parse_messages(_ reader: BufferReader) -> Messages? { - var _1: [Api.Message]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _2: [Api.Chat]? + public static func parse_chats(_ reader: BufferReader) -> Chats? { + var _1: [Api.Chat]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.Messages.messages(messages: _1!, chats: _2!, users: _3!) - } - else { - return nil + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - } - public static func parse_messagesNotModified(_ reader: BufferReader) -> Messages? { - var _1: Int32? - _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { - return Api.messages.Messages.messagesNotModified(count: _1!) + return Api.messages.Chats.chats(chats: _1!) } else { return nil } } - public static func parse_messagesSlice(_ reader: BufferReader) -> Messages? { + public static func parse_chatsSlice(_ reader: BufferReader) -> Chats? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } - var _5: [Api.Message]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _6: [Api.Chat]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _7: [Api.User]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.messages.Messages.messagesSlice(flags: _1!, count: _2!, nextRate: _3, offsetIdOffset: _4, messages: _5!, chats: _6!, users: _7!) + if _c1 && _c2 { + return Api.messages.Chats.chatsSlice(count: _1!, chats: _2!) } else { return nil @@ -387,43 +407,33 @@ public extension Api.messages { } } public extension Api.messages { - enum MyStickers: TypeConstructorDescription { - case myStickers(count: Int32, sets: [Api.StickerSetCovered]) + enum CheckedHistoryImportPeer: TypeConstructorDescription { + case checkedHistoryImportPeer(confirmText: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .myStickers(let count, let sets): + case .checkedHistoryImportPeer(let confirmText): if boxed { - buffer.appendInt32(-83926371) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sets.count)) - for item in sets { - item.serialize(buffer, true) + buffer.appendInt32(-1571952873) } + serializeString(confirmText, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .myStickers(let count, let sets): - return ("myStickers", [("count", count as Any), ("sets", sets as Any)]) + case .checkedHistoryImportPeer(let confirmText): + return ("checkedHistoryImportPeer", [("confirmText", confirmText as Any)]) } } - public static func parse_myStickers(_ reader: BufferReader) -> MyStickers? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } + public static func parse_checkedHistoryImportPeer(_ reader: BufferReader) -> CheckedHistoryImportPeer? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.MyStickers.myStickers(count: _1!, sets: _2!) + if _c1 { + return Api.messages.CheckedHistoryImportPeer.checkedHistoryImportPeer(confirmText: _1!) } else { return nil @@ -433,75 +443,65 @@ public extension Api.messages { } } public extension Api.messages { - enum PeerDialogs: TypeConstructorDescription { - case peerDialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) + enum DhConfig: TypeConstructorDescription { + case dhConfig(g: Int32, p: Buffer, version: Int32, random: Buffer) + case dhConfigNotModified(random: Buffer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerDialogs(let dialogs, let messages, let chats, let users, let state): + case .dhConfig(let g, let p, let version, let random): if boxed { - buffer.appendInt32(863093588) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dialogs.count)) - for item in dialogs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + buffer.appendInt32(740433629) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + serializeInt32(g, buffer: buffer, boxed: false) + serializeBytes(p, buffer: buffer, boxed: false) + serializeInt32(version, buffer: buffer, boxed: false) + serializeBytes(random, buffer: buffer, boxed: false) + break + case .dhConfigNotModified(let random): + if boxed { + buffer.appendInt32(-1058912715) } - state.serialize(buffer, true) + serializeBytes(random, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .peerDialogs(let dialogs, let messages, let chats, let users, let state): - return ("peerDialogs", [("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any), ("state", state as Any)]) + case .dhConfig(let g, let p, let version, let random): + return ("dhConfig", [("g", g as Any), ("p", p as Any), ("version", version as Any), ("random", random as Any)]) + case .dhConfigNotModified(let random): + return ("dhConfigNotModified", [("random", random as Any)]) } } - public static func parse_peerDialogs(_ reader: BufferReader) -> PeerDialogs? { - var _1: [Api.Dialog]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) - } - var _2: [Api.Message]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _5: Api.updates.State? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.updates.State - } + public static func parse_dhConfig(_ reader: BufferReader) -> DhConfig? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.PeerDialogs.peerDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!, state: _5!) + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.DhConfig.dhConfig(g: _1!, p: _2!, version: _3!, random: _4!) + } + else { + return nil + } + } + public static func parse_dhConfigNotModified(_ reader: BufferReader) -> DhConfig? { + var _1: Buffer? + _1 = parseBytes(reader) + let _c1 = _1 != nil + if _c1 { + return Api.messages.DhConfig.dhConfigNotModified(random: _1!) } else { return nil @@ -511,24 +511,19 @@ public extension Api.messages { } } public extension Api.messages { - enum PeerSettings: TypeConstructorDescription { - case peerSettings(settings: Api.PeerSettings, chats: [Api.Chat], users: [Api.User]) + enum DialogFilters: TypeConstructorDescription { + case dialogFilters(flags: Int32, filters: [Api.DialogFilter]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerSettings(let settings, let chats, let users): + case .dialogFilters(let flags, let filters): if boxed { - buffer.appendInt32(1753266509) - } - settings.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + buffer.appendInt32(718878489) } + serializeInt32(flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { + buffer.appendInt32(Int32(filters.count)) + for item in filters { item.serialize(buffer, true) } break @@ -537,29 +532,22 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .peerSettings(let settings, let chats, let users): - return ("peerSettings", [("settings", settings as Any), ("chats", chats as Any), ("users", users as Any)]) + case .dialogFilters(let flags, let filters): + return ("dialogFilters", [("flags", flags as Any), ("filters", filters as Any)]) } } - public static func parse_peerSettings(_ reader: BufferReader) -> PeerSettings? { - var _1: Api.PeerSettings? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PeerSettings - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? + public static func parse_dialogFilters(_ reader: BufferReader) -> DialogFilters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.DialogFilter]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.PeerSettings.peerSettings(settings: _1!, chats: _2!, users: _3!) + if _c1 && _c2 { + return Api.messages.DialogFilters.dialogFilters(flags: _1!, filters: _2!) } else { return nil @@ -569,19 +557,20 @@ public extension Api.messages { } } public extension Api.messages { - enum QuickReplies: TypeConstructorDescription { - case quickReplies(quickReplies: [Api.QuickReply], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) - case quickRepliesNotModified + enum Dialogs: TypeConstructorDescription { + case dialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + case dialogsNotModified(count: Int32) + case dialogsSlice(count: Int32, dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .quickReplies(let quickReplies, let messages, let chats, let users): + case .dialogs(let dialogs, let messages, let chats, let users): if boxed { - buffer.appendInt32(-963811691) + buffer.appendInt32(364538944) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(quickReplies.count)) - for item in quickReplies { + buffer.appendInt32(Int32(dialogs.count)) + for item in dialogs { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -600,28 +589,56 @@ public extension Api.messages { item.serialize(buffer, true) } break - case .quickRepliesNotModified: + case .dialogsNotModified(let count): if boxed { - buffer.appendInt32(1603398491) + buffer.appendInt32(-253500010) + } + serializeInt32(count, buffer: buffer, boxed: false) + break + case .dialogsSlice(let count, let dialogs, let messages, let chats, let users): + if boxed { + buffer.appendInt32(1910543603) + } + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(dialogs.count)) + for item in dialogs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .quickReplies(let quickReplies, let messages, let chats, let users): - return ("quickReplies", [("quickReplies", quickReplies as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) - case .quickRepliesNotModified: - return ("quickRepliesNotModified", []) + case .dialogs(let dialogs, let messages, let chats, let users): + return ("dialogs", [("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .dialogsNotModified(let count): + return ("dialogsNotModified", [("count", count as Any)]) + case .dialogsSlice(let count, let dialogs, let messages, let chats, let users): + return ("dialogsSlice", [("count", count as Any), ("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_quickReplies(_ reader: BufferReader) -> QuickReplies? { - var _1: [Api.QuickReply]? + public static func parse_dialogs(_ reader: BufferReader) -> Dialogs? { + var _1: [Api.Dialog]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) } var _2: [Api.Message]? if let _ = reader.readInt32() { @@ -640,107 +657,160 @@ public extension Api.messages { let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.messages.QuickReplies.quickReplies(quickReplies: _1!, messages: _2!, chats: _3!, users: _4!) + return Api.messages.Dialogs.dialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!) } else { return nil } } - public static func parse_quickRepliesNotModified(_ reader: BufferReader) -> QuickReplies? { - return Api.messages.QuickReplies.quickRepliesNotModified + public static func parse_dialogsNotModified(_ reader: BufferReader) -> Dialogs? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.messages.Dialogs.dialogsNotModified(count: _1!) + } + else { + return nil + } + } + public static func parse_dialogsSlice(_ reader: BufferReader) -> Dialogs? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Dialog]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) + } + var _3: [Api.Message]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.Dialogs.dialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!) + } + else { + return nil + } } } } public extension Api.messages { - enum Reactions: TypeConstructorDescription { - case reactions(hash: Int64, reactions: [Api.Reaction]) - case reactionsNotModified + enum DiscussionMessage: TypeConstructorDescription { + case discussionMessage(flags: Int32, messages: [Api.Message], maxId: Int32?, readInboxMaxId: Int32?, readOutboxMaxId: Int32?, unreadCount: Int32, chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .reactions(let hash, let reactions): + case .discussionMessage(let flags, let messages, let maxId, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chats, let users): if boxed { - buffer.appendInt32(-352454890) + buffer.appendInt32(-1506535550) } - serializeInt64(hash, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reactions.count)) - for item in reactions { + buffer.appendInt32(Int32(messages.count)) + for item in messages { item.serialize(buffer, true) } - break - case .reactionsNotModified: - if boxed { - buffer.appendInt32(-1334846497) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(maxId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(readInboxMaxId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(readOutboxMaxId!, buffer: buffer, boxed: false)} + serializeInt32(unreadCount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .reactions(let hash, let reactions): - return ("reactions", [("hash", hash as Any), ("reactions", reactions as Any)]) - case .reactionsNotModified: - return ("reactionsNotModified", []) + case .discussionMessage(let flags, let messages, let maxId, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chats, let users): + return ("discussionMessage", [("flags", flags as Any), ("messages", messages as Any), ("maxId", maxId as Any), ("readInboxMaxId", readInboxMaxId as Any), ("readOutboxMaxId", readOutboxMaxId as Any), ("unreadCount", unreadCount as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_reactions(_ reader: BufferReader) -> Reactions? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Reaction]? + public static func parse_discussionMessage(_ reader: BufferReader) -> DiscussionMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Message]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + var _5: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } + var _6: Int32? + _6 = reader.readInt32() + var _7: [Api.Chat]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _8: [Api.User]? + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.Reactions.reactions(hash: _1!, reactions: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.messages.DiscussionMessage.discussionMessage(flags: _1!, messages: _2!, maxId: _3, readInboxMaxId: _4, readOutboxMaxId: _5, unreadCount: _6!, chats: _7!, users: _8!) } else { return nil } } - public static func parse_reactionsNotModified(_ reader: BufferReader) -> Reactions? { - return Api.messages.Reactions.reactionsNotModified - } } } public extension Api.messages { - enum RecentStickers: TypeConstructorDescription { - case recentStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32]) - case recentStickersNotModified + enum EmojiGroups: TypeConstructorDescription { + case emojiGroups(hash: Int32, groups: [Api.EmojiGroup]) + case emojiGroupsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .recentStickers(let hash, let packs, let stickers, let dates): + case .emojiGroups(let hash, let groups): if boxed { - buffer.appendInt32(-1999405994) + buffer.appendInt32(-2011186869) } - serializeInt64(hash, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(packs.count)) - for item in packs { + buffer.appendInt32(Int32(groups.count)) + for item in groups { item.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers.count)) - for item in stickers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dates.count)) - for item in dates { - serializeInt32(item, buffer: buffer, boxed: false) - } break - case .recentStickersNotModified: + case .emojiGroupsNotModified: if boxed { - buffer.appendInt32(186120336) + buffer.appendInt32(1874111879) } break @@ -749,104 +819,59 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .recentStickers(let hash, let packs, let stickers, let dates): - return ("recentStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any), ("dates", dates as Any)]) - case .recentStickersNotModified: - return ("recentStickersNotModified", []) + case .emojiGroups(let hash, let groups): + return ("emojiGroups", [("hash", hash as Any), ("groups", groups as Any)]) + case .emojiGroupsNotModified: + return ("emojiGroupsNotModified", []) } } - public static func parse_recentStickers(_ reader: BufferReader) -> RecentStickers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StickerPack]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) - } - var _3: [Api.Document]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - var _4: [Int32]? + public static func parse_emojiGroups(_ reader: BufferReader) -> EmojiGroups? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.EmojiGroup]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiGroup.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.RecentStickers.recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!) + if _c1 && _c2 { + return Api.messages.EmojiGroups.emojiGroups(hash: _1!, groups: _2!) } else { return nil } } - public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? { - return Api.messages.RecentStickers.recentStickersNotModified + public static func parse_emojiGroupsNotModified(_ reader: BufferReader) -> EmojiGroups? { + return Api.messages.EmojiGroups.emojiGroupsNotModified } } } public extension Api.messages { - enum SavedDialogs: TypeConstructorDescription { - case savedDialogs(dialogs: [Api.SavedDialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) - case savedDialogsNotModified(count: Int32) - case savedDialogsSlice(count: Int32, dialogs: [Api.SavedDialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + enum ExportedChatInvite: TypeConstructorDescription { + case exportedChatInvite(invite: Api.ExportedChatInvite, users: [Api.User]) + case exportedChatInviteReplaced(invite: Api.ExportedChatInvite, newInvite: Api.ExportedChatInvite, users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedDialogs(let dialogs, let messages, let chats, let users): - if boxed { - buffer.appendInt32(-130358751) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dialogs.count)) - for item in dialogs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .savedDialogsNotModified(let count): - if boxed { - buffer.appendInt32(-1071681560) - } - serializeInt32(count, buffer: buffer, boxed: false) - break - case .savedDialogsSlice(let count, let dialogs, let messages, let chats, let users): - if boxed { - buffer.appendInt32(1153080793) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dialogs.count)) - for item in dialogs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) + switch self { + case .exportedChatInvite(let invite, let users): + if boxed { + buffer.appendInt32(410107472) } + invite.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) } + break + case .exportedChatInviteReplaced(let invite, let newInvite, let users): + if boxed { + buffer.appendInt32(572915951) + } + invite.serialize(buffer, true) + newInvite.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { @@ -858,80 +883,49 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedDialogs(let dialogs, let messages, let chats, let users): - return ("savedDialogs", [("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) - case .savedDialogsNotModified(let count): - return ("savedDialogsNotModified", [("count", count as Any)]) - case .savedDialogsSlice(let count, let dialogs, let messages, let chats, let users): - return ("savedDialogsSlice", [("count", count as Any), ("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .exportedChatInvite(let invite, let users): + return ("exportedChatInvite", [("invite", invite as Any), ("users", users as Any)]) + case .exportedChatInviteReplaced(let invite, let newInvite, let users): + return ("exportedChatInviteReplaced", [("invite", invite as Any), ("newInvite", newInvite as Any), ("users", users as Any)]) } } - public static func parse_savedDialogs(_ reader: BufferReader) -> SavedDialogs? { - var _1: [Api.SavedDialog]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedDialog.self) - } - var _2: [Api.Message]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + public static func parse_exportedChatInvite(_ reader: BufferReader) -> ExportedChatInvite? { + var _1: Api.ExportedChatInvite? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite } - var _4: [Api.User]? + var _2: [Api.User]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.SavedDialogs.savedDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!) - } - else { - return nil - } - } - public static func parse_savedDialogsNotModified(_ reader: BufferReader) -> SavedDialogs? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.messages.SavedDialogs.savedDialogsNotModified(count: _1!) + if _c1 && _c2 { + return Api.messages.ExportedChatInvite.exportedChatInvite(invite: _1!, users: _2!) } else { return nil } } - public static func parse_savedDialogsSlice(_ reader: BufferReader) -> SavedDialogs? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.SavedDialog]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedDialog.self) - } - var _3: [Api.Message]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + public static func parse_exportedChatInviteReplaced(_ reader: BufferReader) -> ExportedChatInvite? { + var _1: Api.ExportedChatInvite? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + var _2: Api.ExportedChatInvite? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite } - var _5: [Api.User]? + var _3: [Api.User]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.SavedDialogs.savedDialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!) + if _c1 && _c2 && _c3 { + return Api.messages.ExportedChatInvite.exportedChatInviteReplaced(invite: _1!, newInvite: _2!, users: _3!) } else { return nil @@ -941,84 +935,87 @@ public extension Api.messages { } } public extension Api.messages { - enum SavedGifs: TypeConstructorDescription { - case savedGifs(hash: Int64, gifs: [Api.Document]) - case savedGifsNotModified + enum ExportedChatInvites: TypeConstructorDescription { + case exportedChatInvites(count: Int32, invites: [Api.ExportedChatInvite], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedGifs(let hash, let gifs): + case .exportedChatInvites(let count, let invites, let users): if boxed { - buffer.appendInt32(-2069878259) + buffer.appendInt32(-1111085620) } - serializeInt64(hash, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(gifs.count)) - for item in gifs { + buffer.appendInt32(Int32(invites.count)) + for item in invites { item.serialize(buffer, true) } - break - case .savedGifsNotModified: - if boxed { - buffer.appendInt32(-402498398) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedGifs(let hash, let gifs): - return ("savedGifs", [("hash", hash as Any), ("gifs", gifs as Any)]) - case .savedGifsNotModified: - return ("savedGifsNotModified", []) + case .exportedChatInvites(let count, let invites, let users): + return ("exportedChatInvites", [("count", count as Any), ("invites", invites as Any), ("users", users as Any)]) } } - public static func parse_savedGifs(_ reader: BufferReader) -> SavedGifs? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Document]? + public static func parse_exportedChatInvites(_ reader: BufferReader) -> ExportedChatInvites? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.ExportedChatInvite]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedChatInvite.self) + } + var _3: [Api.User]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.SavedGifs.savedGifs(hash: _1!, gifs: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.ExportedChatInvites.exportedChatInvites(count: _1!, invites: _2!, users: _3!) } else { return nil } } - public static func parse_savedGifsNotModified(_ reader: BufferReader) -> SavedGifs? { - return Api.messages.SavedGifs.savedGifsNotModified - } } } public extension Api.messages { - enum SavedReactionTags: TypeConstructorDescription { - case savedReactionTags(tags: [Api.SavedReactionTag], hash: Int64) - case savedReactionTagsNotModified + enum FavedStickers: TypeConstructorDescription { + case favedStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document]) + case favedStickersNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedReactionTags(let tags, let hash): + case .favedStickers(let hash, let packs, let stickers): if boxed { - buffer.appendInt32(844731658) + buffer.appendInt32(750063767) } + serializeInt64(hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(tags.count)) - for item in tags { + buffer.appendInt32(Int32(packs.count)) + for item in packs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers.count)) + for item in stickers { item.serialize(buffer, true) } - serializeInt64(hash, buffer: buffer, boxed: false) break - case .savedReactionTagsNotModified: + case .favedStickersNotModified: if boxed { - buffer.appendInt32(-2003084817) + buffer.appendInt32(-1634752813) } break @@ -1027,47 +1024,69 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedReactionTags(let tags, let hash): - return ("savedReactionTags", [("tags", tags as Any), ("hash", hash as Any)]) - case .savedReactionTagsNotModified: - return ("savedReactionTagsNotModified", []) + case .favedStickers(let hash, let packs, let stickers): + return ("favedStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any)]) + case .favedStickersNotModified: + return ("favedStickersNotModified", []) } } - public static func parse_savedReactionTags(_ reader: BufferReader) -> SavedReactionTags? { - var _1: [Api.SavedReactionTag]? + public static func parse_favedStickers(_ reader: BufferReader) -> FavedStickers? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.StickerPack]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + } + var _3: [Api.Document]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedReactionTag.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } - var _2: Int64? - _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.SavedReactionTags.savedReactionTags(tags: _1!, hash: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.FavedStickers.favedStickers(hash: _1!, packs: _2!, stickers: _3!) } else { return nil } } - public static func parse_savedReactionTagsNotModified(_ reader: BufferReader) -> SavedReactionTags? { - return Api.messages.SavedReactionTags.savedReactionTagsNotModified + public static func parse_favedStickersNotModified(_ reader: BufferReader) -> FavedStickers? { + return Api.messages.FavedStickers.favedStickersNotModified } } } public extension Api.messages { - enum SearchCounter: TypeConstructorDescription { - case searchCounter(flags: Int32, filter: Api.MessagesFilter, count: Int32) + enum FeaturedStickers: TypeConstructorDescription { + case featuredStickers(flags: Int32, hash: Int64, count: Int32, sets: [Api.StickerSetCovered], unread: [Int64]) + case featuredStickersNotModified(count: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .searchCounter(let flags, let filter, let count): + case .featuredStickers(let flags, let hash, let count, let sets, let unread): if boxed { - buffer.appendInt32(-398136321) + buffer.appendInt32(-1103615738) } serializeInt32(flags, buffer: buffer, boxed: false) - filter.serialize(buffer, true) + serializeInt64(hash, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sets.count)) + for item in sets { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(unread.count)) + for item in unread { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .featuredStickersNotModified(let count): + if boxed { + buffer.appendInt32(-958657434) + } serializeInt32(count, buffer: buffer, boxed: false) break } @@ -1075,25 +1094,46 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .searchCounter(let flags, let filter, let count): - return ("searchCounter", [("flags", flags as Any), ("filter", filter as Any), ("count", count as Any)]) + case .featuredStickers(let flags, let hash, let count, let sets, let unread): + return ("featuredStickers", [("flags", flags as Any), ("hash", hash as Any), ("count", count as Any), ("sets", sets as Any), ("unread", unread as Any)]) + case .featuredStickersNotModified(let count): + return ("featuredStickersNotModified", [("count", count as Any)]) } } - public static func parse_searchCounter(_ reader: BufferReader) -> SearchCounter? { + public static func parse_featuredStickers(_ reader: BufferReader) -> FeaturedStickers? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.MessagesFilter? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.MessagesFilter - } + var _2: Int64? + _2 = reader.readInt64() var _3: Int32? _3 = reader.readInt32() + var _4: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } + var _5: [Int64]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.SearchCounter.searchCounter(flags: _1!, filter: _2!, count: _3!) + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.FeaturedStickers.featuredStickers(flags: _1!, hash: _2!, count: _3!, sets: _4!, unread: _5!) + } + else { + return nil + } + } + public static func parse_featuredStickersNotModified(_ reader: BufferReader) -> FeaturedStickers? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.messages.FeaturedStickers.featuredStickersNotModified(count: _1!) } else { return nil @@ -1103,23 +1143,20 @@ public extension Api.messages { } } public extension Api.messages { - enum SearchResultsCalendar: TypeConstructorDescription { - case searchResultsCalendar(flags: Int32, count: Int32, minDate: Int32, minMsgId: Int32, offsetIdOffset: Int32?, periods: [Api.SearchResultsCalendarPeriod], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + enum ForumTopics: TypeConstructorDescription { + case forumTopics(flags: Int32, count: Int32, topics: [Api.ForumTopic], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], pts: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .searchResultsCalendar(let flags, let count, let minDate, let minMsgId, let offsetIdOffset, let periods, let messages, let chats, let users): + case .forumTopics(let flags, let count, let topics, let messages, let chats, let users, let pts): if boxed { - buffer.appendInt32(343859772) + buffer.appendInt32(913709011) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false) - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(minMsgId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) - buffer.appendInt32(Int32(periods.count)) - for item in periods { + buffer.appendInt32(Int32(topics.count)) + for item in topics { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -1137,55 +1174,50 @@ public extension Api.messages { for item in users { item.serialize(buffer, true) } + serializeInt32(pts, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .searchResultsCalendar(let flags, let count, let minDate, let minMsgId, let offsetIdOffset, let periods, let messages, let chats, let users): - return ("searchResultsCalendar", [("flags", flags as Any), ("count", count as Any), ("minDate", minDate as Any), ("minMsgId", minMsgId as Any), ("offsetIdOffset", offsetIdOffset as Any), ("periods", periods as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .forumTopics(let flags, let count, let topics, let messages, let chats, let users, let pts): + return ("forumTopics", [("flags", flags as Any), ("count", count as Any), ("topics", topics as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any), ("pts", pts as Any)]) } } - public static func parse_searchResultsCalendar(_ reader: BufferReader) -> SearchResultsCalendar? { + public static func parse_forumTopics(_ reader: BufferReader) -> ForumTopics? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } - var _6: [Api.SearchResultsCalendarPeriod]? + var _3: [Api.ForumTopic]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsCalendarPeriod.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) } - var _7: [Api.Message]? + var _4: [Api.Message]? if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) } - var _8: [Api.Chat]? + var _5: [Api.Chat]? if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _9: [Api.User]? + var _6: [Api.User]? if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } + var _7: Int32? + _7 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.messages.SearchResultsCalendar.searchResultsCalendar(flags: _1!, count: _2!, minDate: _3!, minMsgId: _4!, offsetIdOffset: _5, periods: _6!, messages: _7!, chats: _8!, users: _9!) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.messages.ForumTopics.forumTopics(flags: _1!, count: _2!, topics: _3!, messages: _4!, chats: _5!, users: _6!, pts: _7!) } else { return nil @@ -1195,105 +1227,143 @@ public extension Api.messages { } } public extension Api.messages { - enum SearchResultsPositions: TypeConstructorDescription { - case searchResultsPositions(count: Int32, positions: [Api.SearchResultsPosition]) + enum FoundStickerSets: TypeConstructorDescription { + case foundStickerSets(hash: Int64, sets: [Api.StickerSetCovered]) + case foundStickerSetsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .searchResultsPositions(let count, let positions): + case .foundStickerSets(let hash, let sets): if boxed { - buffer.appendInt32(1404185519) + buffer.appendInt32(-1963942446) } - serializeInt32(count, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(positions.count)) - for item in positions { + buffer.appendInt32(Int32(sets.count)) + for item in sets { item.serialize(buffer, true) } + break + case .foundStickerSetsNotModified: + if boxed { + buffer.appendInt32(223655517) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .searchResultsPositions(let count, let positions): - return ("searchResultsPositions", [("count", count as Any), ("positions", positions as Any)]) + case .foundStickerSets(let hash, let sets): + return ("foundStickerSets", [("hash", hash as Any), ("sets", sets as Any)]) + case .foundStickerSetsNotModified: + return ("foundStickerSetsNotModified", []) } } - public static func parse_searchResultsPositions(_ reader: BufferReader) -> SearchResultsPositions? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.SearchResultsPosition]? + public static func parse_foundStickerSets(_ reader: BufferReader) -> FoundStickerSets? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.StickerSetCovered]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsPosition.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.SearchResultsPositions.searchResultsPositions(count: _1!, positions: _2!) + return Api.messages.FoundStickerSets.foundStickerSets(hash: _1!, sets: _2!) } else { return nil } } + public static func parse_foundStickerSetsNotModified(_ reader: BufferReader) -> FoundStickerSets? { + return Api.messages.FoundStickerSets.foundStickerSetsNotModified + } } } public extension Api.messages { - enum SentEncryptedMessage: TypeConstructorDescription { - case sentEncryptedFile(date: Int32, file: Api.EncryptedFile) - case sentEncryptedMessage(date: Int32) + enum HighScores: TypeConstructorDescription { + case highScores(scores: [Api.HighScore], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sentEncryptedFile(let date, let file): + case .highScores(let scores, let users): if boxed { - buffer.appendInt32(-1802240206) + buffer.appendInt32(-1707344487) } - serializeInt32(date, buffer: buffer, boxed: false) - file.serialize(buffer, true) - break - case .sentEncryptedMessage(let date): - if boxed { - buffer.appendInt32(1443858741) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(scores.count)) + for item in scores { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeInt32(date, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sentEncryptedFile(let date, let file): - return ("sentEncryptedFile", [("date", date as Any), ("file", file as Any)]) - case .sentEncryptedMessage(let date): - return ("sentEncryptedMessage", [("date", date as Any)]) + case .highScores(let scores, let users): + return ("highScores", [("scores", scores as Any), ("users", users as Any)]) } } - public static func parse_sentEncryptedFile(_ reader: BufferReader) -> SentEncryptedMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.EncryptedFile? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.EncryptedFile + public static func parse_highScores(_ reader: BufferReader) -> HighScores? { + var _1: [Api.HighScore]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.HighScore.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.SentEncryptedMessage.sentEncryptedFile(date: _1!, file: _2!) + return Api.messages.HighScores.highScores(scores: _1!, users: _2!) } else { return nil } } - public static func parse_sentEncryptedMessage(_ reader: BufferReader) -> SentEncryptedMessage? { - var _1: Int32? - _1 = reader.readInt32() + + } +} +public extension Api.messages { + enum HistoryImport: TypeConstructorDescription { + case historyImport(id: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .historyImport(let id): + if boxed { + buffer.appendInt32(375566091) + } + serializeInt64(id, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .historyImport(let id): + return ("historyImport", [("id", id as Any)]) + } + } + + public static func parse_historyImport(_ reader: BufferReader) -> HistoryImport? { + var _1: Int64? + _1 = reader.readInt64() let _c1 = _1 != nil if _c1 { - return Api.messages.SentEncryptedMessage.sentEncryptedMessage(date: _1!) + return Api.messages.HistoryImport.historyImport(id: _1!) } else { return nil @@ -1303,164 +1373,104 @@ public extension Api.messages { } } public extension Api.messages { - enum SponsoredMessages: TypeConstructorDescription { - case sponsoredMessages(flags: Int32, postsBetween: Int32?, messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User]) - case sponsoredMessagesEmpty + enum HistoryImportParsed: TypeConstructorDescription { + case historyImportParsed(flags: Int32, title: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sponsoredMessages(let flags, let postsBetween, let messages, let chats, let users): + case .historyImportParsed(let flags, let title): if boxed { - buffer.appendInt32(-907141753) + buffer.appendInt32(1578088377) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(postsBetween!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .sponsoredMessagesEmpty: - if boxed { - buffer.appendInt32(406407439) - } - + if Int(flags) & Int(1 << 2) != 0 {serializeString(title!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sponsoredMessages(let flags, let postsBetween, let messages, let chats, let users): - return ("sponsoredMessages", [("flags", flags as Any), ("postsBetween", postsBetween as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) - case .sponsoredMessagesEmpty: - return ("sponsoredMessagesEmpty", []) + case .historyImportParsed(let flags, let title): + return ("historyImportParsed", [("flags", flags as Any), ("title", title as Any)]) } } - public static func parse_sponsoredMessages(_ reader: BufferReader) -> SponsoredMessages? { + public static func parse_historyImportParsed(_ reader: BufferReader) -> HistoryImportParsed? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: [Api.SponsoredMessage]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessage.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } + var _2: String? + if Int(_1!) & Int(1 << 2) != 0 {_2 = parseString(reader) } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.SponsoredMessages.sponsoredMessages(flags: _1!, postsBetween: _2, messages: _3!, chats: _4!, users: _5!) + let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil + if _c1 && _c2 { + return Api.messages.HistoryImportParsed.historyImportParsed(flags: _1!, title: _2) } else { return nil } } - public static func parse_sponsoredMessagesEmpty(_ reader: BufferReader) -> SponsoredMessages? { - return Api.messages.SponsoredMessages.sponsoredMessagesEmpty - } } } public extension Api.messages { - enum StickerSet: TypeConstructorDescription { - case stickerSet(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) - case stickerSetNotModified + enum InactiveChats: TypeConstructorDescription { + case inactiveChats(dates: [Int32], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .stickerSet(let set, let packs, let keywords, let documents): + case .inactiveChats(let dates, let chats, let users): if boxed { - buffer.appendInt32(1846886166) + buffer.appendInt32(-1456996667) } - set.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(packs.count)) - for item in packs { - item.serialize(buffer, true) + buffer.appendInt32(Int32(dates.count)) + for item in dates { + serializeInt32(item, buffer: buffer, boxed: false) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(keywords.count)) - for item in keywords { + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documents.count)) - for item in documents { + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) } - break - case .stickerSetNotModified: - if boxed { - buffer.appendInt32(-738646805) - } - break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .stickerSet(let set, let packs, let keywords, let documents): - return ("stickerSet", [("set", set as Any), ("packs", packs as Any), ("keywords", keywords as Any), ("documents", documents as Any)]) - case .stickerSetNotModified: - return ("stickerSetNotModified", []) + case .inactiveChats(let dates, let chats, let users): + return ("inactiveChats", [("dates", dates as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_stickerSet(_ reader: BufferReader) -> StickerSet? { - var _1: Api.StickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StickerSet - } - var _2: [Api.StickerPack]? + public static func parse_inactiveChats(_ reader: BufferReader) -> InactiveChats? { + var _1: [Int32]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } - var _3: [Api.StickerKeyword]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _4: [Api.Document]? + var _3: [Api.User]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.StickerSet.stickerSet(set: _1!, packs: _2!, keywords: _3!, documents: _4!) + if _c1 && _c2 && _c3 { + return Api.messages.InactiveChats.inactiveChats(dates: _1!, chats: _2!, users: _3!) } else { return nil } } - public static func parse_stickerSetNotModified(_ reader: BufferReader) -> StickerSet? { - return Api.messages.StickerSet.stickerSetNotModified - } } } diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index 4e07851e3e3..6cdc8ecbd81 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -1,201 +1,79 @@ public extension Api.messages { - enum StickerSetInstallResult: TypeConstructorDescription { - case stickerSetInstallResultArchive(sets: [Api.StickerSetCovered]) - case stickerSetInstallResultSuccess + indirect enum InvitedUsers: TypeConstructorDescription { + case invitedUsers(updates: Api.Updates, missingInvitees: [Api.MissingInvitee]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .stickerSetInstallResultArchive(let sets): + case .invitedUsers(let updates, let missingInvitees): if boxed { - buffer.appendInt32(904138920) + buffer.appendInt32(2136862630) } + updates.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sets.count)) - for item in sets { + buffer.appendInt32(Int32(missingInvitees.count)) + for item in missingInvitees { item.serialize(buffer, true) } - break - case .stickerSetInstallResultSuccess: - if boxed { - buffer.appendInt32(946083368) - } - break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .stickerSetInstallResultArchive(let sets): - return ("stickerSetInstallResultArchive", [("sets", sets as Any)]) - case .stickerSetInstallResultSuccess: - return ("stickerSetInstallResultSuccess", []) + case .invitedUsers(let updates, let missingInvitees): + return ("invitedUsers", [("updates", updates as Any), ("missingInvitees", missingInvitees as Any)]) } } - public static func parse_stickerSetInstallResultArchive(_ reader: BufferReader) -> StickerSetInstallResult? { - var _1: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.messages.StickerSetInstallResult.stickerSetInstallResultArchive(sets: _1!) - } - else { - return nil + public static func parse_invitedUsers(_ reader: BufferReader) -> InvitedUsers? { + var _1: Api.Updates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Updates } - } - public static func parse_stickerSetInstallResultSuccess(_ reader: BufferReader) -> StickerSetInstallResult? { - return Api.messages.StickerSetInstallResult.stickerSetInstallResultSuccess - } - - } -} -public extension Api.messages { - enum Stickers: TypeConstructorDescription { - case stickers(hash: Int64, stickers: [Api.Document]) - case stickersNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stickers(let hash, let stickers): - if boxed { - buffer.appendInt32(816245886) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers.count)) - for item in stickers { - item.serialize(buffer, true) - } - break - case .stickersNotModified: - if boxed { - buffer.appendInt32(-244016606) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .stickers(let hash, let stickers): - return ("stickers", [("hash", hash as Any), ("stickers", stickers as Any)]) - case .stickersNotModified: - return ("stickersNotModified", []) - } - } - - public static func parse_stickers(_ reader: BufferReader) -> Stickers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Document]? + var _2: [Api.MissingInvitee]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MissingInvitee.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.Stickers.stickers(hash: _1!, stickers: _2!) + return Api.messages.InvitedUsers.invitedUsers(updates: _1!, missingInvitees: _2!) } else { return nil } } - public static func parse_stickersNotModified(_ reader: BufferReader) -> Stickers? { - return Api.messages.Stickers.stickersNotModified - } } } public extension Api.messages { - enum TranscribedAudio: TypeConstructorDescription { - case transcribedAudio(flags: Int32, transcriptionId: Int64, text: String, trialRemainsNum: Int32?, trialRemainsUntilDate: Int32?) + enum MessageEditData: TypeConstructorDescription { + case messageEditData(flags: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .transcribedAudio(let flags, let transcriptionId, let text, let trialRemainsNum, let trialRemainsUntilDate): + case .messageEditData(let flags): if boxed { - buffer.appendInt32(-809903785) + buffer.appendInt32(649453030) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(transcriptionId, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(trialRemainsNum!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(trialRemainsUntilDate!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .transcribedAudio(let flags, let transcriptionId, let text, let trialRemainsNum, let trialRemainsUntilDate): - return ("transcribedAudio", [("flags", flags as Any), ("transcriptionId", transcriptionId as Any), ("text", text as Any), ("trialRemainsNum", trialRemainsNum as Any), ("trialRemainsUntilDate", trialRemainsUntilDate as Any)]) + case .messageEditData(let flags): + return ("messageEditData", [("flags", flags as Any)]) } } - public static func parse_transcribedAudio(_ reader: BufferReader) -> TranscribedAudio? { + public static func parse_messageEditData(_ reader: BufferReader) -> MessageEditData? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.TranscribedAudio.transcribedAudio(flags: _1!, transcriptionId: _2!, text: _3!, trialRemainsNum: _4, trialRemainsUntilDate: _5) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum TranslatedText: TypeConstructorDescription { - case translateResult(result: [Api.TextWithEntities]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .translateResult(let result): - if boxed { - buffer.appendInt32(870003448) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(result.count)) - for item in result { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .translateResult(let result): - return ("translateResult", [("result", result as Any)]) - } - } - - public static func parse_translateResult(_ reader: BufferReader) -> TranslatedText? { - var _1: [Api.TextWithEntities]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TextWithEntities.self) - } let _c1 = _1 != nil if _c1 { - return Api.messages.TranslatedText.translateResult(result: _1!) + return Api.messages.MessageEditData.messageEditData(flags: _1!) } else { return nil @@ -205,20 +83,20 @@ public extension Api.messages { } } public extension Api.messages { - enum VotesList: TypeConstructorDescription { - case votesList(flags: Int32, count: Int32, votes: [Api.MessagePeerVote], chats: [Api.Chat], users: [Api.User], nextOffset: String?) + enum MessageReactionsList: TypeConstructorDescription { + case messageReactionsList(flags: Int32, count: Int32, reactions: [Api.MessagePeerReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .votesList(let flags, let count, let votes, let chats, let users, let nextOffset): + case .messageReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset): if boxed { - buffer.appendInt32(1218005070) + buffer.appendInt32(834488621) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(votes.count)) - for item in votes { + buffer.appendInt32(Int32(reactions.count)) + for item in reactions { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -238,19 +116,19 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .votesList(let flags, let count, let votes, let chats, let users, let nextOffset): - return ("votesList", [("flags", flags as Any), ("count", count as Any), ("votes", votes as Any), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)]) + case .messageReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset): + return ("messageReactionsList", [("flags", flags as Any), ("count", count as Any), ("reactions", reactions as Any), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)]) } } - public static func parse_votesList(_ reader: BufferReader) -> VotesList? { + public static func parse_messageReactionsList(_ reader: BufferReader) -> MessageReactionsList? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: [Api.MessagePeerVote]? + var _3: [Api.MessagePeerReaction]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerVote.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self) } var _4: [Api.Chat]? if let _ = reader.readInt32() { @@ -269,7 +147,7 @@ public extension Api.messages { let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.messages.VotesList.votesList(flags: _1!, count: _2!, votes: _3!, chats: _4!, users: _5!, nextOffset: _6) + return Api.messages.MessageReactionsList.messageReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6) } else { return nil @@ -279,16 +157,20 @@ public extension Api.messages { } } public extension Api.messages { - enum WebPage: TypeConstructorDescription { - case webPage(webpage: Api.WebPage, chats: [Api.Chat], users: [Api.User]) + enum MessageViews: TypeConstructorDescription { + case messageViews(views: [Api.MessageViews], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .webPage(let webpage, let chats, let users): + case .messageViews(let views, let chats, let users): if boxed { - buffer.appendInt32(-44166467) + buffer.appendInt32(-1228606141) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(views.count)) + for item in views { + item.serialize(buffer, true) } - webpage.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -305,15 +187,15 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .webPage(let webpage, let chats, let users): - return ("webPage", [("webpage", webpage as Any), ("chats", chats as Any), ("users", users as Any)]) + case .messageViews(let views, let chats, let users): + return ("messageViews", [("views", views as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_webPage(_ reader: BufferReader) -> WebPage? { - var _1: Api.WebPage? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.WebPage + public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { + var _1: [Api.MessageViews]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageViews.self) } var _2: [Api.Chat]? if let _ = reader.readInt32() { @@ -327,7 +209,7 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.messages.WebPage.webPage(webpage: _1!, chats: _2!, users: _3!) + return Api.messages.MessageViews.messageViews(views: _1!, chats: _2!, users: _3!) } else { return nil @@ -336,69 +218,83 @@ public extension Api.messages { } } -public extension Api.payments { - enum BankCardData: TypeConstructorDescription { - case bankCardData(title: String, openUrls: [Api.BankCardOpenUrl]) +public extension Api.messages { + enum Messages: TypeConstructorDescription { + case channelMessages(flags: Int32, pts: Int32, count: Int32, offsetIdOffset: Int32?, messages: [Api.Message], topics: [Api.ForumTopic], chats: [Api.Chat], users: [Api.User]) + case messages(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + case messagesNotModified(count: Int32) + case messagesSlice(flags: Int32, count: Int32, nextRate: Int32?, offsetIdOffset: Int32?, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .bankCardData(let title, let openUrls): + case .channelMessages(let flags, let pts, let count, let offsetIdOffset, let messages, let topics, let chats, let users): if boxed { - buffer.appendInt32(1042605427) + buffer.appendInt32(-948520370) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) } - serializeString(title, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(openUrls.count)) - for item in openUrls { + buffer.appendInt32(Int32(topics.count)) + for item in topics { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) } break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .bankCardData(let title, let openUrls): - return ("bankCardData", [("title", title as Any), ("openUrls", openUrls as Any)]) - } - } - - public static func parse_bankCardData(_ reader: BufferReader) -> BankCardData? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.BankCardOpenUrl]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BankCardOpenUrl.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.payments.BankCardData.bankCardData(title: _1!, openUrls: _2!) - } - else { - return nil - } - } - - } -} -public extension Api.payments { - enum CheckedGiftCode: TypeConstructorDescription { - case checkedGiftCode(flags: Int32, fromId: Api.Peer?, giveawayMsgId: Int32?, toId: Int64?, date: Int32, months: Int32, usedDate: Int32?, chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .checkedGiftCode(let flags, let fromId, let giveawayMsgId, let toId, let date, let months, let usedDate, let chats, let users): + case .messages(let messages, let chats, let users): if boxed { - buffer.appendInt32(675942550) + buffer.appendInt32(-1938715001) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .messagesNotModified(let count): + if boxed { + buffer.appendInt32(1951620897) + } + serializeInt32(count, buffer: buffer, boxed: false) + break + case .messagesSlice(let flags, let count, let nextRate, let offsetIdOffset, let messages, let chats, let users): + if boxed { + buffer.appendInt32(978610270) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 4) != 0 {fromId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(giveawayMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(toId!, buffer: buffer, boxed: false)} - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(months, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(usedDate!, buffer: buffer, boxed: false)} + serializeInt32(count, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextRate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -415,175 +311,121 @@ public extension Api.payments { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .checkedGiftCode(let flags, let fromId, let giveawayMsgId, let toId, let date, let months, let usedDate, let chats, let users): - return ("checkedGiftCode", [("flags", flags as Any), ("fromId", fromId as Any), ("giveawayMsgId", giveawayMsgId as Any), ("toId", toId as Any), ("date", date as Any), ("months", months as Any), ("usedDate", usedDate as Any), ("chats", chats as Any), ("users", users as Any)]) + case .channelMessages(let flags, let pts, let count, let offsetIdOffset, let messages, let topics, let chats, let users): + return ("channelMessages", [("flags", flags as Any), ("pts", pts as Any), ("count", count as Any), ("offsetIdOffset", offsetIdOffset as Any), ("messages", messages as Any), ("topics", topics as Any), ("chats", chats as Any), ("users", users as Any)]) + case .messages(let messages, let chats, let users): + return ("messages", [("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .messagesNotModified(let count): + return ("messagesNotModified", [("count", count as Any)]) + case .messagesSlice(let flags, let count, let nextRate, let offsetIdOffset, let messages, let chats, let users): + return ("messagesSlice", [("flags", flags as Any), ("count", count as Any), ("nextRate", nextRate as Any), ("offsetIdOffset", offsetIdOffset as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_checkedGiftCode(_ reader: BufferReader) -> CheckedGiftCode? { + public static func parse_channelMessages(_ reader: BufferReader) -> Messages? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } } + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_3 = reader.readInt32() } - var _4: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt64() } - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_7 = reader.readInt32() } - var _8: [Api.Chat]? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } + var _5: [Api.Message]? if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) } - var _9: [Api.User]? + var _6: [Api.ForumTopic]? if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) + } + var _7: [Api.Chat]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _8: [Api.User]? + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c7 = _7 != nil let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.payments.CheckedGiftCode.checkedGiftCode(flags: _1!, fromId: _2, giveawayMsgId: _3, toId: _4, date: _5!, months: _6!, usedDate: _7, chats: _8!, users: _9!) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.messages.Messages.channelMessages(flags: _1!, pts: _2!, count: _3!, offsetIdOffset: _4, messages: _5!, topics: _6!, chats: _7!, users: _8!) } else { return nil } } - - } -} -public extension Api.payments { - enum ExportedInvoice: TypeConstructorDescription { - case exportedInvoice(url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .exportedInvoice(let url): - if boxed { - buffer.appendInt32(-1362048039) - } - serializeString(url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .exportedInvoice(let url): - return ("exportedInvoice", [("url", url as Any)]) - } - } - - public static func parse_exportedInvoice(_ reader: BufferReader) -> ExportedInvoice? { - var _1: String? - _1 = parseString(reader) + public static func parse_messages(_ reader: BufferReader) -> Messages? { + var _1: [Api.Message]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil - if _c1 { - return Api.payments.ExportedInvoice.exportedInvoice(url: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.Messages.messages(messages: _1!, chats: _2!, users: _3!) } else { return nil } } - - } -} -public extension Api.payments { - enum GiveawayInfo: TypeConstructorDescription { - case giveawayInfo(flags: Int32, startDate: Int32, joinedTooEarlyDate: Int32?, adminDisallowedChatId: Int64?, disallowedCountry: String?) - case giveawayInfoResults(flags: Int32, startDate: Int32, giftCodeSlug: String?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .giveawayInfo(let flags, let startDate, let joinedTooEarlyDate, let adminDisallowedChatId, let disallowedCountry): - if boxed { - buffer.appendInt32(1130879648) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(startDate, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(joinedTooEarlyDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt64(adminDisallowedChatId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeString(disallowedCountry!, buffer: buffer, boxed: false)} - break - case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount): - if boxed { - buffer.appendInt32(13456752) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(startDate, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(giftCodeSlug!, buffer: buffer, boxed: false)} - serializeInt32(finishDate, buffer: buffer, boxed: false) - serializeInt32(winnersCount, buffer: buffer, boxed: false) - serializeInt32(activatedCount, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .giveawayInfo(let flags, let startDate, let joinedTooEarlyDate, let adminDisallowedChatId, let disallowedCountry): - return ("giveawayInfo", [("flags", flags as Any), ("startDate", startDate as Any), ("joinedTooEarlyDate", joinedTooEarlyDate as Any), ("adminDisallowedChatId", adminDisallowedChatId as Any), ("disallowedCountry", disallowedCountry as Any)]) - case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount): - return ("giveawayInfoResults", [("flags", flags as Any), ("startDate", startDate as Any), ("giftCodeSlug", giftCodeSlug as Any), ("finishDate", finishDate as Any), ("winnersCount", winnersCount as Any), ("activatedCount", activatedCount as Any)]) - } - } - - public static func parse_giveawayInfo(_ reader: BufferReader) -> GiveawayInfo? { + public static func parse_messagesNotModified(_ reader: BufferReader) -> Messages? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } - var _4: Int64? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt64() } - var _5: String? - if Int(_1!) & Int(1 << 4) != 0 {_5 = parseString(reader) } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.payments.GiveawayInfo.giveawayInfo(flags: _1!, startDate: _2!, joinedTooEarlyDate: _3, adminDisallowedChatId: _4, disallowedCountry: _5) + if _c1 { + return Api.messages.Messages.messagesNotModified(count: _1!) } else { return nil } } - public static func parse_giveawayInfoResults(_ reader: BufferReader) -> GiveawayInfo? { + public static func parse_messagesSlice(_ reader: BufferReader) -> Messages? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() + if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } + var _5: [Api.Message]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _6: [Api.Chat]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _7: [Api.User]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.payments.GiveawayInfo.giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, finishDate: _4!, winnersCount: _5!, activatedCount: _6!) + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.messages.Messages.messagesSlice(flags: _1!, count: _2!, nextRate: _3, offsetIdOffset: _4, messages: _5!, chats: _6!, users: _7!) } else { return nil @@ -592,41 +434,20 @@ public extension Api.payments { } } -public extension Api.payments { - enum PaymentForm: TypeConstructorDescription { - case paymentForm(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, additionalMethods: [Api.PaymentFormMethod]?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: [Api.PaymentSavedCredentials]?, users: [Api.User]) +public extension Api.messages { + enum MyStickers: TypeConstructorDescription { + case myStickers(count: Int32, sets: [Api.StickerSetCovered]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users): + case .myStickers(let count, let sets): if boxed { - buffer.appendInt32(-1610250415) + buffer.appendInt32(-83926371) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(formId, buffer: buffer, boxed: false) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 5) != 0 {photo!.serialize(buffer, true)} - invoice.serialize(buffer, true) - serializeInt64(providerId, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 4) != 0 {serializeString(nativeProvider!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {nativeParams!.serialize(buffer, true)} - if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(additionalMethods!.count)) - for item in additionalMethods! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 0) != 0 {savedInfo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(savedCredentials!.count)) - for item in savedCredentials! { - item.serialize(buffer, true) - }} + serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { + buffer.appendInt32(Int32(sets.count)) + for item in sets { item.serialize(buffer, true) } break @@ -635,73 +456,22 @@ public extension Api.payments { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users): - return ("paymentForm", [("flags", flags as Any), ("formId", formId as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("providerId", providerId as Any), ("url", url as Any), ("nativeProvider", nativeProvider as Any), ("nativeParams", nativeParams as Any), ("additionalMethods", additionalMethods as Any), ("savedInfo", savedInfo as Any), ("savedCredentials", savedCredentials as Any), ("users", users as Any)]) + case .myStickers(let count, let sets): + return ("myStickers", [("count", count as Any), ("sets", sets as Any)]) } } - public static func parse_paymentForm(_ reader: BufferReader) -> PaymentForm? { + public static func parse_myStickers(_ reader: BufferReader) -> MyStickers? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Api.WebDocument? - if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WebDocument - } } - var _7: Api.Invoice? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Invoice - } - var _8: Int64? - _8 = reader.readInt64() - var _9: String? - _9 = parseString(reader) - var _10: String? - if Int(_1!) & Int(1 << 4) != 0 {_10 = parseString(reader) } - var _11: Api.DataJSON? - if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.DataJSON - } } - var _12: [Api.PaymentFormMethod]? - if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentFormMethod.self) - } } - var _13: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } } - var _14: [Api.PaymentSavedCredentials]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentSavedCredentials.self) - } } - var _15: [Api.User]? + var _2: [Api.StickerSetCovered]? if let _ = reader.readInt32() { - _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 4) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 0) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 1) == 0) || _14 != nil - let _c15 = _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.payments.PaymentForm.paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, additionalMethods: _12, savedInfo: _13, savedCredentials: _14, users: _15!) + if _c1 && _c2 { + return Api.messages.MyStickers.myStickers(count: _1!, sets: _2!) } else { return nil @@ -710,104 +480,76 @@ public extension Api.payments { } } -public extension Api.payments { - enum PaymentReceipt: TypeConstructorDescription { - case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User]) +public extension Api.messages { + enum PeerDialogs: TypeConstructorDescription { + case peerDialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): + case .peerDialogs(let dialogs, let messages, let chats, let users, let state): if boxed { - buffer.appendInt32(1891958275) + buffer.appendInt32(863093588) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(dialogs.count)) + for item in dialogs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeInt64(providerId, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} - invoice.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {shipping!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)} - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(totalAmount, buffer: buffer, boxed: false) - serializeString(credentialsTitle, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { item.serialize(buffer, true) } + state.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): - return ("paymentReceipt", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("providerId", providerId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("info", info as Any), ("shipping", shipping as Any), ("tipAmount", tipAmount as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("credentialsTitle", credentialsTitle as Any), ("users", users as Any)]) + case .peerDialogs(let dialogs, let messages, let chats, let users, let state): + return ("peerDialogs", [("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any), ("state", state as Any)]) } } - public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - var _7: Api.WebDocument? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.WebDocument - } } - var _8: Api.Invoice? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Invoice - } - var _9: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } } - var _10: Api.ShippingOption? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.ShippingOption - } } - var _11: Int64? - if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() } - var _12: String? - _12 = parseString(reader) - var _13: Int64? - _13 = reader.readInt64() - var _14: String? - _14 = parseString(reader) - var _15: [Api.User]? + public static func parse_peerDialogs(_ reader: BufferReader) -> PeerDialogs? { + var _1: [Api.Dialog]? if let _ = reader.readInt32() { - _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) + } + var _2: [Api.Message]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _5: Api.updates.State? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.updates.State } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = _12 != nil - let _c13 = _13 != nil - let _c14 = _14 != nil - let _c15 = _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.payments.PaymentReceipt.paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!) + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.PeerDialogs.peerDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!, state: _5!) } else { return nil @@ -816,56 +558,56 @@ public extension Api.payments { } } -public extension Api.payments { - indirect enum PaymentResult: TypeConstructorDescription { - case paymentResult(updates: Api.Updates) - case paymentVerificationNeeded(url: String) +public extension Api.messages { + enum PeerSettings: TypeConstructorDescription { + case peerSettings(settings: Api.PeerSettings, chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .paymentResult(let updates): + case .peerSettings(let settings, let chats, let users): if boxed { - buffer.appendInt32(1314881805) + buffer.appendInt32(1753266509) } - updates.serialize(buffer, true) - break - case .paymentVerificationNeeded(let url): - if boxed { - buffer.appendInt32(-666824391) + settings.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeString(url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .paymentResult(let updates): - return ("paymentResult", [("updates", updates as Any)]) - case .paymentVerificationNeeded(let url): - return ("paymentVerificationNeeded", [("url", url as Any)]) + case .peerSettings(let settings, let chats, let users): + return ("peerSettings", [("settings", settings as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_paymentResult(_ reader: BufferReader) -> PaymentResult? { - var _1: Api.Updates? + public static func parse_peerSettings(_ reader: BufferReader) -> PeerSettings? { + var _1: Api.PeerSettings? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Updates + _1 = Api.parse(reader, signature: signature) as? Api.PeerSettings } - let _c1 = _1 != nil - if _c1 { - return Api.payments.PaymentResult.paymentResult(updates: _1!) + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - else { - return nil + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - } - public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? { - var _1: String? - _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.payments.PaymentResult.paymentVerificationNeeded(url: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.PeerSettings.peerSettings(settings: _1!, chats: _2!, users: _3!) } else { return nil @@ -874,151 +616,280 @@ public extension Api.payments { } } -public extension Api.payments { - enum SavedInfo: TypeConstructorDescription { - case savedInfo(flags: Int32, savedInfo: Api.PaymentRequestedInfo?) +public extension Api.messages { + enum QuickReplies: TypeConstructorDescription { + case quickReplies(quickReplies: [Api.QuickReply], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + case quickRepliesNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedInfo(let flags, let savedInfo): + case .quickReplies(let quickReplies, let messages, let chats, let users): if boxed { - buffer.appendInt32(-74456004) + buffer.appendInt32(-963811691) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {savedInfo!.serialize(buffer, true)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(quickReplies.count)) + for item in quickReplies { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .quickRepliesNotModified: + if boxed { + buffer.appendInt32(1603398491) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedInfo(let flags, let savedInfo): - return ("savedInfo", [("flags", flags as Any), ("savedInfo", savedInfo as Any)]) + case .quickReplies(let quickReplies, let messages, let chats, let users): + return ("quickReplies", [("quickReplies", quickReplies as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .quickRepliesNotModified: + return ("quickRepliesNotModified", []) } } - public static func parse_savedInfo(_ reader: BufferReader) -> SavedInfo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } } + public static func parse_quickReplies(_ reader: BufferReader) -> QuickReplies? { + var _1: [Api.QuickReply]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) + } + var _2: [Api.Message]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.payments.SavedInfo.savedInfo(flags: _1!, savedInfo: _2) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.QuickReplies.quickReplies(quickReplies: _1!, messages: _2!, chats: _3!, users: _4!) } else { return nil } } + public static func parse_quickRepliesNotModified(_ reader: BufferReader) -> QuickReplies? { + return Api.messages.QuickReplies.quickRepliesNotModified + } } } -public extension Api.payments { - enum ValidatedRequestedInfo: TypeConstructorDescription { - case validatedRequestedInfo(flags: Int32, id: String?, shippingOptions: [Api.ShippingOption]?) +public extension Api.messages { + enum Reactions: TypeConstructorDescription { + case reactions(hash: Int64, reactions: [Api.Reaction]) + case reactionsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .validatedRequestedInfo(let flags, let id, let shippingOptions): + case .reactions(let hash, let reactions): if boxed { - buffer.appendInt32(-784000893) + buffer.appendInt32(-352454890) } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(id!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(shippingOptions!.count)) - for item in shippingOptions! { + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(reactions.count)) + for item in reactions { item.serialize(buffer, true) - }} + } + break + case .reactionsNotModified: + if boxed { + buffer.appendInt32(-1334846497) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .validatedRequestedInfo(let flags, let id, let shippingOptions): - return ("validatedRequestedInfo", [("flags", flags as Any), ("id", id as Any), ("shippingOptions", shippingOptions as Any)]) + case .reactions(let hash, let reactions): + return ("reactions", [("hash", hash as Any), ("reactions", reactions as Any)]) + case .reactionsNotModified: + return ("reactionsNotModified", []) } } - public static func parse_validatedRequestedInfo(_ reader: BufferReader) -> ValidatedRequestedInfo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - var _3: [Api.ShippingOption]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ShippingOption.self) - } } + public static func parse_reactions(_ reader: BufferReader) -> Reactions? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Reaction]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.ValidatedRequestedInfo.validatedRequestedInfo(flags: _1!, id: _2, shippingOptions: _3) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.Reactions.reactions(hash: _1!, reactions: _2!) } else { return nil } } + public static func parse_reactionsNotModified(_ reader: BufferReader) -> Reactions? { + return Api.messages.Reactions.reactionsNotModified + } } } -public extension Api.phone { - enum ExportedGroupCallInvite: TypeConstructorDescription { - case exportedGroupCallInvite(link: String) +public extension Api.messages { + enum RecentStickers: TypeConstructorDescription { + case recentStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32]) + case recentStickersNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedGroupCallInvite(let link): + case .recentStickers(let hash, let packs, let stickers, let dates): if boxed { - buffer.appendInt32(541839704) + buffer.appendInt32(-1999405994) } - serializeString(link, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(packs.count)) + for item in packs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers.count)) + for item in stickers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(dates.count)) + for item in dates { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .recentStickersNotModified: + if boxed { + buffer.appendInt32(186120336) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .exportedGroupCallInvite(let link): - return ("exportedGroupCallInvite", [("link", link as Any)]) + case .recentStickers(let hash, let packs, let stickers, let dates): + return ("recentStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any), ("dates", dates as Any)]) + case .recentStickersNotModified: + return ("recentStickersNotModified", []) } } - public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? { - var _1: String? - _1 = parseString(reader) + public static func parse_recentStickers(_ reader: BufferReader) -> RecentStickers? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.StickerPack]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + } + var _3: [Api.Document]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + var _4: [Int32]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } let _c1 = _1 != nil - if _c1 { - return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(link: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.RecentStickers.recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!) } else { return nil } } + public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? { + return Api.messages.RecentStickers.recentStickersNotModified + } } } -public extension Api.phone { - enum GroupCall: TypeConstructorDescription { - case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User]) +public extension Api.messages { + enum SavedDialogs: TypeConstructorDescription { + case savedDialogs(dialogs: [Api.SavedDialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + case savedDialogsNotModified(count: Int32) + case savedDialogsSlice(count: Int32, dialogs: [Api.SavedDialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users): + case .savedDialogs(let dialogs, let messages, let chats, let users): + if boxed { + buffer.appendInt32(-130358751) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(dialogs.count)) + for item in dialogs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .savedDialogsNotModified(let count): if boxed { - buffer.appendInt32(-1636664659) + buffer.appendInt32(-1071681560) + } + serializeInt32(count, buffer: buffer, boxed: false) + break + case .savedDialogsSlice(let count, let dialogs, let messages, let chats, let users): + if boxed { + buffer.appendInt32(1153080793) + } + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(dialogs.count)) + for item in dialogs { + item.serialize(buffer, true) } - call.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(participants.count)) - for item in participants { + buffer.appendInt32(Int32(messages.count)) + for item in messages { item.serialize(buffer, true) } - serializeString(participantsNextOffset, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -1035,22 +906,65 @@ public extension Api.phone { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users): - return ("groupCall", [("call", call as Any), ("participants", participants as Any), ("participantsNextOffset", participantsNextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) + case .savedDialogs(let dialogs, let messages, let chats, let users): + return ("savedDialogs", [("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .savedDialogsNotModified(let count): + return ("savedDialogsNotModified", [("count", count as Any)]) + case .savedDialogsSlice(let count, let dialogs, let messages, let chats, let users): + return ("savedDialogsSlice", [("count", count as Any), ("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? { - var _1: Api.GroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.GroupCall + public static func parse_savedDialogs(_ reader: BufferReader) -> SavedDialogs? { + var _1: [Api.SavedDialog]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedDialog.self) + } + var _2: [Api.Message]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.SavedDialogs.savedDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!) + } + else { + return nil + } + } + public static func parse_savedDialogsNotModified(_ reader: BufferReader) -> SavedDialogs? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.messages.SavedDialogs.savedDialogsNotModified(count: _1!) + } + else { + return nil + } + } + public static func parse_savedDialogsSlice(_ reader: BufferReader) -> SavedDialogs? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.SavedDialog]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedDialog.self) } - var _2: [Api.GroupCallParticipant]? + var _3: [Api.Message]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) } - var _3: String? - _3 = parseString(reader) var _4: [Api.Chat]? if let _ = reader.readInt32() { _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) @@ -1065,7 +979,7 @@ public extension Api.phone { let _c4 = _4 != nil let _c5 = _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!) + return Api.messages.SavedDialogs.savedDialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!) } else { return nil @@ -1074,154 +988,160 @@ public extension Api.phone { } } -public extension Api.phone { - enum GroupCallStreamChannels: TypeConstructorDescription { - case groupCallStreamChannels(channels: [Api.GroupCallStreamChannel]) +public extension Api.messages { + enum SavedGifs: TypeConstructorDescription { + case savedGifs(hash: Int64, gifs: [Api.Document]) + case savedGifsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupCallStreamChannels(let channels): + case .savedGifs(let hash, let gifs): if boxed { - buffer.appendInt32(-790330702) + buffer.appendInt32(-2069878259) } + serializeInt64(hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(channels.count)) - for item in channels { + buffer.appendInt32(Int32(gifs.count)) + for item in gifs { item.serialize(buffer, true) } + break + case .savedGifsNotModified: + if boxed { + buffer.appendInt32(-402498398) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .groupCallStreamChannels(let channels): - return ("groupCallStreamChannels", [("channels", channels as Any)]) + case .savedGifs(let hash, let gifs): + return ("savedGifs", [("hash", hash as Any), ("gifs", gifs as Any)]) + case .savedGifsNotModified: + return ("savedGifsNotModified", []) } } - public static func parse_groupCallStreamChannels(_ reader: BufferReader) -> GroupCallStreamChannels? { - var _1: [Api.GroupCallStreamChannel]? + public static func parse_savedGifs(_ reader: BufferReader) -> SavedGifs? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Document]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallStreamChannel.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil - if _c1 { - return Api.phone.GroupCallStreamChannels.groupCallStreamChannels(channels: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.SavedGifs.savedGifs(hash: _1!, gifs: _2!) } else { return nil } } + public static func parse_savedGifsNotModified(_ reader: BufferReader) -> SavedGifs? { + return Api.messages.SavedGifs.savedGifsNotModified + } } } -public extension Api.phone { - enum GroupCallStreamRtmpUrl: TypeConstructorDescription { - case groupCallStreamRtmpUrl(url: String, key: String) +public extension Api.messages { + enum SavedReactionTags: TypeConstructorDescription { + case savedReactionTags(tags: [Api.SavedReactionTag], hash: Int64) + case savedReactionTagsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupCallStreamRtmpUrl(let url, let key): + case .savedReactionTags(let tags, let hash): if boxed { - buffer.appendInt32(767505458) + buffer.appendInt32(844731658) } - serializeString(url, buffer: buffer, boxed: false) - serializeString(key, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(tags.count)) + for item in tags { + item.serialize(buffer, true) + } + serializeInt64(hash, buffer: buffer, boxed: false) + break + case .savedReactionTagsNotModified: + if boxed { + buffer.appendInt32(-2003084817) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .groupCallStreamRtmpUrl(let url, let key): - return ("groupCallStreamRtmpUrl", [("url", url as Any), ("key", key as Any)]) + case .savedReactionTags(let tags, let hash): + return ("savedReactionTags", [("tags", tags as Any), ("hash", hash as Any)]) + case .savedReactionTagsNotModified: + return ("savedReactionTagsNotModified", []) } } - public static func parse_groupCallStreamRtmpUrl(_ reader: BufferReader) -> GroupCallStreamRtmpUrl? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) + public static func parse_savedReactionTags(_ reader: BufferReader) -> SavedReactionTags? { + var _1: [Api.SavedReactionTag]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedReactionTag.self) + } + var _2: Int64? + _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.phone.GroupCallStreamRtmpUrl.groupCallStreamRtmpUrl(url: _1!, key: _2!) + return Api.messages.SavedReactionTags.savedReactionTags(tags: _1!, hash: _2!) } else { return nil } } + public static func parse_savedReactionTagsNotModified(_ reader: BufferReader) -> SavedReactionTags? { + return Api.messages.SavedReactionTags.savedReactionTagsNotModified + } } } -public extension Api.phone { - enum GroupParticipants: TypeConstructorDescription { - case groupParticipants(count: Int32, participants: [Api.GroupCallParticipant], nextOffset: String, chats: [Api.Chat], users: [Api.User], version: Int32) +public extension Api.messages { + enum SearchCounter: TypeConstructorDescription { + case searchCounter(flags: Int32, filter: Api.MessagesFilter, count: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupParticipants(let count, let participants, let nextOffset, let chats, let users, let version): + case .searchCounter(let flags, let filter, let count): if boxed { - buffer.appendInt32(-193506890) + buffer.appendInt32(-398136321) } + serializeInt32(flags, buffer: buffer, boxed: false) + filter.serialize(buffer, true) serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(participants.count)) - for item in participants { - item.serialize(buffer, true) - } - serializeString(nextOffset, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - serializeInt32(version, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .groupParticipants(let count, let participants, let nextOffset, let chats, let users, let version): - return ("groupParticipants", [("count", count as Any), ("participants", participants as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any), ("version", version as Any)]) + case .searchCounter(let flags, let filter, let count): + return ("searchCounter", [("flags", flags as Any), ("filter", filter as Any), ("count", count as Any)]) } } - public static func parse_groupParticipants(_ reader: BufferReader) -> GroupParticipants? { + public static func parse_searchCounter(_ reader: BufferReader) -> SearchCounter? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.GroupCallParticipant]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) - } - var _3: String? - _3 = parseString(reader) - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + var _2: Api.MessagesFilter? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.MessagesFilter } - var _6: Int32? - _6 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.phone.GroupParticipants.groupParticipants(count: _1!, participants: _2!, nextOffset: _3!, chats: _4!, users: _5!, version: _6!) + if _c1 && _c2 && _c3 { + return Api.messages.SearchCounter.searchCounter(flags: _1!, filter: _2!, count: _3!) } else { return nil @@ -1230,19 +1150,29 @@ public extension Api.phone { } } -public extension Api.phone { - enum JoinAsPeers: TypeConstructorDescription { - case joinAsPeers(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) +public extension Api.messages { + enum SearchResultsCalendar: TypeConstructorDescription { + case searchResultsCalendar(flags: Int32, count: Int32, minDate: Int32, minMsgId: Int32, offsetIdOffset: Int32?, periods: [Api.SearchResultsCalendarPeriod], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .joinAsPeers(let peers, let chats, let users): + case .searchResultsCalendar(let flags, let count, let minDate, let minMsgId, let offsetIdOffset, let periods, let messages, let chats, let users): if boxed { - buffer.appendInt32(-1343921601) + buffer.appendInt32(343859772) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(minMsgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(periods.count)) + for item in periods { + item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { + buffer.appendInt32(Int32(messages.count)) + for item in messages { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -1261,77 +1191,49 @@ public extension Api.phone { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .joinAsPeers(let peers, let chats, let users): - return ("joinAsPeers", [("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)]) + case .searchResultsCalendar(let flags, let count, let minDate, let minMsgId, let offsetIdOffset, let periods, let messages, let chats, let users): + return ("searchResultsCalendar", [("flags", flags as Any), ("count", count as Any), ("minDate", minDate as Any), ("minMsgId", minMsgId as Any), ("offsetIdOffset", offsetIdOffset as Any), ("periods", periods as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_joinAsPeers(_ reader: BufferReader) -> JoinAsPeers? { - var _1: [Api.Peer]? + public static func parse_searchResultsCalendar(_ reader: BufferReader) -> SearchResultsCalendar? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } + var _6: [Api.SearchResultsCalendarPeriod]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsCalendarPeriod.self) } - var _2: [Api.Chat]? + var _7: [Api.Message]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) } - var _3: [Api.User]? + var _8: [Api.Chat]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.phone.JoinAsPeers.joinAsPeers(peers: _1!, chats: _2!, users: _3!) - } - else { - return nil - } - } - - } -} -public extension Api.phone { - enum PhoneCall: TypeConstructorDescription { - case phoneCall(phoneCall: Api.PhoneCall, users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .phoneCall(let phoneCall, let users): - if boxed { - buffer.appendInt32(-326966976) - } - phoneCall.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .phoneCall(let phoneCall, let users): - return ("phoneCall", [("phoneCall", phoneCall as Any), ("users", users as Any)]) - } - } - - public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? { - var _1: Api.PhoneCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _2: [Api.User]? + var _9: [Api.User]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.phone.PhoneCall.phoneCall(phoneCall: _1!, users: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.messages.SearchResultsCalendar.searchResultsCalendar(flags: _1!, count: _2!, minDate: _3!, minMsgId: _4!, offsetIdOffset: _5, periods: _6!, messages: _7!, chats: _8!, users: _9!) } else { return nil @@ -1340,20 +1242,20 @@ public extension Api.phone { } } -public extension Api.photos { - enum Photo: TypeConstructorDescription { - case photo(photo: Api.Photo, users: [Api.User]) +public extension Api.messages { + enum SearchResultsPositions: TypeConstructorDescription { + case searchResultsPositions(count: Int32, positions: [Api.SearchResultsPosition]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .photo(let photo, let users): + case .searchResultsPositions(let count, let positions): if boxed { - buffer.appendInt32(539045032) + buffer.appendInt32(1404185519) } - photo.serialize(buffer, true) + serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { + buffer.appendInt32(Int32(positions.count)) + for item in positions { item.serialize(buffer, true) } break @@ -1362,24 +1264,22 @@ public extension Api.photos { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .photo(let photo, let users): - return ("photo", [("photo", photo as Any), ("users", users as Any)]) + case .searchResultsPositions(let count, let positions): + return ("searchResultsPositions", [("count", count as Any), ("positions", positions as Any)]) } } - public static func parse_photo(_ reader: BufferReader) -> Photo? { - var _1: Api.Photo? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Photo - } - var _2: [Api.User]? + public static func parse_searchResultsPositions(_ reader: BufferReader) -> SearchResultsPositions? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.SearchResultsPosition]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsPosition.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.photos.Photo.photo(photo: _1!, users: _2!) + return Api.messages.SearchResultsPositions.searchResultsPositions(count: _1!, positions: _2!) } else { return nil @@ -1388,90 +1288,60 @@ public extension Api.photos { } } -public extension Api.photos { - enum Photos: TypeConstructorDescription { - case photos(photos: [Api.Photo], users: [Api.User]) - case photosSlice(count: Int32, photos: [Api.Photo], users: [Api.User]) +public extension Api.messages { + enum SentEncryptedMessage: TypeConstructorDescription { + case sentEncryptedFile(date: Int32, file: Api.EncryptedFile) + case sentEncryptedMessage(date: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .photos(let photos, let users): + case .sentEncryptedFile(let date, let file): if boxed { - buffer.appendInt32(-1916114267) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(photos.count)) - for item in photos { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(-1802240206) } + serializeInt32(date, buffer: buffer, boxed: false) + file.serialize(buffer, true) break - case .photosSlice(let count, let photos, let users): + case .sentEncryptedMessage(let date): if boxed { - buffer.appendInt32(352657236) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(photos.count)) - for item in photos { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) + buffer.appendInt32(1443858741) } + serializeInt32(date, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .photos(let photos, let users): - return ("photos", [("photos", photos as Any), ("users", users as Any)]) - case .photosSlice(let count, let photos, let users): - return ("photosSlice", [("count", count as Any), ("photos", photos as Any), ("users", users as Any)]) + case .sentEncryptedFile(let date, let file): + return ("sentEncryptedFile", [("date", date as Any), ("file", file as Any)]) + case .sentEncryptedMessage(let date): + return ("sentEncryptedMessage", [("date", date as Any)]) } } - public static func parse_photos(_ reader: BufferReader) -> Photos? { - var _1: [Api.Photo]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + public static func parse_sentEncryptedFile(_ reader: BufferReader) -> SentEncryptedMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.EncryptedFile? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.EncryptedFile } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.photos.Photos.photos(photos: _1!, users: _2!) + return Api.messages.SentEncryptedMessage.sentEncryptedFile(date: _1!, file: _2!) } else { return nil } } - public static func parse_photosSlice(_ reader: BufferReader) -> Photos? { + public static func parse_sentEncryptedMessage(_ reader: BufferReader) -> SentEncryptedMessage? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.Photo]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.photos.Photos.photosSlice(count: _1!, photos: _2!, users: _3!) + if _c1 { + return Api.messages.SentEncryptedMessage.sentEncryptedMessage(date: _1!) } else { return nil @@ -1480,215 +1350,165 @@ public extension Api.photos { } } -public extension Api.premium { - enum BoostsList: TypeConstructorDescription { - case boostsList(flags: Int32, count: Int32, boosts: [Api.Boost], nextOffset: String?, users: [Api.User]) +public extension Api.messages { + enum SponsoredMessages: TypeConstructorDescription { + case sponsoredMessages(flags: Int32, postsBetween: Int32?, messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User]) + case sponsoredMessagesEmpty public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .boostsList(let flags, let count, let boosts, let nextOffset, let users): + case .sponsoredMessages(let flags, let postsBetween, let messages, let chats, let users): if boxed { - buffer.appendInt32(-2030542532) + buffer.appendInt32(-907141753) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(postsBetween!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) - buffer.appendInt32(Int32(boosts.count)) - for item in boosts { + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { item.serialize(buffer, true) } + break + case .sponsoredMessagesEmpty: + if boxed { + buffer.appendInt32(406407439) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .boostsList(let flags, let count, let boosts, let nextOffset, let users): - return ("boostsList", [("flags", flags as Any), ("count", count as Any), ("boosts", boosts as Any), ("nextOffset", nextOffset as Any), ("users", users as Any)]) + case .sponsoredMessages(let flags, let postsBetween, let messages, let chats, let users): + return ("sponsoredMessages", [("flags", flags as Any), ("postsBetween", postsBetween as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .sponsoredMessagesEmpty: + return ("sponsoredMessagesEmpty", []) } } - public static func parse_boostsList(_ reader: BufferReader) -> BoostsList? { + public static func parse_sponsoredMessages(_ reader: BufferReader) -> SponsoredMessages? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.Boost]? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _3: [Api.SponsoredMessage]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Boost.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessage.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } var _5: [Api.User]? if let _ = reader.readInt32() { _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = _4 != nil let _c5 = _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.premium.BoostsList.boostsList(flags: _1!, count: _2!, boosts: _3!, nextOffset: _4, users: _5!) + return Api.messages.SponsoredMessages.sponsoredMessages(flags: _1!, postsBetween: _2, messages: _3!, chats: _4!, users: _5!) } else { return nil } } - - } -} -public extension Api.premium { - enum BoostsStatus: TypeConstructorDescription { - case boostsStatus(flags: Int32, level: Int32, currentLevelBoosts: Int32, boosts: Int32, giftBoosts: Int32?, nextLevelBoosts: Int32?, premiumAudience: Api.StatsPercentValue?, boostUrl: String, prepaidGiveaways: [Api.PrepaidGiveaway]?, myBoostSlots: [Int32]?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let giftBoosts, let nextLevelBoosts, let premiumAudience, let boostUrl, let prepaidGiveaways, let myBoostSlots): - if boxed { - buffer.appendInt32(1230586490) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(level, buffer: buffer, boxed: false) - serializeInt32(currentLevelBoosts, buffer: buffer, boxed: false) - serializeInt32(boosts, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(giftBoosts!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextLevelBoosts!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {premiumAudience!.serialize(buffer, true)} - serializeString(boostUrl, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(prepaidGiveaways!.count)) - for item in prepaidGiveaways! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(myBoostSlots!.count)) - for item in myBoostSlots! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let giftBoosts, let nextLevelBoosts, let premiumAudience, let boostUrl, let prepaidGiveaways, let myBoostSlots): - return ("boostsStatus", [("flags", flags as Any), ("level", level as Any), ("currentLevelBoosts", currentLevelBoosts as Any), ("boosts", boosts as Any), ("giftBoosts", giftBoosts as Any), ("nextLevelBoosts", nextLevelBoosts as Any), ("premiumAudience", premiumAudience as Any), ("boostUrl", boostUrl as Any), ("prepaidGiveaways", prepaidGiveaways as Any), ("myBoostSlots", myBoostSlots as Any)]) - } - } - - public static func parse_boostsStatus(_ reader: BufferReader) -> BoostsStatus? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } - var _6: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } - var _7: Api.StatsPercentValue? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue - } } - var _8: String? - _8 = parseString(reader) - var _9: [Api.PrepaidGiveaway]? - if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrepaidGiveaway.self) - } } - var _10: [Int32]? - if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.premium.BoostsStatus.boostsStatus(flags: _1!, level: _2!, currentLevelBoosts: _3!, boosts: _4!, giftBoosts: _5, nextLevelBoosts: _6, premiumAudience: _7, boostUrl: _8!, prepaidGiveaways: _9, myBoostSlots: _10) - } - else { - return nil - } + public static func parse_sponsoredMessagesEmpty(_ reader: BufferReader) -> SponsoredMessages? { + return Api.messages.SponsoredMessages.sponsoredMessagesEmpty } } } -public extension Api.premium { - enum MyBoosts: TypeConstructorDescription { - case myBoosts(myBoosts: [Api.MyBoost], chats: [Api.Chat], users: [Api.User]) +public extension Api.messages { + enum StickerSet: TypeConstructorDescription { + case stickerSet(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) + case stickerSetNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .myBoosts(let myBoosts, let chats, let users): + case .stickerSet(let set, let packs, let keywords, let documents): if boxed { - buffer.appendInt32(-1696454430) + buffer.appendInt32(1846886166) } + set.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(myBoosts.count)) - for item in myBoosts { + buffer.appendInt32(Int32(packs.count)) + for item in packs { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(keywords.count)) + for item in keywords { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { + buffer.appendInt32(Int32(documents.count)) + for item in documents { item.serialize(buffer, true) } + break + case .stickerSetNotModified: + if boxed { + buffer.appendInt32(-738646805) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .myBoosts(let myBoosts, let chats, let users): - return ("myBoosts", [("myBoosts", myBoosts as Any), ("chats", chats as Any), ("users", users as Any)]) + case .stickerSet(let set, let packs, let keywords, let documents): + return ("stickerSet", [("set", set as Any), ("packs", packs as Any), ("keywords", keywords as Any), ("documents", documents as Any)]) + case .stickerSetNotModified: + return ("stickerSetNotModified", []) } } - public static func parse_myBoosts(_ reader: BufferReader) -> MyBoosts? { - var _1: [Api.MyBoost]? + public static func parse_stickerSet(_ reader: BufferReader) -> StickerSet? { + var _1: Api.StickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StickerSet + } + var _2: [Api.StickerPack]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MyBoost.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) } - var _2: [Api.Chat]? + var _3: [Api.StickerKeyword]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self) } - var _3: [Api.User]? + var _4: [Api.Document]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.premium.MyBoosts.myBoosts(myBoosts: _1!, chats: _2!, users: _3!) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.StickerSet.stickerSet(set: _1!, packs: _2!, keywords: _3!, documents: _4!) } else { return nil } } + public static func parse_stickerSetNotModified(_ reader: BufferReader) -> StickerSet? { + return Api.messages.StickerSet.stickerSetNotModified + } } } diff --git a/submodules/TelegramApi/Sources/Api33.swift b/submodules/TelegramApi/Sources/Api33.swift index 5e8eefe5eaf..da4798d2eef 100644 --- a/submodules/TelegramApi/Sources/Api33.swift +++ b/submodules/TelegramApi/Sources/Api33.swift @@ -1,199 +1,159 @@ -public extension Api.smsjobs { - enum EligibilityToJoin: TypeConstructorDescription { - case eligibleToJoin(termsUrl: String, monthlySentSms: Int32) +public extension Api.messages { + enum StickerSetInstallResult: TypeConstructorDescription { + case stickerSetInstallResultArchive(sets: [Api.StickerSetCovered]) + case stickerSetInstallResultSuccess public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .eligibleToJoin(let termsUrl, let monthlySentSms): + case .stickerSetInstallResultArchive(let sets): if boxed { - buffer.appendInt32(-594852657) + buffer.appendInt32(904138920) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sets.count)) + for item in sets { + item.serialize(buffer, true) } - serializeString(termsUrl, buffer: buffer, boxed: false) - serializeInt32(monthlySentSms, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .eligibleToJoin(let termsUrl, let monthlySentSms): - return ("eligibleToJoin", [("termsUrl", termsUrl as Any), ("monthlySentSms", monthlySentSms as Any)]) - } - } - - public static func parse_eligibleToJoin(_ reader: BufferReader) -> EligibilityToJoin? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.smsjobs.EligibilityToJoin.eligibleToJoin(termsUrl: _1!, monthlySentSms: _2!) - } - else { - return nil - } - } - - } -} -public extension Api.smsjobs { - enum Status: TypeConstructorDescription { - case status(flags: Int32, recentSent: Int32, recentSince: Int32, recentRemains: Int32, totalSent: Int32, totalSince: Int32, lastGiftSlug: String?, termsUrl: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .status(let flags, let recentSent, let recentSince, let recentRemains, let totalSent, let totalSince, let lastGiftSlug, let termsUrl): + case .stickerSetInstallResultSuccess: if boxed { - buffer.appendInt32(720277905) + buffer.appendInt32(946083368) } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(recentSent, buffer: buffer, boxed: false) - serializeInt32(recentSince, buffer: buffer, boxed: false) - serializeInt32(recentRemains, buffer: buffer, boxed: false) - serializeInt32(totalSent, buffer: buffer, boxed: false) - serializeInt32(totalSince, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(lastGiftSlug!, buffer: buffer, boxed: false)} - serializeString(termsUrl, buffer: buffer, boxed: false) + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .status(let flags, let recentSent, let recentSince, let recentRemains, let totalSent, let totalSince, let lastGiftSlug, let termsUrl): - return ("status", [("flags", flags as Any), ("recentSent", recentSent as Any), ("recentSince", recentSince as Any), ("recentRemains", recentRemains as Any), ("totalSent", totalSent as Any), ("totalSince", totalSince as Any), ("lastGiftSlug", lastGiftSlug as Any), ("termsUrl", termsUrl as Any)]) + case .stickerSetInstallResultArchive(let sets): + return ("stickerSetInstallResultArchive", [("sets", sets as Any)]) + case .stickerSetInstallResultSuccess: + return ("stickerSetInstallResultSuccess", []) } } - public static func parse_status(_ reader: BufferReader) -> Status? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } - var _8: String? - _8 = parseString(reader) + public static func parse_stickerSetInstallResultArchive(_ reader: BufferReader) -> StickerSetInstallResult? { + var _1: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.smsjobs.Status.status(flags: _1!, recentSent: _2!, recentSince: _3!, recentRemains: _4!, totalSent: _5!, totalSince: _6!, lastGiftSlug: _7, termsUrl: _8!) + if _c1 { + return Api.messages.StickerSetInstallResult.stickerSetInstallResultArchive(sets: _1!) } else { return nil } } + public static func parse_stickerSetInstallResultSuccess(_ reader: BufferReader) -> StickerSetInstallResult? { + return Api.messages.StickerSetInstallResult.stickerSetInstallResultSuccess + } } } -public extension Api.stats { - enum BroadcastRevenueStats: TypeConstructorDescription { - case broadcastRevenueStats(topHoursGraph: Api.StatsGraph, revenueGraph: Api.StatsGraph, balances: Api.BroadcastRevenueBalances, usdRate: Double) +public extension Api.messages { + enum Stickers: TypeConstructorDescription { + case stickers(hash: Int64, stickers: [Api.Document]) + case stickersNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .broadcastRevenueStats(let topHoursGraph, let revenueGraph, let balances, let usdRate): + case .stickers(let hash, let stickers): if boxed { - buffer.appendInt32(1409802903) + buffer.appendInt32(816245886) } - topHoursGraph.serialize(buffer, true) - revenueGraph.serialize(buffer, true) - balances.serialize(buffer, true) - serializeDouble(usdRate, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers.count)) + for item in stickers { + item.serialize(buffer, true) + } + break + case .stickersNotModified: + if boxed { + buffer.appendInt32(-244016606) + } + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .broadcastRevenueStats(let topHoursGraph, let revenueGraph, let balances, let usdRate): - return ("broadcastRevenueStats", [("topHoursGraph", topHoursGraph as Any), ("revenueGraph", revenueGraph as Any), ("balances", balances as Any), ("usdRate", usdRate as Any)]) + case .stickers(let hash, let stickers): + return ("stickers", [("hash", hash as Any), ("stickers", stickers as Any)]) + case .stickersNotModified: + return ("stickersNotModified", []) } } - public static func parse_broadcastRevenueStats(_ reader: BufferReader) -> BroadcastRevenueStats? { - var _1: Api.StatsGraph? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _2: Api.StatsGraph? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _3: Api.BroadcastRevenueBalances? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.BroadcastRevenueBalances + public static func parse_stickers(_ reader: BufferReader) -> Stickers? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Document]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } - var _4: Double? - _4 = reader.readDouble() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.stats.BroadcastRevenueStats.broadcastRevenueStats(topHoursGraph: _1!, revenueGraph: _2!, balances: _3!, usdRate: _4!) + if _c1 && _c2 { + return Api.messages.Stickers.stickers(hash: _1!, stickers: _2!) } else { return nil } } + public static func parse_stickersNotModified(_ reader: BufferReader) -> Stickers? { + return Api.messages.Stickers.stickersNotModified + } } } -public extension Api.stats { - enum BroadcastRevenueTransactions: TypeConstructorDescription { - case broadcastRevenueTransactions(count: Int32, transactions: [Api.BroadcastRevenueTransaction]) +public extension Api.messages { + enum TranscribedAudio: TypeConstructorDescription { + case transcribedAudio(flags: Int32, transcriptionId: Int64, text: String, trialRemainsNum: Int32?, trialRemainsUntilDate: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .broadcastRevenueTransactions(let count, let transactions): + case .transcribedAudio(let flags, let transcriptionId, let text, let trialRemainsNum, let trialRemainsUntilDate): if boxed { - buffer.appendInt32(-2028632986) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(transactions.count)) - for item in transactions { - item.serialize(buffer, true) + buffer.appendInt32(-809903785) } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(transcriptionId, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(trialRemainsNum!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(trialRemainsUntilDate!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .broadcastRevenueTransactions(let count, let transactions): - return ("broadcastRevenueTransactions", [("count", count as Any), ("transactions", transactions as Any)]) + case .transcribedAudio(let flags, let transcriptionId, let text, let trialRemainsNum, let trialRemainsUntilDate): + return ("transcribedAudio", [("flags", flags as Any), ("transcriptionId", transcriptionId as Any), ("text", text as Any), ("trialRemainsNum", trialRemainsNum as Any), ("trialRemainsUntilDate", trialRemainsUntilDate as Any)]) } } - public static func parse_broadcastRevenueTransactions(_ reader: BufferReader) -> BroadcastRevenueTransactions? { + public static func parse_transcribedAudio(_ reader: BufferReader) -> TranscribedAudio? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.BroadcastRevenueTransaction]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BroadcastRevenueTransaction.self) - } + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + var _5: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stats.BroadcastRevenueTransactions.broadcastRevenueTransactions(count: _1!, transactions: _2!) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.TranscribedAudio.transcribedAudio(flags: _1!, transcriptionId: _2!, text: _3!, trialRemainsNum: _4, trialRemainsUntilDate: _5) } else { return nil @@ -202,34 +162,40 @@ public extension Api.stats { } } -public extension Api.stats { - enum BroadcastRevenueWithdrawalUrl: TypeConstructorDescription { - case broadcastRevenueWithdrawalUrl(url: String) +public extension Api.messages { + enum TranslatedText: TypeConstructorDescription { + case translateResult(result: [Api.TextWithEntities]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .broadcastRevenueWithdrawalUrl(let url): + case .translateResult(let result): if boxed { - buffer.appendInt32(-328886473) + buffer.appendInt32(870003448) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(result.count)) + for item in result { + item.serialize(buffer, true) } - serializeString(url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .broadcastRevenueWithdrawalUrl(let url): - return ("broadcastRevenueWithdrawalUrl", [("url", url as Any)]) + case .translateResult(let result): + return ("translateResult", [("result", result as Any)]) } } - public static func parse_broadcastRevenueWithdrawalUrl(_ reader: BufferReader) -> BroadcastRevenueWithdrawalUrl? { - var _1: String? - _1 = parseString(reader) + public static func parse_translateResult(_ reader: BufferReader) -> TranslatedText? { + var _1: [Api.TextWithEntities]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TextWithEntities.self) + } let _c1 = _1 != nil if _c1 { - return Api.stats.BroadcastRevenueWithdrawalUrl.broadcastRevenueWithdrawalUrl(url: _1!) + return Api.messages.TranslatedText.translateResult(result: _1!) } else { return nil @@ -238,166 +204,72 @@ public extension Api.stats { } } -public extension Api.stats { - enum BroadcastStats: TypeConstructorDescription { - case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, reactionsPerPost: Api.StatsAbsValueAndPrev, viewsPerStory: Api.StatsAbsValueAndPrev, sharesPerStory: Api.StatsAbsValueAndPrev, reactionsPerStory: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph, ivInteractionsGraph: Api.StatsGraph, viewsBySourceGraph: Api.StatsGraph, newFollowersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph, storyInteractionsGraph: Api.StatsGraph, storyReactionsByEmotionGraph: Api.StatsGraph, recentPostsInteractions: [Api.PostInteractionCounters]) +public extension Api.messages { + enum VotesList: TypeConstructorDescription { + case votesList(flags: Int32, count: Int32, votes: [Api.MessagePeerVote], chats: [Api.Chat], users: [Api.User], nextOffset: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let reactionsPerPost, let viewsPerStory, let sharesPerStory, let reactionsPerStory, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let reactionsByEmotionGraph, let storyInteractionsGraph, let storyReactionsByEmotionGraph, let recentPostsInteractions): + case .votesList(let flags, let count, let votes, let chats, let users, let nextOffset): if boxed { - buffer.appendInt32(963421692) - } - period.serialize(buffer, true) - followers.serialize(buffer, true) - viewsPerPost.serialize(buffer, true) - sharesPerPost.serialize(buffer, true) - reactionsPerPost.serialize(buffer, true) - viewsPerStory.serialize(buffer, true) - sharesPerStory.serialize(buffer, true) - reactionsPerStory.serialize(buffer, true) - enabledNotifications.serialize(buffer, true) - growthGraph.serialize(buffer, true) - followersGraph.serialize(buffer, true) - muteGraph.serialize(buffer, true) - topHoursGraph.serialize(buffer, true) - interactionsGraph.serialize(buffer, true) - ivInteractionsGraph.serialize(buffer, true) - viewsBySourceGraph.serialize(buffer, true) - newFollowersBySourceGraph.serialize(buffer, true) - languagesGraph.serialize(buffer, true) - reactionsByEmotionGraph.serialize(buffer, true) - storyInteractionsGraph.serialize(buffer, true) - storyReactionsByEmotionGraph.serialize(buffer, true) + buffer.appendInt32(1218005070) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(votes.count)) + for item in votes { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentPostsInteractions.count)) - for item in recentPostsInteractions { + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) } + if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let reactionsPerPost, let viewsPerStory, let sharesPerStory, let reactionsPerStory, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let reactionsByEmotionGraph, let storyInteractionsGraph, let storyReactionsByEmotionGraph, let recentPostsInteractions): - return ("broadcastStats", [("period", period as Any), ("followers", followers as Any), ("viewsPerPost", viewsPerPost as Any), ("sharesPerPost", sharesPerPost as Any), ("reactionsPerPost", reactionsPerPost as Any), ("viewsPerStory", viewsPerStory as Any), ("sharesPerStory", sharesPerStory as Any), ("reactionsPerStory", reactionsPerStory as Any), ("enabledNotifications", enabledNotifications as Any), ("growthGraph", growthGraph as Any), ("followersGraph", followersGraph as Any), ("muteGraph", muteGraph as Any), ("topHoursGraph", topHoursGraph as Any), ("interactionsGraph", interactionsGraph as Any), ("ivInteractionsGraph", ivInteractionsGraph as Any), ("viewsBySourceGraph", viewsBySourceGraph as Any), ("newFollowersBySourceGraph", newFollowersBySourceGraph as Any), ("languagesGraph", languagesGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any), ("storyInteractionsGraph", storyInteractionsGraph as Any), ("storyReactionsByEmotionGraph", storyReactionsByEmotionGraph as Any), ("recentPostsInteractions", recentPostsInteractions as Any)]) + case .votesList(let flags, let count, let votes, let chats, let users, let nextOffset): + return ("votesList", [("flags", flags as Any), ("count", count as Any), ("votes", votes as Any), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)]) } } - public static func parse_broadcastStats(_ reader: BufferReader) -> BroadcastStats? { - var _1: Api.StatsDateRangeDays? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays - } - var _2: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _3: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _4: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _5: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _6: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _7: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _8: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _9: Api.StatsPercentValue? - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue - } - var _10: Api.StatsGraph? - if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _11: Api.StatsGraph? - if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _12: Api.StatsGraph? - if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _13: Api.StatsGraph? - if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _14: Api.StatsGraph? - if let signature = reader.readInt32() { - _14 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _15: Api.StatsGraph? - if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _16: Api.StatsGraph? - if let signature = reader.readInt32() { - _16 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _17: Api.StatsGraph? - if let signature = reader.readInt32() { - _17 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _18: Api.StatsGraph? - if let signature = reader.readInt32() { - _18 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _19: Api.StatsGraph? - if let signature = reader.readInt32() { - _19 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _20: Api.StatsGraph? - if let signature = reader.readInt32() { - _20 = Api.parse(reader, signature: signature) as? Api.StatsGraph + public static func parse_votesList(_ reader: BufferReader) -> VotesList? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.MessagePeerVote]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerVote.self) } - var _21: Api.StatsGraph? - if let signature = reader.readInt32() { - _21 = Api.parse(reader, signature: signature) as? Api.StatsGraph + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _22: [Api.PostInteractionCounters]? + var _5: [Api.User]? if let _ = reader.readInt32() { - _22 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PostInteractionCounters.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - let _c12 = _12 != nil - let _c13 = _13 != nil - let _c14 = _14 != nil - let _c15 = _15 != nil - let _c16 = _16 != nil - let _c17 = _17 != nil - let _c18 = _18 != nil - let _c19 = _19 != nil - let _c20 = _20 != nil - let _c21 = _21 != nil - let _c22 = _22 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { - return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, reactionsPerPost: _5!, viewsPerStory: _6!, sharesPerStory: _7!, reactionsPerStory: _8!, enabledNotifications: _9!, growthGraph: _10!, followersGraph: _11!, muteGraph: _12!, topHoursGraph: _13!, interactionsGraph: _14!, ivInteractionsGraph: _15!, viewsBySourceGraph: _16!, newFollowersBySourceGraph: _17!, languagesGraph: _18!, reactionsByEmotionGraph: _19!, storyInteractionsGraph: _20!, storyReactionsByEmotionGraph: _21!, recentPostsInteractions: _22!) + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.messages.VotesList.votesList(flags: _1!, count: _2!, votes: _3!, chats: _4!, users: _5!, nextOffset: _6) } else { return nil @@ -406,42 +278,20 @@ public extension Api.stats { } } -public extension Api.stats { - enum MegagroupStats: TypeConstructorDescription { - case megagroupStats(period: Api.StatsDateRangeDays, members: Api.StatsAbsValueAndPrev, messages: Api.StatsAbsValueAndPrev, viewers: Api.StatsAbsValueAndPrev, posters: Api.StatsAbsValueAndPrev, growthGraph: Api.StatsGraph, membersGraph: Api.StatsGraph, newMembersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, messagesGraph: Api.StatsGraph, actionsGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, weekdaysGraph: Api.StatsGraph, topPosters: [Api.StatsGroupTopPoster], topAdmins: [Api.StatsGroupTopAdmin], topInviters: [Api.StatsGroupTopInviter], users: [Api.User]) +public extension Api.messages { + enum WebPage: TypeConstructorDescription { + case webPage(webpage: Api.WebPage, chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .megagroupStats(let period, let members, let messages, let viewers, let posters, let growthGraph, let membersGraph, let newMembersBySourceGraph, let languagesGraph, let messagesGraph, let actionsGraph, let topHoursGraph, let weekdaysGraph, let topPosters, let topAdmins, let topInviters, let users): + case .webPage(let webpage, let chats, let users): if boxed { - buffer.appendInt32(-276825834) - } - period.serialize(buffer, true) - members.serialize(buffer, true) - messages.serialize(buffer, true) - viewers.serialize(buffer, true) - posters.serialize(buffer, true) - growthGraph.serialize(buffer, true) - membersGraph.serialize(buffer, true) - newMembersBySourceGraph.serialize(buffer, true) - languagesGraph.serialize(buffer, true) - messagesGraph.serialize(buffer, true) - actionsGraph.serialize(buffer, true) - topHoursGraph.serialize(buffer, true) - weekdaysGraph.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(topPosters.count)) - for item in topPosters { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(topAdmins.count)) - for item in topAdmins { - item.serialize(buffer, true) + buffer.appendInt32(-44166467) } + webpage.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(topInviters.count)) - for item in topInviters { + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -455,99 +305,29 @@ public extension Api.stats { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .megagroupStats(let period, let members, let messages, let viewers, let posters, let growthGraph, let membersGraph, let newMembersBySourceGraph, let languagesGraph, let messagesGraph, let actionsGraph, let topHoursGraph, let weekdaysGraph, let topPosters, let topAdmins, let topInviters, let users): - return ("megagroupStats", [("period", period as Any), ("members", members as Any), ("messages", messages as Any), ("viewers", viewers as Any), ("posters", posters as Any), ("growthGraph", growthGraph as Any), ("membersGraph", membersGraph as Any), ("newMembersBySourceGraph", newMembersBySourceGraph as Any), ("languagesGraph", languagesGraph as Any), ("messagesGraph", messagesGraph as Any), ("actionsGraph", actionsGraph as Any), ("topHoursGraph", topHoursGraph as Any), ("weekdaysGraph", weekdaysGraph as Any), ("topPosters", topPosters as Any), ("topAdmins", topAdmins as Any), ("topInviters", topInviters as Any), ("users", users as Any)]) + case .webPage(let webpage, let chats, let users): + return ("webPage", [("webpage", webpage as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_megagroupStats(_ reader: BufferReader) -> MegagroupStats? { - var _1: Api.StatsDateRangeDays? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays - } - var _2: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _3: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _4: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _5: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _6: Api.StatsGraph? - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _7: Api.StatsGraph? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _8: Api.StatsGraph? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _9: Api.StatsGraph? - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _10: Api.StatsGraph? - if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _11: Api.StatsGraph? - if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _12: Api.StatsGraph? - if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _13: Api.StatsGraph? + public static func parse_webPage(_ reader: BufferReader) -> WebPage? { + var _1: Api.WebPage? if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _14: [Api.StatsGroupTopPoster]? - if let _ = reader.readInt32() { - _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopPoster.self) - } - var _15: [Api.StatsGroupTopAdmin]? - if let _ = reader.readInt32() { - _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopAdmin.self) + _1 = Api.parse(reader, signature: signature) as? Api.WebPage } - var _16: [Api.StatsGroupTopInviter]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopInviter.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _17: [Api.User]? + var _3: [Api.User]? if let _ = reader.readInt32() { - _17 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - let _c12 = _12 != nil - let _c13 = _13 != nil - let _c14 = _14 != nil - let _c15 = _15 != nil - let _c16 = _16 != nil - let _c17 = _17 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { - return Api.stats.MegagroupStats.megagroupStats(period: _1!, members: _2!, messages: _3!, viewers: _4!, posters: _5!, growthGraph: _6!, membersGraph: _7!, newMembersBySourceGraph: _8!, languagesGraph: _9!, messagesGraph: _10!, actionsGraph: _11!, topHoursGraph: _12!, weekdaysGraph: _13!, topPosters: _14!, topAdmins: _15!, topInviters: _16!, users: _17!) + if _c1 && _c2 && _c3 { + return Api.messages.WebPage.webPage(webpage: _1!, chats: _2!, users: _3!) } else { return nil @@ -556,42 +336,44 @@ public extension Api.stats { } } -public extension Api.stats { - enum MessageStats: TypeConstructorDescription { - case messageStats(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph) +public extension Api.payments { + enum BankCardData: TypeConstructorDescription { + case bankCardData(title: String, openUrls: [Api.BankCardOpenUrl]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageStats(let viewsGraph, let reactionsByEmotionGraph): + case .bankCardData(let title, let openUrls): if boxed { - buffer.appendInt32(2145983508) + buffer.appendInt32(1042605427) + } + serializeString(title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(openUrls.count)) + for item in openUrls { + item.serialize(buffer, true) } - viewsGraph.serialize(buffer, true) - reactionsByEmotionGraph.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageStats(let viewsGraph, let reactionsByEmotionGraph): - return ("messageStats", [("viewsGraph", viewsGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any)]) + case .bankCardData(let title, let openUrls): + return ("bankCardData", [("title", title as Any), ("openUrls", openUrls as Any)]) } } - public static func parse_messageStats(_ reader: BufferReader) -> MessageStats? { - var _1: Api.StatsGraph? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _2: Api.StatsGraph? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + public static func parse_bankCardData(_ reader: BufferReader) -> BankCardData? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.BankCardOpenUrl]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BankCardOpenUrl.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.stats.MessageStats.messageStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!) + return Api.payments.BankCardData.bankCardData(title: _1!, openUrls: _2!) } else { return nil @@ -600,24 +382,23 @@ public extension Api.stats { } } -public extension Api.stats { - enum PublicForwards: TypeConstructorDescription { - case publicForwards(flags: Int32, count: Int32, forwards: [Api.PublicForward], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) +public extension Api.payments { + enum CheckedGiftCode: TypeConstructorDescription { + case checkedGiftCode(flags: Int32, fromId: Api.Peer?, giveawayMsgId: Int32?, toId: Int64?, date: Int32, months: Int32, usedDate: Int32?, chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users): + case .checkedGiftCode(let flags, let fromId, let giveawayMsgId, let toId, let date, let months, let usedDate, let chats, let users): if boxed { - buffer.appendInt32(-1828487648) + buffer.appendInt32(675942550) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(forwards.count)) - for item in forwards { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {fromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(giveawayMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(toId!, buffer: buffer, boxed: false)} + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(months, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(usedDate!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -634,38 +415,47 @@ public extension Api.stats { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users): - return ("publicForwards", [("flags", flags as Any), ("count", count as Any), ("forwards", forwards as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) + case .checkedGiftCode(let flags, let fromId, let giveawayMsgId, let toId, let date, let months, let usedDate, let chats, let users): + return ("checkedGiftCode", [("flags", flags as Any), ("fromId", fromId as Any), ("giveawayMsgId", giveawayMsgId as Any), ("toId", toId as Any), ("date", date as Any), ("months", months as Any), ("usedDate", usedDate as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_publicForwards(_ reader: BufferReader) -> PublicForwards? { + public static func parse_checkedGiftCode(_ reader: BufferReader) -> CheckedGiftCode? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.PublicForward]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PublicForward.self) - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } - var _5: [Api.Chat]? + var _2: Api.Peer? + if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _3: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_3 = reader.readInt32() } + var _4: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt64() } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_7 = reader.readInt32() } + var _8: [Api.Chat]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _6: [Api.User]? + var _9: [Api.User]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil + let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stats.PublicForwards.publicForwards(flags: _1!, count: _2!, forwards: _3!, nextOffset: _4, chats: _5!, users: _6!) + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.payments.CheckedGiftCode.checkedGiftCode(flags: _1!, fromId: _2, giveawayMsgId: _3, toId: _4, date: _5!, months: _6!, usedDate: _7, chats: _8!, users: _9!) } else { return nil @@ -674,42 +464,34 @@ public extension Api.stats { } } -public extension Api.stats { - enum StoryStats: TypeConstructorDescription { - case storyStats(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph) +public extension Api.payments { + enum ExportedInvoice: TypeConstructorDescription { + case exportedInvoice(url: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .storyStats(let viewsGraph, let reactionsByEmotionGraph): + case .exportedInvoice(let url): if boxed { - buffer.appendInt32(1355613820) + buffer.appendInt32(-1362048039) } - viewsGraph.serialize(buffer, true) - reactionsByEmotionGraph.serialize(buffer, true) + serializeString(url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .storyStats(let viewsGraph, let reactionsByEmotionGraph): - return ("storyStats", [("viewsGraph", viewsGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any)]) + case .exportedInvoice(let url): + return ("exportedInvoice", [("url", url as Any)]) } } - public static func parse_storyStats(_ reader: BufferReader) -> StoryStats? { - var _1: Api.StatsGraph? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _2: Api.StatsGraph? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } + public static func parse_exportedInvoice(_ reader: BufferReader) -> ExportedInvoice? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stats.StoryStats.storyStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!) + if _c1 { + return Api.payments.ExportedInvoice.exportedInvoice(url: _1!) } else { return nil @@ -718,34 +500,90 @@ public extension Api.stats { } } -public extension Api.stickers { - enum SuggestedShortName: TypeConstructorDescription { - case suggestedShortName(shortName: String) +public extension Api.payments { + enum GiveawayInfo: TypeConstructorDescription { + case giveawayInfo(flags: Int32, startDate: Int32, joinedTooEarlyDate: Int32?, adminDisallowedChatId: Int64?, disallowedCountry: String?) + case giveawayInfoResults(flags: Int32, startDate: Int32, giftCodeSlug: String?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .suggestedShortName(let shortName): + case .giveawayInfo(let flags, let startDate, let joinedTooEarlyDate, let adminDisallowedChatId, let disallowedCountry): if boxed { - buffer.appendInt32(-2046910401) + buffer.appendInt32(1130879648) } - serializeString(shortName, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(startDate, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(joinedTooEarlyDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt64(adminDisallowedChatId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeString(disallowedCountry!, buffer: buffer, boxed: false)} + break + case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount): + if boxed { + buffer.appendInt32(13456752) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(startDate, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(giftCodeSlug!, buffer: buffer, boxed: false)} + serializeInt32(finishDate, buffer: buffer, boxed: false) + serializeInt32(winnersCount, buffer: buffer, boxed: false) + serializeInt32(activatedCount, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .suggestedShortName(let shortName): - return ("suggestedShortName", [("shortName", shortName as Any)]) + case .giveawayInfo(let flags, let startDate, let joinedTooEarlyDate, let adminDisallowedChatId, let disallowedCountry): + return ("giveawayInfo", [("flags", flags as Any), ("startDate", startDate as Any), ("joinedTooEarlyDate", joinedTooEarlyDate as Any), ("adminDisallowedChatId", adminDisallowedChatId as Any), ("disallowedCountry", disallowedCountry as Any)]) + case .giveawayInfoResults(let flags, let startDate, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount): + return ("giveawayInfoResults", [("flags", flags as Any), ("startDate", startDate as Any), ("giftCodeSlug", giftCodeSlug as Any), ("finishDate", finishDate as Any), ("winnersCount", winnersCount as Any), ("activatedCount", activatedCount as Any)]) } } - public static func parse_suggestedShortName(_ reader: BufferReader) -> SuggestedShortName? { - var _1: String? - _1 = parseString(reader) + public static func parse_giveawayInfo(_ reader: BufferReader) -> GiveawayInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } + var _4: Int64? + if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt64() } + var _5: String? + if Int(_1!) & Int(1 << 4) != 0 {_5 = parseString(reader) } let _c1 = _1 != nil - if _c1 { - return Api.stickers.SuggestedShortName.suggestedShortName(shortName: _1!) + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.payments.GiveawayInfo.giveawayInfo(flags: _1!, startDate: _2!, joinedTooEarlyDate: _3, adminDisallowedChatId: _4, disallowedCountry: _5) + } + else { + return nil + } + } + public static func parse_giveawayInfoResults(_ reader: BufferReader) -> GiveawayInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.payments.GiveawayInfo.giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, finishDate: _4!, winnersCount: _5!, activatedCount: _6!) } else { return nil @@ -754,161 +592,476 @@ public extension Api.stickers { } } -public extension Api.storage { - enum FileType: TypeConstructorDescription { - case fileGif - case fileJpeg - case fileMov - case fileMp3 - case fileMp4 - case filePartial - case filePdf - case filePng - case fileUnknown - case fileWebp +public extension Api.payments { + enum PaymentForm: TypeConstructorDescription { + case paymentForm(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, additionalMethods: [Api.PaymentFormMethod]?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: [Api.PaymentSavedCredentials]?, users: [Api.User]) + case paymentFormStars(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .fileGif: + case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users): if boxed { - buffer.appendInt32(-891180321) + buffer.appendInt32(-1610250415) } - - break - case .fileJpeg: - if boxed { - buffer.appendInt32(8322574) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(formId, buffer: buffer, boxed: false) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 5) != 0 {photo!.serialize(buffer, true)} + invoice.serialize(buffer, true) + serializeInt64(providerId, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 {serializeString(nativeProvider!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {nativeParams!.serialize(buffer, true)} + if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(additionalMethods!.count)) + for item in additionalMethods! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 0) != 0 {savedInfo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(savedCredentials!.count)) + for item in savedCredentials! { + item.serialize(buffer, true) + }} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - break - case .fileMov: + case .paymentFormStars(let flags, let formId, let botId, let title, let description, let photo, let invoice, let users): if boxed { - buffer.appendInt32(1258941372) + buffer.appendInt32(2079764828) } - - break - case .fileMp3: - if boxed { - buffer.appendInt32(1384777335) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(formId, buffer: buffer, boxed: false) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 5) != 0 {photo!.serialize(buffer, true)} + invoice.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - break - case .fileMp4: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users): + return ("paymentForm", [("flags", flags as Any), ("formId", formId as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("providerId", providerId as Any), ("url", url as Any), ("nativeProvider", nativeProvider as Any), ("nativeParams", nativeParams as Any), ("additionalMethods", additionalMethods as Any), ("savedInfo", savedInfo as Any), ("savedCredentials", savedCredentials as Any), ("users", users as Any)]) + case .paymentFormStars(let flags, let formId, let botId, let title, let description, let photo, let invoice, let users): + return ("paymentFormStars", [("flags", flags as Any), ("formId", formId as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("users", users as Any)]) + } + } + + public static func parse_paymentForm(_ reader: BufferReader) -> PaymentForm? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.WebDocument? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } + var _7: Api.Invoice? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _8: Int64? + _8 = reader.readInt64() + var _9: String? + _9 = parseString(reader) + var _10: String? + if Int(_1!) & Int(1 << 4) != 0 {_10 = parseString(reader) } + var _11: Api.DataJSON? + if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.DataJSON + } } + var _12: [Api.PaymentFormMethod]? + if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() { + _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentFormMethod.self) + } } + var _13: Api.PaymentRequestedInfo? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } } + var _14: [Api.PaymentSavedCredentials]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentSavedCredentials.self) + } } + var _15: [Api.User]? + if let _ = reader.readInt32() { + _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 4) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 0) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 1) == 0) || _14 != nil + let _c15 = _15 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { + return Api.payments.PaymentForm.paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, additionalMethods: _12, savedInfo: _13, savedCredentials: _14, users: _15!) + } + else { + return nil + } + } + public static func parse_paymentFormStars(_ reader: BufferReader) -> PaymentForm? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.WebDocument? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } + var _7: Api.Invoice? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _8: [Api.User]? + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.payments.PaymentForm.paymentFormStars(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, users: _8!) + } + else { + return nil + } + } + + } +} +public extension Api.payments { + enum PaymentReceipt: TypeConstructorDescription { + case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User]) + case paymentReceiptStars(flags: Int32, date: Int32, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, currency: String, totalAmount: Int64, transactionId: String, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): if boxed { - buffer.appendInt32(-1278304028) + buffer.appendInt32(1891958275) } - - break - case .filePartial: - if boxed { - buffer.appendInt32(1086091090) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeInt64(providerId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} + invoice.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {shipping!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)} + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(totalAmount, buffer: buffer, boxed: false) + serializeString(credentialsTitle, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - break - case .filePdf: + case .paymentReceiptStars(let flags, let date, let botId, let title, let description, let photo, let invoice, let currency, let totalAmount, let transactionId, let users): if boxed { - buffer.appendInt32(-1373745011) + buffer.appendInt32(-625215430) } - - break - case .filePng: - if boxed { - buffer.appendInt32(172975040) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} + invoice.serialize(buffer, true) + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(totalAmount, buffer: buffer, boxed: false) + serializeString(transactionId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - break - case .fileUnknown: + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): + return ("paymentReceipt", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("providerId", providerId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("info", info as Any), ("shipping", shipping as Any), ("tipAmount", tipAmount as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("credentialsTitle", credentialsTitle as Any), ("users", users as Any)]) + case .paymentReceiptStars(let flags, let date, let botId, let title, let description, let photo, let invoice, let currency, let totalAmount, let transactionId, let users): + return ("paymentReceiptStars", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("transactionId", transactionId as Any), ("users", users as Any)]) + } + } + + public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + var _7: Api.WebDocument? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } + var _8: Api.Invoice? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _9: Api.PaymentRequestedInfo? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } } + var _10: Api.ShippingOption? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.ShippingOption + } } + var _11: Int64? + if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() } + var _12: String? + _12 = parseString(reader) + var _13: Int64? + _13 = reader.readInt64() + var _14: String? + _14 = parseString(reader) + var _15: [Api.User]? + if let _ = reader.readInt32() { + _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil + let _c12 = _12 != nil + let _c13 = _13 != nil + let _c14 = _14 != nil + let _c15 = _15 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { + return Api.payments.PaymentReceipt.paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!) + } + else { + return nil + } + } + public static func parse_paymentReceiptStars(_ reader: BufferReader) -> PaymentReceipt? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.WebDocument? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } + var _7: Api.Invoice? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _8: String? + _8 = parseString(reader) + var _9: Int64? + _9 = reader.readInt64() + var _10: String? + _10 = parseString(reader) + var _11: [Api.User]? + if let _ = reader.readInt32() { + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.payments.PaymentReceipt.paymentReceiptStars(flags: _1!, date: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, currency: _8!, totalAmount: _9!, transactionId: _10!, users: _11!) + } + else { + return nil + } + } + + } +} +public extension Api.payments { + indirect enum PaymentResult: TypeConstructorDescription { + case paymentResult(updates: Api.Updates) + case paymentVerificationNeeded(url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentResult(let updates): if boxed { - buffer.appendInt32(-1432995067) + buffer.appendInt32(1314881805) } - + updates.serialize(buffer, true) break - case .fileWebp: + case .paymentVerificationNeeded(let url): if boxed { - buffer.appendInt32(276907596) + buffer.appendInt32(-666824391) } - + serializeString(url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .fileGif: - return ("fileGif", []) - case .fileJpeg: - return ("fileJpeg", []) - case .fileMov: - return ("fileMov", []) - case .fileMp3: - return ("fileMp3", []) - case .fileMp4: - return ("fileMp4", []) - case .filePartial: - return ("filePartial", []) - case .filePdf: - return ("filePdf", []) - case .filePng: - return ("filePng", []) - case .fileUnknown: - return ("fileUnknown", []) - case .fileWebp: - return ("fileWebp", []) - } - } - - public static func parse_fileGif(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileGif - } - public static func parse_fileJpeg(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileJpeg - } - public static func parse_fileMov(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileMov - } - public static func parse_fileMp3(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileMp3 - } - public static func parse_fileMp4(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileMp4 - } - public static func parse_filePartial(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.filePartial - } - public static func parse_filePdf(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.filePdf - } - public static func parse_filePng(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.filePng + case .paymentResult(let updates): + return ("paymentResult", [("updates", updates as Any)]) + case .paymentVerificationNeeded(let url): + return ("paymentVerificationNeeded", [("url", url as Any)]) + } + } + + public static func parse_paymentResult(_ reader: BufferReader) -> PaymentResult? { + var _1: Api.Updates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Updates + } + let _c1 = _1 != nil + if _c1 { + return Api.payments.PaymentResult.paymentResult(updates: _1!) + } + else { + return nil + } } - public static func parse_fileUnknown(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileUnknown + public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.payments.PaymentResult.paymentVerificationNeeded(url: _1!) + } + else { + return nil + } } - public static func parse_fileWebp(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileWebp + + } +} +public extension Api.payments { + enum SavedInfo: TypeConstructorDescription { + case savedInfo(flags: Int32, savedInfo: Api.PaymentRequestedInfo?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedInfo(let flags, let savedInfo): + if boxed { + buffer.appendInt32(-74456004) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {savedInfo!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .savedInfo(let flags, let savedInfo): + return ("savedInfo", [("flags", flags as Any), ("savedInfo", savedInfo as Any)]) + } + } + + public static func parse_savedInfo(_ reader: BufferReader) -> SavedInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.PaymentRequestedInfo? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.payments.SavedInfo.savedInfo(flags: _1!, savedInfo: _2) + } + else { + return nil + } } } } -public extension Api.stories { - enum AllStories: TypeConstructorDescription { - case allStories(flags: Int32, count: Int32, state: String, peerStories: [Api.PeerStories], chats: [Api.Chat], users: [Api.User], stealthMode: Api.StoriesStealthMode) - case allStoriesNotModified(flags: Int32, state: String, stealthMode: Api.StoriesStealthMode) +public extension Api.payments { + enum StarsStatus: TypeConstructorDescription { + case starsStatus(flags: Int32, balance: Int64, history: [Api.StarsTransaction], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .allStories(let flags, let count, let state, let peerStories, let chats, let users, let stealthMode): + case .starsStatus(let flags, let balance, let history, let nextOffset, let chats, let users): if boxed { - buffer.appendInt32(1862033025) + buffer.appendInt32(-1930105248) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - serializeString(state, buffer: buffer, boxed: false) + serializeInt64(balance, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peerStories.count)) - for item in peerStories { + buffer.appendInt32(Int32(history.count)) + for item in history { item.serialize(buffer, true) } + if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -919,39 +1072,28 @@ public extension Api.stories { for item in users { item.serialize(buffer, true) } - stealthMode.serialize(buffer, true) - break - case .allStoriesNotModified(let flags, let state, let stealthMode): - if boxed { - buffer.appendInt32(291044926) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(state, buffer: buffer, boxed: false) - stealthMode.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .allStories(let flags, let count, let state, let peerStories, let chats, let users, let stealthMode): - return ("allStories", [("flags", flags as Any), ("count", count as Any), ("state", state as Any), ("peerStories", peerStories as Any), ("chats", chats as Any), ("users", users as Any), ("stealthMode", stealthMode as Any)]) - case .allStoriesNotModified(let flags, let state, let stealthMode): - return ("allStoriesNotModified", [("flags", flags as Any), ("state", state as Any), ("stealthMode", stealthMode as Any)]) + case .starsStatus(let flags, let balance, let history, let nextOffset, let chats, let users): + return ("starsStatus", [("flags", flags as Any), ("balance", balance as Any), ("history", history as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_allStories(_ reader: BufferReader) -> AllStories? { + public static func parse_starsStatus(_ reader: BufferReader) -> StarsStatus? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - var _4: [Api.PeerStories]? + var _2: Int64? + _2 = reader.readInt64() + var _3: [Api.StarsTransaction]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerStories.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTransaction.self) } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } var _5: [Api.Chat]? if let _ = reader.readInt32() { _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) @@ -960,38 +1102,64 @@ public extension Api.stories { if let _ = reader.readInt32() { _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _7: Api.StoriesStealthMode? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode - } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.stories.AllStories.allStories(flags: _1!, count: _2!, state: _3!, peerStories: _4!, chats: _5!, users: _6!, stealthMode: _7!) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.payments.StarsStatus.starsStatus(flags: _1!, balance: _2!, history: _3!, nextOffset: _4, chats: _5!, users: _6!) } else { return nil } } - public static func parse_allStoriesNotModified(_ reader: BufferReader) -> AllStories? { + + } +} +public extension Api.payments { + enum ValidatedRequestedInfo: TypeConstructorDescription { + case validatedRequestedInfo(flags: Int32, id: String?, shippingOptions: [Api.ShippingOption]?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .validatedRequestedInfo(let flags, let id, let shippingOptions): + if boxed { + buffer.appendInt32(-784000893) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(id!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(shippingOptions!.count)) + for item in shippingOptions! { + item.serialize(buffer, true) + }} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .validatedRequestedInfo(let flags, let id, let shippingOptions): + return ("validatedRequestedInfo", [("flags", flags as Any), ("id", id as Any), ("shippingOptions", shippingOptions as Any)]) + } + } + + public static func parse_validatedRequestedInfo(_ reader: BufferReader) -> ValidatedRequestedInfo? { var _1: Int32? _1 = reader.readInt32() var _2: String? - _2 = parseString(reader) - var _3: Api.StoriesStealthMode? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode - } + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + var _3: [Api.ShippingOption]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ShippingOption.self) + } } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil if _c1 && _c2 && _c3 { - return Api.stories.AllStories.allStoriesNotModified(flags: _1!, state: _2!, stealthMode: _3!) + return Api.payments.ValidatedRequestedInfo.validatedRequestedInfo(flags: _1!, id: _2, shippingOptions: _3) } else { return nil @@ -1000,17 +1168,59 @@ public extension Api.stories { } } -public extension Api.stories { - enum PeerStories: TypeConstructorDescription { - case peerStories(stories: Api.PeerStories, chats: [Api.Chat], users: [Api.User]) +public extension Api.phone { + enum ExportedGroupCallInvite: TypeConstructorDescription { + case exportedGroupCallInvite(link: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerStories(let stories, let chats, let users): + case .exportedGroupCallInvite(let link): if boxed { - buffer.appendInt32(-890861720) + buffer.appendInt32(541839704) } - stories.serialize(buffer, true) + serializeString(link, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .exportedGroupCallInvite(let link): + return ("exportedGroupCallInvite", [("link", link as Any)]) + } + } + + public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(link: _1!) + } + else { + return nil + } + } + + } +} +public extension Api.phone { + enum GroupCall: TypeConstructorDescription { + case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users): + if boxed { + buffer.appendInt32(-1636664659) + } + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(participants.count)) + for item in participants { + item.serialize(buffer, true) + } + serializeString(participantsNextOffset, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -1027,29 +1237,37 @@ public extension Api.stories { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .peerStories(let stories, let chats, let users): - return ("peerStories", [("stories", stories as Any), ("chats", chats as Any), ("users", users as Any)]) + case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users): + return ("groupCall", [("call", call as Any), ("participants", participants as Any), ("participantsNextOffset", participantsNextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? { - var _1: Api.PeerStories? + public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? { + var _1: Api.GroupCall? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PeerStories + _1 = Api.parse(reader, signature: signature) as? Api.GroupCall } - var _2: [Api.Chat]? + var _2: [Api.GroupCallParticipant]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) } - var _3: [Api.User]? + var _3: String? + _3 = parseString(reader) + var _4: [Api.Chat]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.stories.PeerStories.peerStories(stories: _1!, chats: _2!, users: _3!) + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!) } else { return nil @@ -1058,36 +1276,19 @@ public extension Api.stories { } } -public extension Api.stories { - enum Stories: TypeConstructorDescription { - case stories(flags: Int32, count: Int32, stories: [Api.StoryItem], pinnedToTop: [Int32]?, chats: [Api.Chat], users: [Api.User]) +public extension Api.phone { + enum GroupCallStreamChannels: TypeConstructorDescription { + case groupCallStreamChannels(channels: [Api.GroupCallStreamChannel]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users): + case .groupCallStreamChannels(let channels): if boxed { - buffer.appendInt32(1673780490) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stories.count)) - for item in stories { - item.serialize(buffer, true) + buffer.appendInt32(-790330702) } - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(pinnedToTop!.count)) - for item in pinnedToTop! { - serializeInt32(item, buffer: buffer, boxed: false) - }} buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { + buffer.appendInt32(Int32(channels.count)) + for item in channels { item.serialize(buffer, true) } break @@ -1096,40 +1297,59 @@ public extension Api.stories { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users): - return ("stories", [("flags", flags as Any), ("count", count as Any), ("stories", stories as Any), ("pinnedToTop", pinnedToTop as Any), ("chats", chats as Any), ("users", users as Any)]) + case .groupCallStreamChannels(let channels): + return ("groupCallStreamChannels", [("channels", channels as Any)]) } } - public static func parse_stories(_ reader: BufferReader) -> Stories? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.StoryItem]? + public static func parse_groupCallStreamChannels(_ reader: BufferReader) -> GroupCallStreamChannels? { + var _1: [Api.GroupCallStreamChannel]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallStreamChannel.self) } - var _4: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + let _c1 = _1 != nil + if _c1 { + return Api.phone.GroupCallStreamChannels.groupCallStreamChannels(channels: _1!) } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + else { + return nil } + } + + } +} +public extension Api.phone { + enum GroupCallStreamRtmpUrl: TypeConstructorDescription { + case groupCallStreamRtmpUrl(url: String, key: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCallStreamRtmpUrl(let url, let key): + if boxed { + buffer.appendInt32(767505458) + } + serializeString(url, buffer: buffer, boxed: false) + serializeString(key, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupCallStreamRtmpUrl(let url, let key): + return ("groupCallStreamRtmpUrl", [("url", url as Any), ("key", key as Any)]) + } + } + + public static func parse_groupCallStreamRtmpUrl(_ reader: BufferReader) -> GroupCallStreamRtmpUrl? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stories.Stories.stories(flags: _1!, count: _2!, stories: _3!, pinnedToTop: _4, chats: _5!, users: _6!) + if _c1 && _c2 { + return Api.phone.GroupCallStreamRtmpUrl.groupCallStreamRtmpUrl(url: _1!, key: _2!) } else { return nil @@ -1138,23 +1358,23 @@ public extension Api.stories { } } -public extension Api.stories { - enum StoryReactionsList: TypeConstructorDescription { - case storyReactionsList(flags: Int32, count: Int32, reactions: [Api.StoryReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?) +public extension Api.phone { + enum GroupParticipants: TypeConstructorDescription { + case groupParticipants(count: Int32, participants: [Api.GroupCallParticipant], nextOffset: String, chats: [Api.Chat], users: [Api.User], version: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .storyReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset): + case .groupParticipants(let count, let participants, let nextOffset, let chats, let users, let version): if boxed { - buffer.appendInt32(-1436583780) + buffer.appendInt32(-193506890) } - serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reactions.count)) - for item in reactions { + buffer.appendInt32(Int32(participants.count)) + for item in participants { item.serialize(buffer, true) } + serializeString(nextOffset, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -1165,27 +1385,27 @@ public extension Api.stories { for item in users { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + serializeInt32(version, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .storyReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset): - return ("storyReactionsList", [("flags", flags as Any), ("count", count as Any), ("reactions", reactions as Any), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)]) + case .groupParticipants(let count, let participants, let nextOffset, let chats, let users, let version): + return ("groupParticipants", [("count", count as Any), ("participants", participants as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any), ("version", version as Any)]) } } - public static func parse_storyReactionsList(_ reader: BufferReader) -> StoryReactionsList? { + public static func parse_groupParticipants(_ reader: BufferReader) -> GroupParticipants? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.StoryReaction]? + var _2: [Api.GroupCallParticipant]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryReaction.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) } + var _3: String? + _3 = parseString(reader) var _4: [Api.Chat]? if let _ = reader.readInt32() { _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) @@ -1194,16 +1414,16 @@ public extension Api.stories { if let _ = reader.readInt32() { _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } + var _6: Int32? + _6 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c6 = _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stories.StoryReactionsList.storyReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6) + return Api.phone.GroupParticipants.groupParticipants(count: _1!, participants: _2!, nextOffset: _3!, chats: _4!, users: _5!, version: _6!) } else { return nil @@ -1212,19 +1432,24 @@ public extension Api.stories { } } -public extension Api.stories { - enum StoryViews: TypeConstructorDescription { - case storyViews(views: [Api.StoryViews], users: [Api.User]) +public extension Api.phone { + enum JoinAsPeers: TypeConstructorDescription { + case joinAsPeers(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .storyViews(let views, let users): + case .joinAsPeers(let peers, let chats, let users): if boxed { - buffer.appendInt32(-560009955) + buffer.appendInt32(-1343921601) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(views.count)) - for item in views { + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -1238,24 +1463,29 @@ public extension Api.stories { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .storyViews(let views, let users): - return ("storyViews", [("views", views as Any), ("users", users as Any)]) + case .joinAsPeers(let peers, let chats, let users): + return ("joinAsPeers", [("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { - var _1: [Api.StoryViews]? + public static func parse_joinAsPeers(_ reader: BufferReader) -> JoinAsPeers? { + var _1: [Api.Peer]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryViews.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } - var _2: [Api.User]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stories.StoryViews.storyViews(views: _1!, users: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.phone.JoinAsPeers.joinAsPeers(peers: _1!, chats: _2!, users: _3!) } else { return nil @@ -1264,84 +1494,46 @@ public extension Api.stories { } } -public extension Api.stories { - enum StoryViewsList: TypeConstructorDescription { - case storyViewsList(flags: Int32, count: Int32, viewsCount: Int32, forwardsCount: Int32, reactionsCount: Int32, views: [Api.StoryView], chats: [Api.Chat], users: [Api.User], nextOffset: String?) +public extension Api.phone { + enum PhoneCall: TypeConstructorDescription { + case phoneCall(phoneCall: Api.PhoneCall, users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .storyViewsList(let flags, let count, let viewsCount, let forwardsCount, let reactionsCount, let views, let chats, let users, let nextOffset): + case .phoneCall(let phoneCall, let users): if boxed { - buffer.appendInt32(1507299269) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - serializeInt32(viewsCount, buffer: buffer, boxed: false) - serializeInt32(forwardsCount, buffer: buffer, boxed: false) - serializeInt32(reactionsCount, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(views.count)) - for item in views { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + buffer.appendInt32(-326966976) } + phoneCall.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .storyViewsList(let flags, let count, let viewsCount, let forwardsCount, let reactionsCount, let views, let chats, let users, let nextOffset): - return ("storyViewsList", [("flags", flags as Any), ("count", count as Any), ("viewsCount", viewsCount as Any), ("forwardsCount", forwardsCount as Any), ("reactionsCount", reactionsCount as Any), ("views", views as Any), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)]) + case .phoneCall(let phoneCall, let users): + return ("phoneCall", [("phoneCall", phoneCall as Any), ("users", users as Any)]) } } - public static func parse_storyViewsList(_ reader: BufferReader) -> StoryViewsList? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: [Api.StoryView]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryView.self) - } - var _7: [Api.Chat]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? { + var _1: Api.PhoneCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall } - var _8: [Api.User]? + var _2: [Api.User]? if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _9: String? - if Int(_1!) & Int(1 << 0) != 0 {_9 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.stories.StoryViewsList.storyViewsList(flags: _1!, count: _2!, viewsCount: _3!, forwardsCount: _4!, reactionsCount: _5!, views: _6!, chats: _7!, users: _8!, nextOffset: _9) + if _c1 && _c2 { + return Api.phone.PhoneCall.phoneCall(phoneCall: _1!, users: _2!) } else { return nil @@ -1350,67 +1542,17 @@ public extension Api.stories { } } -public extension Api.updates { - indirect enum ChannelDifference: TypeConstructorDescription { - case channelDifference(flags: Int32, pts: Int32, timeout: Int32?, newMessages: [Api.Message], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User]) - case channelDifferenceEmpty(flags: Int32, pts: Int32, timeout: Int32?) - case channelDifferenceTooLong(flags: Int32, timeout: Int32?, dialog: Api.Dialog, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) +public extension Api.photos { + enum Photo: TypeConstructorDescription { + case photo(photo: Api.Photo, users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users): - if boxed { - buffer.appendInt32(543450958) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(newMessages.count)) - for item in newMessages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(otherUpdates.count)) - for item in otherUpdates { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .channelDifferenceEmpty(let flags, let pts, let timeout): - if boxed { - buffer.appendInt32(1041346555) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)} - break - case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users): + case .photo(let photo, let users): if boxed { - buffer.appendInt32(-1531132162) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)} - dialog.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) + buffer.appendInt32(539045032) } + photo.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { @@ -1422,98 +1564,24 @@ public extension Api.updates { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users): - return ("channelDifference", [("flags", flags as Any), ("pts", pts as Any), ("timeout", timeout as Any), ("newMessages", newMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any)]) - case .channelDifferenceEmpty(let flags, let pts, let timeout): - return ("channelDifferenceEmpty", [("flags", flags as Any), ("pts", pts as Any), ("timeout", timeout as Any)]) - case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users): - return ("channelDifferenceTooLong", [("flags", flags as Any), ("timeout", timeout as Any), ("dialog", dialog as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .photo(let photo, let users): + return ("photo", [("photo", photo as Any), ("users", users as Any)]) } } - public static func parse_channelDifference(_ reader: BufferReader) -> ChannelDifference? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } - var _4: [Api.Message]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _5: [Api.Update]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) - } - var _6: [Api.Chat]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _7: [Api.User]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.updates.ChannelDifference.channelDifference(flags: _1!, pts: _2!, timeout: _3, newMessages: _4!, otherUpdates: _5!, chats: _6!, users: _7!) - } - else { - return nil - } - } - public static func parse_channelDifferenceEmpty(_ reader: BufferReader) -> ChannelDifference? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.updates.ChannelDifference.channelDifferenceEmpty(flags: _1!, pts: _2!, timeout: _3) - } - else { - return nil - } - } - public static func parse_channelDifferenceTooLong(_ reader: BufferReader) -> ChannelDifference? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } - var _3: Api.Dialog? + public static func parse_photo(_ reader: BufferReader) -> Photo? { + var _1: Api.Photo? if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Dialog - } - var _4: [Api.Message]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _1 = Api.parse(reader, signature: signature) as? Api.Photo } - var _6: [Api.User]? + var _2: [Api.User]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.updates.ChannelDifference.channelDifferenceTooLong(flags: _1!, timeout: _2, dialog: _3!, messages: _4!, chats: _5!, users: _6!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.photos.Photo.photo(photo: _1!, users: _2!) } else { return nil @@ -1522,37 +1590,20 @@ public extension Api.updates { } } -public extension Api.updates { - enum Difference: TypeConstructorDescription { - case difference(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) - case differenceEmpty(date: Int32, seq: Int32) - case differenceSlice(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], intermediateState: Api.updates.State) - case differenceTooLong(pts: Int32) +public extension Api.photos { + enum Photos: TypeConstructorDescription { + case photos(photos: [Api.Photo], users: [Api.User]) + case photosSlice(count: Int32, photos: [Api.Photo], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state): + case .photos(let photos, let users): if boxed { - buffer.appendInt32(16030880) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(newMessages.count)) - for item in newMessages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(newEncryptedMessages.count)) - for item in newEncryptedMessages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(otherUpdates.count)) - for item in otherUpdates { - item.serialize(buffer, true) + buffer.appendInt32(-1916114267) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(photos.count)) + for item in photos { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -1560,37 +1611,15 @@ public extension Api.updates { for item in users { item.serialize(buffer, true) } - state.serialize(buffer, true) - break - case .differenceEmpty(let date, let seq): - if boxed { - buffer.appendInt32(1567990072) - } - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(seq, buffer: buffer, boxed: false) break - case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState): + case .photosSlice(let count, let photos, let users): if boxed { - buffer.appendInt32(-1459938943) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(newMessages.count)) - for item in newMessages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(newEncryptedMessages.count)) - for item in newEncryptedMessages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(otherUpdates.count)) - for item in otherUpdates { - item.serialize(buffer, true) + buffer.appendInt32(352657236) } + serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(photos.count)) + for item in photos { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -1598,126 +1627,53 @@ public extension Api.updates { for item in users { item.serialize(buffer, true) } - intermediateState.serialize(buffer, true) - break - case .differenceTooLong(let pts): - if boxed { - buffer.appendInt32(1258196845) - } - serializeInt32(pts, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state): - return ("difference", [("newMessages", newMessages as Any), ("newEncryptedMessages", newEncryptedMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any), ("state", state as Any)]) - case .differenceEmpty(let date, let seq): - return ("differenceEmpty", [("date", date as Any), ("seq", seq as Any)]) - case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState): - return ("differenceSlice", [("newMessages", newMessages as Any), ("newEncryptedMessages", newEncryptedMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any), ("intermediateState", intermediateState as Any)]) - case .differenceTooLong(let pts): - return ("differenceTooLong", [("pts", pts as Any)]) + case .photos(let photos, let users): + return ("photos", [("photos", photos as Any), ("users", users as Any)]) + case .photosSlice(let count, let photos, let users): + return ("photosSlice", [("count", count as Any), ("photos", photos as Any), ("users", users as Any)]) } } - public static func parse_difference(_ reader: BufferReader) -> Difference? { - var _1: [Api.Message]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _2: [Api.EncryptedMessage]? + public static func parse_photos(_ reader: BufferReader) -> Photos? { + var _1: [Api.Photo]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) } - var _3: [Api.Update]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? + var _2: [Api.User]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: Api.updates.State? - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.updates.State + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.updates.Difference.difference(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, state: _6!) + if _c1 && _c2 { + return Api.photos.Photos.photos(photos: _1!, users: _2!) } else { return nil } } - public static func parse_differenceEmpty(_ reader: BufferReader) -> Difference? { + public static func parse_photosSlice(_ reader: BufferReader) -> Photos? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.updates.Difference.differenceEmpty(date: _1!, seq: _2!) - } - else { - return nil - } - } - public static func parse_differenceSlice(_ reader: BufferReader) -> Difference? { - var _1: [Api.Message]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _2: [Api.EncryptedMessage]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self) - } - var _3: [Api.Update]? + var _2: [Api.Photo]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? + var _3: [Api.User]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: Api.updates.State? - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.updates.State + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.updates.Difference.differenceSlice(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, intermediateState: _6!) - } - else { - return nil - } - } - public static func parse_differenceTooLong(_ reader: BufferReader) -> Difference? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.updates.Difference.differenceTooLong(pts: _1!) + if _c1 && _c2 && _c3 { + return Api.photos.Photos.photosSlice(count: _1!, photos: _2!, users: _3!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api34.swift b/submodules/TelegramApi/Sources/Api34.swift index a2670b0450f..8952189c1c0 100644 --- a/submodules/TelegramApi/Sources/Api34.swift +++ b/submodules/TelegramApi/Sources/Api34.swift @@ -1,30 +1,107 @@ -public extension Api.updates { - enum State: TypeConstructorDescription { - case state(pts: Int32, qts: Int32, date: Int32, seq: Int32, unreadCount: Int32) +public extension Api.premium { + enum BoostsList: TypeConstructorDescription { + case boostsList(flags: Int32, count: Int32, boosts: [Api.Boost], nextOffset: String?, users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .state(let pts, let qts, let date, let seq, let unreadCount): + case .boostsList(let flags, let count, let boosts, let nextOffset, let users): if boxed { - buffer.appendInt32(-1519637954) + buffer.appendInt32(-2030542532) } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(qts, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(seq, buffer: buffer, boxed: false) - serializeInt32(unreadCount, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(boosts.count)) + for item in boosts { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .boostsList(let flags, let count, let boosts, let nextOffset, let users): + return ("boostsList", [("flags", flags as Any), ("count", count as Any), ("boosts", boosts as Any), ("nextOffset", nextOffset as Any), ("users", users as Any)]) + } + } + + public static func parse_boostsList(_ reader: BufferReader) -> BoostsList? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.Boost]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Boost.self) + } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.premium.BoostsList.boostsList(flags: _1!, count: _2!, boosts: _3!, nextOffset: _4, users: _5!) + } + else { + return nil + } + } + + } +} +public extension Api.premium { + enum BoostsStatus: TypeConstructorDescription { + case boostsStatus(flags: Int32, level: Int32, currentLevelBoosts: Int32, boosts: Int32, giftBoosts: Int32?, nextLevelBoosts: Int32?, premiumAudience: Api.StatsPercentValue?, boostUrl: String, prepaidGiveaways: [Api.PrepaidGiveaway]?, myBoostSlots: [Int32]?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let giftBoosts, let nextLevelBoosts, let premiumAudience, let boostUrl, let prepaidGiveaways, let myBoostSlots): + if boxed { + buffer.appendInt32(1230586490) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(level, buffer: buffer, boxed: false) + serializeInt32(currentLevelBoosts, buffer: buffer, boxed: false) + serializeInt32(boosts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(giftBoosts!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextLevelBoosts!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {premiumAudience!.serialize(buffer, true)} + serializeString(boostUrl, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(prepaidGiveaways!.count)) + for item in prepaidGiveaways! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(myBoostSlots!.count)) + for item in myBoostSlots! { + serializeInt32(item, buffer: buffer, boxed: false) + }} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .state(let pts, let qts, let date, let seq, let unreadCount): - return ("state", [("pts", pts as Any), ("qts", qts as Any), ("date", date as Any), ("seq", seq as Any), ("unreadCount", unreadCount as Any)]) + case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let giftBoosts, let nextLevelBoosts, let premiumAudience, let boostUrl, let prepaidGiveaways, let myBoostSlots): + return ("boostsStatus", [("flags", flags as Any), ("level", level as Any), ("currentLevelBoosts", currentLevelBoosts as Any), ("boosts", boosts as Any), ("giftBoosts", giftBoosts as Any), ("nextLevelBoosts", nextLevelBoosts as Any), ("premiumAudience", premiumAudience as Any), ("boostUrl", boostUrl as Any), ("prepaidGiveaways", prepaidGiveaways as Any), ("myBoostSlots", myBoostSlots as Any)]) } } - public static func parse_state(_ reader: BufferReader) -> State? { + public static func parse_boostsStatus(_ reader: BufferReader) -> BoostsStatus? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? @@ -34,14 +111,35 @@ public extension Api.updates { var _4: Int32? _4 = reader.readInt32() var _5: Int32? - _5 = reader.readInt32() + if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } + var _7: Api.StatsPercentValue? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue + } } + var _8: String? + _8 = parseString(reader) + var _9: [Api.PrepaidGiveaway]? + if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrepaidGiveaway.self) + } } + var _10: [Int32]? + if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() { + _10 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.updates.State.state(pts: _1!, qts: _2!, date: _3!, seq: _4!, unreadCount: _5!) + let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.premium.BoostsStatus.boostsStatus(flags: _1!, level: _2!, currentLevelBoosts: _3!, boosts: _4!, giftBoosts: _5, nextLevelBoosts: _6, premiumAudience: _7, boostUrl: _8!, prepaidGiveaways: _9, myBoostSlots: _10) } else { return nil @@ -50,54 +148,100 @@ public extension Api.updates { } } -public extension Api.upload { - enum CdnFile: TypeConstructorDescription { - case cdnFile(bytes: Buffer) - case cdnFileReuploadNeeded(requestToken: Buffer) +public extension Api.premium { + enum MyBoosts: TypeConstructorDescription { + case myBoosts(myBoosts: [Api.MyBoost], chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .cdnFile(let bytes): + case .myBoosts(let myBoosts, let chats, let users): if boxed { - buffer.appendInt32(-1449145777) + buffer.appendInt32(-1696454430) } - serializeBytes(bytes, buffer: buffer, boxed: false) - break - case .cdnFileReuploadNeeded(let requestToken): - if boxed { - buffer.appendInt32(-290921362) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(myBoosts.count)) + for item in myBoosts { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) } - serializeBytes(requestToken, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .cdnFile(let bytes): - return ("cdnFile", [("bytes", bytes as Any)]) - case .cdnFileReuploadNeeded(let requestToken): - return ("cdnFileReuploadNeeded", [("requestToken", requestToken as Any)]) + case .myBoosts(let myBoosts, let chats, let users): + return ("myBoosts", [("myBoosts", myBoosts as Any), ("chats", chats as Any), ("users", users as Any)]) } } - public static func parse_cdnFile(_ reader: BufferReader) -> CdnFile? { - var _1: Buffer? - _1 = parseBytes(reader) + public static func parse_myBoosts(_ reader: BufferReader) -> MyBoosts? { + var _1: [Api.MyBoost]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MyBoost.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil - if _c1 { - return Api.upload.CdnFile.cdnFile(bytes: _1!) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.premium.MyBoosts.myBoosts(myBoosts: _1!, chats: _2!, users: _3!) } else { return nil } } - public static func parse_cdnFileReuploadNeeded(_ reader: BufferReader) -> CdnFile? { - var _1: Buffer? - _1 = parseBytes(reader) + + } +} +public extension Api.smsjobs { + enum EligibilityToJoin: TypeConstructorDescription { + case eligibleToJoin(termsUrl: String, monthlySentSms: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .eligibleToJoin(let termsUrl, let monthlySentSms): + if boxed { + buffer.appendInt32(-594852657) + } + serializeString(termsUrl, buffer: buffer, boxed: false) + serializeInt32(monthlySentSms, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .eligibleToJoin(let termsUrl, let monthlySentSms): + return ("eligibleToJoin", [("termsUrl", termsUrl as Any), ("monthlySentSms", monthlySentSms as Any)]) + } + } + + public static func parse_eligibleToJoin(_ reader: BufferReader) -> EligibilityToJoin? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.upload.CdnFile.cdnFileReuploadNeeded(requestToken: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.smsjobs.EligibilityToJoin.eligibleToJoin(termsUrl: _1!, monthlySentSms: _2!) } else { return nil @@ -106,86 +250,162 @@ public extension Api.upload { } } -public extension Api.upload { - enum File: TypeConstructorDescription { - case file(type: Api.storage.FileType, mtime: Int32, bytes: Buffer) - case fileCdnRedirect(dcId: Int32, fileToken: Buffer, encryptionKey: Buffer, encryptionIv: Buffer, fileHashes: [Api.FileHash]) +public extension Api.smsjobs { + enum Status: TypeConstructorDescription { + case status(flags: Int32, recentSent: Int32, recentSince: Int32, recentRemains: Int32, totalSent: Int32, totalSince: Int32, lastGiftSlug: String?, termsUrl: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .file(let type, let mtime, let bytes): + case .status(let flags, let recentSent, let recentSince, let recentRemains, let totalSent, let totalSince, let lastGiftSlug, let termsUrl): if boxed { - buffer.appendInt32(157948117) + buffer.appendInt32(720277905) } - type.serialize(buffer, true) - serializeInt32(mtime, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(recentSent, buffer: buffer, boxed: false) + serializeInt32(recentSince, buffer: buffer, boxed: false) + serializeInt32(recentRemains, buffer: buffer, boxed: false) + serializeInt32(totalSent, buffer: buffer, boxed: false) + serializeInt32(totalSince, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(lastGiftSlug!, buffer: buffer, boxed: false)} + serializeString(termsUrl, buffer: buffer, boxed: false) break - case .fileCdnRedirect(let dcId, let fileToken, let encryptionKey, let encryptionIv, let fileHashes): + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .status(let flags, let recentSent, let recentSince, let recentRemains, let totalSent, let totalSince, let lastGiftSlug, let termsUrl): + return ("status", [("flags", flags as Any), ("recentSent", recentSent as Any), ("recentSince", recentSince as Any), ("recentRemains", recentRemains as Any), ("totalSent", totalSent as Any), ("totalSince", totalSince as Any), ("lastGiftSlug", lastGiftSlug as Any), ("termsUrl", termsUrl as Any)]) + } + } + + public static func parse_status(_ reader: BufferReader) -> Status? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: String? + if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } + var _8: String? + _8 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.smsjobs.Status.status(flags: _1!, recentSent: _2!, recentSince: _3!, recentRemains: _4!, totalSent: _5!, totalSince: _6!, lastGiftSlug: _7, termsUrl: _8!) + } + else { + return nil + } + } + + } +} +public extension Api.stats { + enum BroadcastRevenueStats: TypeConstructorDescription { + case broadcastRevenueStats(topHoursGraph: Api.StatsGraph, revenueGraph: Api.StatsGraph, balances: Api.BroadcastRevenueBalances, usdRate: Double) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .broadcastRevenueStats(let topHoursGraph, let revenueGraph, let balances, let usdRate): if boxed { - buffer.appendInt32(-242427324) - } - serializeInt32(dcId, buffer: buffer, boxed: false) - serializeBytes(fileToken, buffer: buffer, boxed: false) - serializeBytes(encryptionKey, buffer: buffer, boxed: false) - serializeBytes(encryptionIv, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(fileHashes.count)) - for item in fileHashes { - item.serialize(buffer, true) + buffer.appendInt32(1409802903) } + topHoursGraph.serialize(buffer, true) + revenueGraph.serialize(buffer, true) + balances.serialize(buffer, true) + serializeDouble(usdRate, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .file(let type, let mtime, let bytes): - return ("file", [("type", type as Any), ("mtime", mtime as Any), ("bytes", bytes as Any)]) - case .fileCdnRedirect(let dcId, let fileToken, let encryptionKey, let encryptionIv, let fileHashes): - return ("fileCdnRedirect", [("dcId", dcId as Any), ("fileToken", fileToken as Any), ("encryptionKey", encryptionKey as Any), ("encryptionIv", encryptionIv as Any), ("fileHashes", fileHashes as Any)]) + case .broadcastRevenueStats(let topHoursGraph, let revenueGraph, let balances, let usdRate): + return ("broadcastRevenueStats", [("topHoursGraph", topHoursGraph as Any), ("revenueGraph", revenueGraph as Any), ("balances", balances as Any), ("usdRate", usdRate as Any)]) } } - public static func parse_file(_ reader: BufferReader) -> File? { - var _1: Api.storage.FileType? + public static func parse_broadcastRevenueStats(_ reader: BufferReader) -> BroadcastRevenueStats? { + var _1: Api.StatsGraph? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.storage.FileType + _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph } - var _2: Int32? - _2 = reader.readInt32() - var _3: Buffer? - _3 = parseBytes(reader) + var _2: Api.StatsGraph? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _3: Api.BroadcastRevenueBalances? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.BroadcastRevenueBalances + } + var _4: Double? + _4 = reader.readDouble() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.upload.File.file(type: _1!, mtime: _2!, bytes: _3!) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.stats.BroadcastRevenueStats.broadcastRevenueStats(topHoursGraph: _1!, revenueGraph: _2!, balances: _3!, usdRate: _4!) } else { return nil } } - public static func parse_fileCdnRedirect(_ reader: BufferReader) -> File? { + + } +} +public extension Api.stats { + enum BroadcastRevenueTransactions: TypeConstructorDescription { + case broadcastRevenueTransactions(count: Int32, transactions: [Api.BroadcastRevenueTransaction]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .broadcastRevenueTransactions(let count, let transactions): + if boxed { + buffer.appendInt32(-2028632986) + } + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(transactions.count)) + for item in transactions { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .broadcastRevenueTransactions(let count, let transactions): + return ("broadcastRevenueTransactions", [("count", count as Any), ("transactions", transactions as Any)]) + } + } + + public static func parse_broadcastRevenueTransactions(_ reader: BufferReader) -> BroadcastRevenueTransactions? { var _1: Int32? _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Buffer? - _3 = parseBytes(reader) - var _4: Buffer? - _4 = parseBytes(reader) - var _5: [Api.FileHash]? + var _2: [Api.BroadcastRevenueTransaction]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BroadcastRevenueTransaction.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.upload.File.fileCdnRedirect(dcId: _1!, fileToken: _2!, encryptionKey: _3!, encryptionIv: _4!, fileHashes: _5!) + if _c1 && _c2 { + return Api.stats.BroadcastRevenueTransactions.broadcastRevenueTransactions(count: _1!, transactions: _2!) } else { return nil @@ -194,52 +414,202 @@ public extension Api.upload { } } -public extension Api.upload { - enum WebFile: TypeConstructorDescription { - case webFile(size: Int32, mimeType: String, fileType: Api.storage.FileType, mtime: Int32, bytes: Buffer) +public extension Api.stats { + enum BroadcastRevenueWithdrawalUrl: TypeConstructorDescription { + case broadcastRevenueWithdrawalUrl(url: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .webFile(let size, let mimeType, let fileType, let mtime, let bytes): + case .broadcastRevenueWithdrawalUrl(let url): if boxed { - buffer.appendInt32(568808380) + buffer.appendInt32(-328886473) } - serializeInt32(size, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - fileType.serialize(buffer, true) - serializeInt32(mtime, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .webFile(let size, let mimeType, let fileType, let mtime, let bytes): - return ("webFile", [("size", size as Any), ("mimeType", mimeType as Any), ("fileType", fileType as Any), ("mtime", mtime as Any), ("bytes", bytes as Any)]) + case .broadcastRevenueWithdrawalUrl(let url): + return ("broadcastRevenueWithdrawalUrl", [("url", url as Any)]) } } - public static func parse_webFile(_ reader: BufferReader) -> WebFile? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.storage.FileType? + public static func parse_broadcastRevenueWithdrawalUrl(_ reader: BufferReader) -> BroadcastRevenueWithdrawalUrl? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.stats.BroadcastRevenueWithdrawalUrl.broadcastRevenueWithdrawalUrl(url: _1!) + } + else { + return nil + } + } + + } +} +public extension Api.stats { + enum BroadcastStats: TypeConstructorDescription { + case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, reactionsPerPost: Api.StatsAbsValueAndPrev, viewsPerStory: Api.StatsAbsValueAndPrev, sharesPerStory: Api.StatsAbsValueAndPrev, reactionsPerStory: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph, ivInteractionsGraph: Api.StatsGraph, viewsBySourceGraph: Api.StatsGraph, newFollowersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph, storyInteractionsGraph: Api.StatsGraph, storyReactionsByEmotionGraph: Api.StatsGraph, recentPostsInteractions: [Api.PostInteractionCounters]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let reactionsPerPost, let viewsPerStory, let sharesPerStory, let reactionsPerStory, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let reactionsByEmotionGraph, let storyInteractionsGraph, let storyReactionsByEmotionGraph, let recentPostsInteractions): + if boxed { + buffer.appendInt32(963421692) + } + period.serialize(buffer, true) + followers.serialize(buffer, true) + viewsPerPost.serialize(buffer, true) + sharesPerPost.serialize(buffer, true) + reactionsPerPost.serialize(buffer, true) + viewsPerStory.serialize(buffer, true) + sharesPerStory.serialize(buffer, true) + reactionsPerStory.serialize(buffer, true) + enabledNotifications.serialize(buffer, true) + growthGraph.serialize(buffer, true) + followersGraph.serialize(buffer, true) + muteGraph.serialize(buffer, true) + topHoursGraph.serialize(buffer, true) + interactionsGraph.serialize(buffer, true) + ivInteractionsGraph.serialize(buffer, true) + viewsBySourceGraph.serialize(buffer, true) + newFollowersBySourceGraph.serialize(buffer, true) + languagesGraph.serialize(buffer, true) + reactionsByEmotionGraph.serialize(buffer, true) + storyInteractionsGraph.serialize(buffer, true) + storyReactionsByEmotionGraph.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentPostsInteractions.count)) + for item in recentPostsInteractions { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let reactionsPerPost, let viewsPerStory, let sharesPerStory, let reactionsPerStory, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let reactionsByEmotionGraph, let storyInteractionsGraph, let storyReactionsByEmotionGraph, let recentPostsInteractions): + return ("broadcastStats", [("period", period as Any), ("followers", followers as Any), ("viewsPerPost", viewsPerPost as Any), ("sharesPerPost", sharesPerPost as Any), ("reactionsPerPost", reactionsPerPost as Any), ("viewsPerStory", viewsPerStory as Any), ("sharesPerStory", sharesPerStory as Any), ("reactionsPerStory", reactionsPerStory as Any), ("enabledNotifications", enabledNotifications as Any), ("growthGraph", growthGraph as Any), ("followersGraph", followersGraph as Any), ("muteGraph", muteGraph as Any), ("topHoursGraph", topHoursGraph as Any), ("interactionsGraph", interactionsGraph as Any), ("ivInteractionsGraph", ivInteractionsGraph as Any), ("viewsBySourceGraph", viewsBySourceGraph as Any), ("newFollowersBySourceGraph", newFollowersBySourceGraph as Any), ("languagesGraph", languagesGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any), ("storyInteractionsGraph", storyInteractionsGraph as Any), ("storyReactionsByEmotionGraph", storyReactionsByEmotionGraph as Any), ("recentPostsInteractions", recentPostsInteractions as Any)]) + } + } + + public static func parse_broadcastStats(_ reader: BufferReader) -> BroadcastStats? { + var _1: Api.StatsDateRangeDays? if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.storage.FileType + _1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays + } + var _2: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _3: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _4: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _5: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _6: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _7: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _8: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _9: Api.StatsPercentValue? + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue + } + var _10: Api.StatsGraph? + if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _11: Api.StatsGraph? + if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _12: Api.StatsGraph? + if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _13: Api.StatsGraph? + if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _14: Api.StatsGraph? + if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _15: Api.StatsGraph? + if let signature = reader.readInt32() { + _15 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _16: Api.StatsGraph? + if let signature = reader.readInt32() { + _16 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _17: Api.StatsGraph? + if let signature = reader.readInt32() { + _17 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _18: Api.StatsGraph? + if let signature = reader.readInt32() { + _18 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _19: Api.StatsGraph? + if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _20: Api.StatsGraph? + if let signature = reader.readInt32() { + _20 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _21: Api.StatsGraph? + if let signature = reader.readInt32() { + _21 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _22: [Api.PostInteractionCounters]? + if let _ = reader.readInt32() { + _22 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PostInteractionCounters.self) } - var _4: Int32? - _4 = reader.readInt32() - var _5: Buffer? - _5 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.upload.WebFile.webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!) + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + let _c12 = _12 != nil + let _c13 = _13 != nil + let _c14 = _14 != nil + let _c15 = _15 != nil + let _c16 = _16 != nil + let _c17 = _17 != nil + let _c18 = _18 != nil + let _c19 = _19 != nil + let _c20 = _20 != nil + let _c21 = _21 != nil + let _c22 = _22 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { + return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, reactionsPerPost: _5!, viewsPerStory: _6!, sharesPerStory: _7!, reactionsPerStory: _8!, enabledNotifications: _9!, growthGraph: _10!, followersGraph: _11!, muteGraph: _12!, topHoursGraph: _13!, interactionsGraph: _14!, ivInteractionsGraph: _15!, viewsBySourceGraph: _16!, newFollowersBySourceGraph: _17!, languagesGraph: _18!, reactionsByEmotionGraph: _19!, storyInteractionsGraph: _20!, storyReactionsByEmotionGraph: _21!, recentPostsInteractions: _22!) } else { return nil @@ -248,20 +618,42 @@ public extension Api.upload { } } -public extension Api.users { - enum UserFull: TypeConstructorDescription { - case userFull(fullUser: Api.UserFull, chats: [Api.Chat], users: [Api.User]) +public extension Api.stats { + enum MegagroupStats: TypeConstructorDescription { + case megagroupStats(period: Api.StatsDateRangeDays, members: Api.StatsAbsValueAndPrev, messages: Api.StatsAbsValueAndPrev, viewers: Api.StatsAbsValueAndPrev, posters: Api.StatsAbsValueAndPrev, growthGraph: Api.StatsGraph, membersGraph: Api.StatsGraph, newMembersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, messagesGraph: Api.StatsGraph, actionsGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, weekdaysGraph: Api.StatsGraph, topPosters: [Api.StatsGroupTopPoster], topAdmins: [Api.StatsGroupTopAdmin], topInviters: [Api.StatsGroupTopInviter], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .userFull(let fullUser, let chats, let users): + case .megagroupStats(let period, let members, let messages, let viewers, let posters, let growthGraph, let membersGraph, let newMembersBySourceGraph, let languagesGraph, let messagesGraph, let actionsGraph, let topHoursGraph, let weekdaysGraph, let topPosters, let topAdmins, let topInviters, let users): if boxed { - buffer.appendInt32(997004590) + buffer.appendInt32(-276825834) } - fullUser.serialize(buffer, true) + period.serialize(buffer, true) + members.serialize(buffer, true) + messages.serialize(buffer, true) + viewers.serialize(buffer, true) + posters.serialize(buffer, true) + growthGraph.serialize(buffer, true) + membersGraph.serialize(buffer, true) + newMembersBySourceGraph.serialize(buffer, true) + languagesGraph.serialize(buffer, true) + messagesGraph.serialize(buffer, true) + actionsGraph.serialize(buffer, true) + topHoursGraph.serialize(buffer, true) + weekdaysGraph.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(topPosters.count)) + for item in topPosters { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(topAdmins.count)) + for item in topAdmins { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(topInviters.count)) + for item in topInviters { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -275,29 +667,1065 @@ public extension Api.users { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .userFull(let fullUser, let chats, let users): - return ("userFull", [("fullUser", fullUser as Any), ("chats", chats as Any), ("users", users as Any)]) + case .megagroupStats(let period, let members, let messages, let viewers, let posters, let growthGraph, let membersGraph, let newMembersBySourceGraph, let languagesGraph, let messagesGraph, let actionsGraph, let topHoursGraph, let weekdaysGraph, let topPosters, let topAdmins, let topInviters, let users): + return ("megagroupStats", [("period", period as Any), ("members", members as Any), ("messages", messages as Any), ("viewers", viewers as Any), ("posters", posters as Any), ("growthGraph", growthGraph as Any), ("membersGraph", membersGraph as Any), ("newMembersBySourceGraph", newMembersBySourceGraph as Any), ("languagesGraph", languagesGraph as Any), ("messagesGraph", messagesGraph as Any), ("actionsGraph", actionsGraph as Any), ("topHoursGraph", topHoursGraph as Any), ("weekdaysGraph", weekdaysGraph as Any), ("topPosters", topPosters as Any), ("topAdmins", topAdmins as Any), ("topInviters", topInviters as Any), ("users", users as Any)]) } } - public static func parse_userFull(_ reader: BufferReader) -> UserFull? { - var _1: Api.UserFull? + public static func parse_megagroupStats(_ reader: BufferReader) -> MegagroupStats? { + var _1: Api.StatsDateRangeDays? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.UserFull + _1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + var _2: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev } - var _3: [Api.User]? + var _3: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _4: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _5: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _6: Api.StatsGraph? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _7: Api.StatsGraph? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _8: Api.StatsGraph? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _9: Api.StatsGraph? + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _10: Api.StatsGraph? + if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _11: Api.StatsGraph? + if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _12: Api.StatsGraph? + if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _13: Api.StatsGraph? + if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _14: [Api.StatsGroupTopPoster]? + if let _ = reader.readInt32() { + _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopPoster.self) + } + var _15: [Api.StatsGroupTopAdmin]? + if let _ = reader.readInt32() { + _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopAdmin.self) + } + var _16: [Api.StatsGroupTopInviter]? + if let _ = reader.readInt32() { + _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopInviter.self) + } + var _17: [Api.User]? + if let _ = reader.readInt32() { + _17 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + let _c12 = _12 != nil + let _c13 = _13 != nil + let _c14 = _14 != nil + let _c15 = _15 != nil + let _c16 = _16 != nil + let _c17 = _17 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { + return Api.stats.MegagroupStats.megagroupStats(period: _1!, members: _2!, messages: _3!, viewers: _4!, posters: _5!, growthGraph: _6!, membersGraph: _7!, newMembersBySourceGraph: _8!, languagesGraph: _9!, messagesGraph: _10!, actionsGraph: _11!, topHoursGraph: _12!, weekdaysGraph: _13!, topPosters: _14!, topAdmins: _15!, topInviters: _16!, users: _17!) + } + else { + return nil + } + } + + } +} +public extension Api.stats { + enum MessageStats: TypeConstructorDescription { + case messageStats(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageStats(let viewsGraph, let reactionsByEmotionGraph): + if boxed { + buffer.appendInt32(2145983508) + } + viewsGraph.serialize(buffer, true) + reactionsByEmotionGraph.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageStats(let viewsGraph, let reactionsByEmotionGraph): + return ("messageStats", [("viewsGraph", viewsGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any)]) + } + } + + public static func parse_messageStats(_ reader: BufferReader) -> MessageStats? { + var _1: Api.StatsGraph? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _2: Api.StatsGraph? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.stats.MessageStats.messageStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!) + } + else { + return nil + } + } + + } +} +public extension Api.stats { + enum PublicForwards: TypeConstructorDescription { + case publicForwards(flags: Int32, count: Int32, forwards: [Api.PublicForward], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users): + if boxed { + buffer.appendInt32(-1828487648) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(forwards.count)) + for item in forwards { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .publicForwards(let flags, let count, let forwards, let nextOffset, let chats, let users): + return ("publicForwards", [("flags", flags as Any), ("count", count as Any), ("forwards", forwards as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_publicForwards(_ reader: BufferReader) -> PublicForwards? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.PublicForward]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PublicForward.self) + } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.stats.PublicForwards.publicForwards(flags: _1!, count: _2!, forwards: _3!, nextOffset: _4, chats: _5!, users: _6!) + } + else { + return nil + } + } + + } +} +public extension Api.stats { + enum StoryStats: TypeConstructorDescription { + case storyStats(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyStats(let viewsGraph, let reactionsByEmotionGraph): + if boxed { + buffer.appendInt32(1355613820) + } + viewsGraph.serialize(buffer, true) + reactionsByEmotionGraph.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyStats(let viewsGraph, let reactionsByEmotionGraph): + return ("storyStats", [("viewsGraph", viewsGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any)]) + } + } + + public static func parse_storyStats(_ reader: BufferReader) -> StoryStats? { + var _1: Api.StatsGraph? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _2: Api.StatsGraph? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.stats.StoryStats.storyStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!) + } + else { + return nil + } + } + + } +} +public extension Api.stickers { + enum SuggestedShortName: TypeConstructorDescription { + case suggestedShortName(shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .suggestedShortName(let shortName): + if boxed { + buffer.appendInt32(-2046910401) + } + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .suggestedShortName(let shortName): + return ("suggestedShortName", [("shortName", shortName as Any)]) + } + } + + public static func parse_suggestedShortName(_ reader: BufferReader) -> SuggestedShortName? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.stickers.SuggestedShortName.suggestedShortName(shortName: _1!) + } + else { + return nil + } + } + + } +} +public extension Api.storage { + enum FileType: TypeConstructorDescription { + case fileGif + case fileJpeg + case fileMov + case fileMp3 + case fileMp4 + case filePartial + case filePdf + case filePng + case fileUnknown + case fileWebp + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .fileGif: + if boxed { + buffer.appendInt32(-891180321) + } + + break + case .fileJpeg: + if boxed { + buffer.appendInt32(8322574) + } + + break + case .fileMov: + if boxed { + buffer.appendInt32(1258941372) + } + + break + case .fileMp3: + if boxed { + buffer.appendInt32(1384777335) + } + + break + case .fileMp4: + if boxed { + buffer.appendInt32(-1278304028) + } + + break + case .filePartial: + if boxed { + buffer.appendInt32(1086091090) + } + + break + case .filePdf: + if boxed { + buffer.appendInt32(-1373745011) + } + + break + case .filePng: + if boxed { + buffer.appendInt32(172975040) + } + + break + case .fileUnknown: + if boxed { + buffer.appendInt32(-1432995067) + } + + break + case .fileWebp: + if boxed { + buffer.appendInt32(276907596) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .fileGif: + return ("fileGif", []) + case .fileJpeg: + return ("fileJpeg", []) + case .fileMov: + return ("fileMov", []) + case .fileMp3: + return ("fileMp3", []) + case .fileMp4: + return ("fileMp4", []) + case .filePartial: + return ("filePartial", []) + case .filePdf: + return ("filePdf", []) + case .filePng: + return ("filePng", []) + case .fileUnknown: + return ("fileUnknown", []) + case .fileWebp: + return ("fileWebp", []) + } + } + + public static func parse_fileGif(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileGif + } + public static func parse_fileJpeg(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileJpeg + } + public static func parse_fileMov(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileMov + } + public static func parse_fileMp3(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileMp3 + } + public static func parse_fileMp4(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileMp4 + } + public static func parse_filePartial(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.filePartial + } + public static func parse_filePdf(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.filePdf + } + public static func parse_filePng(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.filePng + } + public static func parse_fileUnknown(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileUnknown + } + public static func parse_fileWebp(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileWebp + } + + } +} +public extension Api.stories { + enum AllStories: TypeConstructorDescription { + case allStories(flags: Int32, count: Int32, state: String, peerStories: [Api.PeerStories], chats: [Api.Chat], users: [Api.User], stealthMode: Api.StoriesStealthMode) + case allStoriesNotModified(flags: Int32, state: String, stealthMode: Api.StoriesStealthMode) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .allStories(let flags, let count, let state, let peerStories, let chats, let users, let stealthMode): + if boxed { + buffer.appendInt32(1862033025) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + serializeString(state, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peerStories.count)) + for item in peerStories { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + stealthMode.serialize(buffer, true) + break + case .allStoriesNotModified(let flags, let state, let stealthMode): + if boxed { + buffer.appendInt32(291044926) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(state, buffer: buffer, boxed: false) + stealthMode.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .allStories(let flags, let count, let state, let peerStories, let chats, let users, let stealthMode): + return ("allStories", [("flags", flags as Any), ("count", count as Any), ("state", state as Any), ("peerStories", peerStories as Any), ("chats", chats as Any), ("users", users as Any), ("stealthMode", stealthMode as Any)]) + case .allStoriesNotModified(let flags, let state, let stealthMode): + return ("allStoriesNotModified", [("flags", flags as Any), ("state", state as Any), ("stealthMode", stealthMode as Any)]) + } + } + + public static func parse_allStories(_ reader: BufferReader) -> AllStories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: [Api.PeerStories]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerStories.self) + } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _7: Api.StoriesStealthMode? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.stories.AllStories.allStories(flags: _1!, count: _2!, state: _3!, peerStories: _4!, chats: _5!, users: _6!, stealthMode: _7!) + } + else { + return nil + } + } + public static func parse_allStoriesNotModified(_ reader: BufferReader) -> AllStories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.StoriesStealthMode? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.stories.AllStories.allStoriesNotModified(flags: _1!, state: _2!, stealthMode: _3!) + } + else { + return nil + } + } + + } +} +public extension Api.stories { + enum PeerStories: TypeConstructorDescription { + case peerStories(stories: Api.PeerStories, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerStories(let stories, let chats, let users): + if boxed { + buffer.appendInt32(-890861720) + } + stories.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .peerStories(let stories, let chats, let users): + return ("peerStories", [("stories", stories as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? { + var _1: Api.PeerStories? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PeerStories + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.stories.PeerStories.peerStories(stories: _1!, chats: _2!, users: _3!) + } + else { + return nil + } + } + + } +} +public extension Api.stories { + enum Stories: TypeConstructorDescription { + case stories(flags: Int32, count: Int32, stories: [Api.StoryItem], pinnedToTop: [Int32]?, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users): + if boxed { + buffer.appendInt32(1673780490) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stories.count)) + for item in stories { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(pinnedToTop!.count)) + for item in pinnedToTop! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users): + return ("stories", [("flags", flags as Any), ("count", count as Any), ("stories", stories as Any), ("pinnedToTop", pinnedToTop as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_stories(_ reader: BufferReader) -> Stories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.StoryItem]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self) + } + var _4: [Int32]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + var _5: [Api.Chat]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.stories.Stories.stories(flags: _1!, count: _2!, stories: _3!, pinnedToTop: _4, chats: _5!, users: _6!) + } + else { + return nil + } + } + + } +} +public extension Api.stories { + enum StoryReactionsList: TypeConstructorDescription { + case storyReactionsList(flags: Int32, count: Int32, reactions: [Api.StoryReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset): + if boxed { + buffer.appendInt32(-1436583780) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(reactions.count)) + for item in reactions { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset): + return ("storyReactionsList", [("flags", flags as Any), ("count", count as Any), ("reactions", reactions as Any), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)]) + } + } + + public static func parse_storyReactionsList(_ reader: BufferReader) -> StoryReactionsList? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.StoryReaction]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryReaction.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.stories.StoryReactionsList.storyReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6) + } + else { + return nil + } + } + + } +} +public extension Api.stories { + enum StoryViews: TypeConstructorDescription { + case storyViews(views: [Api.StoryViews], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyViews(let views, let users): + if boxed { + buffer.appendInt32(-560009955) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(views.count)) + for item in views { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyViews(let views, let users): + return ("storyViews", [("views", views as Any), ("users", users as Any)]) + } + } + + public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { + var _1: [Api.StoryViews]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryViews.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.stories.StoryViews.storyViews(views: _1!, users: _2!) + } + else { + return nil + } + } + + } +} +public extension Api.stories { + enum StoryViewsList: TypeConstructorDescription { + case storyViewsList(flags: Int32, count: Int32, viewsCount: Int32, forwardsCount: Int32, reactionsCount: Int32, views: [Api.StoryView], chats: [Api.Chat], users: [Api.User], nextOffset: String?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyViewsList(let flags, let count, let viewsCount, let forwardsCount, let reactionsCount, let views, let chats, let users, let nextOffset): + if boxed { + buffer.appendInt32(1507299269) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + serializeInt32(viewsCount, buffer: buffer, boxed: false) + serializeInt32(forwardsCount, buffer: buffer, boxed: false) + serializeInt32(reactionsCount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(views.count)) + for item in views { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyViewsList(let flags, let count, let viewsCount, let forwardsCount, let reactionsCount, let views, let chats, let users, let nextOffset): + return ("storyViewsList", [("flags", flags as Any), ("count", count as Any), ("viewsCount", viewsCount as Any), ("forwardsCount", forwardsCount as Any), ("reactionsCount", reactionsCount as Any), ("views", views as Any), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)]) + } + } + + public static func parse_storyViewsList(_ reader: BufferReader) -> StoryViewsList? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: [Api.StoryView]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryView.self) + } + var _7: [Api.Chat]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _8: [Api.User]? + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } + var _9: String? + if Int(_1!) & Int(1 << 0) != 0 {_9 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.stories.StoryViewsList.storyViewsList(flags: _1!, count: _2!, viewsCount: _3!, forwardsCount: _4!, reactionsCount: _5!, views: _6!, chats: _7!, users: _8!, nextOffset: _9) + } + else { + return nil + } + } + + } +} +public extension Api.updates { + indirect enum ChannelDifference: TypeConstructorDescription { + case channelDifference(flags: Int32, pts: Int32, timeout: Int32?, newMessages: [Api.Message], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User]) + case channelDifferenceEmpty(flags: Int32, pts: Int32, timeout: Int32?) + case channelDifferenceTooLong(flags: Int32, timeout: Int32?, dialog: Api.Dialog, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users): + if boxed { + buffer.appendInt32(543450958) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(newMessages.count)) + for item in newMessages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(otherUpdates.count)) + for item in otherUpdates { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .channelDifferenceEmpty(let flags, let pts, let timeout): + if boxed { + buffer.appendInt32(1041346555) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)} + break + case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users): + if boxed { + buffer.appendInt32(-1531132162) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)} + dialog.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users): + return ("channelDifference", [("flags", flags as Any), ("pts", pts as Any), ("timeout", timeout as Any), ("newMessages", newMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any)]) + case .channelDifferenceEmpty(let flags, let pts, let timeout): + return ("channelDifferenceEmpty", [("flags", flags as Any), ("pts", pts as Any), ("timeout", timeout as Any)]) + case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users): + return ("channelDifferenceTooLong", [("flags", flags as Any), ("timeout", timeout as Any), ("dialog", dialog as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_channelDifference(_ reader: BufferReader) -> ChannelDifference? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } + var _4: [Api.Message]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _5: [Api.Update]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + } + var _6: [Api.Chat]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _7: [Api.User]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.updates.ChannelDifference.channelDifference(flags: _1!, pts: _2!, timeout: _3, newMessages: _4!, otherUpdates: _5!, chats: _6!, users: _7!) + } + else { + return nil + } + } + public static func parse_channelDifferenceEmpty(_ reader: BufferReader) -> ChannelDifference? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil if _c1 && _c2 && _c3 { - return Api.users.UserFull.userFull(fullUser: _1!, chats: _2!, users: _3!) + return Api.updates.ChannelDifference.channelDifferenceEmpty(flags: _1!, pts: _2!, timeout: _3) + } + else { + return nil + } + } + public static func parse_channelDifferenceTooLong(_ reader: BufferReader) -> ChannelDifference? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } + var _3: Api.Dialog? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Dialog + } + var _4: [Api.Message]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.updates.ChannelDifference.channelDifferenceTooLong(flags: _1!, timeout: _2, dialog: _3!, messages: _4!, chats: _5!, users: _6!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api35.swift b/submodules/TelegramApi/Sources/Api35.swift index bc3083babea..f6d0dc14fb8 100644 --- a/submodules/TelegramApi/Sources/Api35.swift +++ b/submodules/TelegramApi/Sources/Api35.swift @@ -1,10611 +1,512 @@ -public extension Api.functions.account { - static func acceptAuthorization(botId: Int64, scope: String, publicKey: String, valueHashes: [Api.SecureValueHash], credentials: Api.SecureCredentialsEncrypted) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-202552205) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeString(scope, buffer: buffer, boxed: false) - serializeString(publicKey, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(valueHashes.count)) - for item in valueHashes { - item.serialize(buffer, true) - } - credentials.serialize(buffer, true) - return (FunctionDescription(name: "account.acceptAuthorization", parameters: [("botId", String(describing: botId)), ("scope", String(describing: scope)), ("publicKey", String(describing: publicKey)), ("valueHashes", String(describing: valueHashes)), ("credentials", String(describing: credentials))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func cancelPasswordEmail() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1043606090) - - return (FunctionDescription(name: "account.cancelPasswordEmail", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func changeAuthorizationSettings(flags: Int32, hash: Int64, encryptedRequestsDisabled: Api.Bool?, callRequestsDisabled: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1089766498) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {encryptedRequestsDisabled!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {callRequestsDisabled!.serialize(buffer, true)} - return (FunctionDescription(name: "account.changeAuthorizationSettings", parameters: [("flags", String(describing: flags)), ("hash", String(describing: hash)), ("encryptedRequestsDisabled", String(describing: encryptedRequestsDisabled)), ("callRequestsDisabled", String(describing: callRequestsDisabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func changePhone(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1891839707) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeString(phoneCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.changePhone", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("phoneCode", String(describing: phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User - } - return result - }) - } -} -public extension Api.functions.account { - static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(655677548) - serializeString(username, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.checkUsername", parameters: [("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func clearRecentEmojiStatuses() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(404757166) - - return (FunctionDescription(name: "account.clearRecentEmojiStatuses", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func confirmPasswordEmail(code: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1881204448) - serializeString(code, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.confirmPasswordEmail", parameters: [("code", String(describing: code))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func confirmPhone(phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1596029123) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeString(phoneCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.confirmPhone", parameters: [("phoneCodeHash", String(describing: phoneCodeHash)), ("phoneCode", String(describing: phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func createBusinessChatLink(link: Api.InputBusinessChatLink) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2007898482) - link.serialize(buffer, true) - return (FunctionDescription(name: "account.createBusinessChatLink", parameters: [("link", String(describing: link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BusinessChatLink? in - let reader = BufferReader(buffer) - var result: Api.BusinessChatLink? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.BusinessChatLink - } - return result - }) - } -} -public extension Api.functions.account { - static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1697530880) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(slug, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(settings!.count)) - for item in settings! { - item.serialize(buffer, true) - }} - return (FunctionDescription(name: "account.createTheme", parameters: [("flags", String(describing: flags)), ("slug", String(describing: slug)), ("title", String(describing: title)), ("document", String(describing: document)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in - let reader = BufferReader(buffer) - var result: Api.Theme? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Theme - } - return result - }) - } -} -public extension Api.functions.account { - static func declinePasswordReset() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1284770294) - - return (FunctionDescription(name: "account.declinePasswordReset", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func deleteAccount(flags: Int32, reason: String, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1564422284) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(reason, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {password!.serialize(buffer, true)} - return (FunctionDescription(name: "account.deleteAccount", parameters: [("flags", String(describing: flags)), ("reason", String(describing: reason)), ("password", String(describing: password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func deleteAutoSaveExceptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1404829728) - - return (FunctionDescription(name: "account.deleteAutoSaveExceptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func deleteBusinessChatLink(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1611085428) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.deleteBusinessChatLink", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func deleteSecureValue(types: [Api.SecureValueType]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1199522741) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(types.count)) - for item in types { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "account.deleteSecureValue", parameters: [("types", String(describing: types))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func disablePeerConnectedBot(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1581481689) - peer.serialize(buffer, true) - return (FunctionDescription(name: "account.disablePeerConnectedBot", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func editBusinessChatLink(slug: String, link: Api.InputBusinessChatLink) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1942744913) - serializeString(slug, buffer: buffer, boxed: false) - link.serialize(buffer, true) - return (FunctionDescription(name: "account.editBusinessChatLink", parameters: [("slug", String(describing: slug)), ("link", String(describing: link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BusinessChatLink? in - let reader = BufferReader(buffer) - var result: Api.BusinessChatLink? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.BusinessChatLink - } - return result - }) - } -} -public extension Api.functions.account { - static func finishTakeoutSession(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(489050862) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.finishTakeoutSession", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func getAccountTTL() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(150761757) - - return (FunctionDescription(name: "account.getAccountTTL", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AccountDaysTTL? in - let reader = BufferReader(buffer) - var result: Api.AccountDaysTTL? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.AccountDaysTTL - } - return result - }) - } -} -public extension Api.functions.account { - static func getAllSecureValues() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SecureValue]>) { - let buffer = Buffer() - buffer.appendInt32(-1299661699) - - return (FunctionDescription(name: "account.getAllSecureValues", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SecureValue]? in - let reader = BufferReader(buffer) - var result: [Api.SecureValue]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) - } - return result - }) - } -} -public extension Api.functions.account { - static func getAuthorizationForm(botId: Int64, scope: String, publicKey: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1456907910) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeString(scope, buffer: buffer, boxed: false) - serializeString(publicKey, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getAuthorizationForm", parameters: [("botId", String(describing: botId)), ("scope", String(describing: scope)), ("publicKey", String(describing: publicKey))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AuthorizationForm? in - let reader = BufferReader(buffer) - var result: Api.account.AuthorizationForm? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.AuthorizationForm - } - return result - }) - } -} -public extension Api.functions.account { - static func getAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-484392616) - - return (FunctionDescription(name: "account.getAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Authorizations? in - let reader = BufferReader(buffer) - var result: Api.account.Authorizations? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Authorizations - } - return result - }) - } -} -public extension Api.functions.account { - static func getAutoDownloadSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1457130303) - - return (FunctionDescription(name: "account.getAutoDownloadSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AutoDownloadSettings? in - let reader = BufferReader(buffer) - var result: Api.account.AutoDownloadSettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.AutoDownloadSettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getAutoSaveSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1379156774) - - return (FunctionDescription(name: "account.getAutoSaveSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AutoSaveSettings? in - let reader = BufferReader(buffer) - var result: Api.account.AutoSaveSettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.AutoSaveSettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getBotBusinessConnection(connectionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1990746736) - serializeString(connectionId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getBotBusinessConnection", parameters: [("connectionId", String(describing: connectionId))]), 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.account { - static func getBusinessChatLinks() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1869667809) - - return (FunctionDescription(name: "account.getBusinessChatLinks", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.BusinessChatLinks? in - let reader = BufferReader(buffer) - var result: Api.account.BusinessChatLinks? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.BusinessChatLinks - } - return result - }) - } -} -public extension Api.functions.account { - static func getChannelDefaultEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1999087573) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getChannelDefaultEmojiStatuses", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in - let reader = BufferReader(buffer) - var result: Api.account.EmojiStatuses? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses - } - return result - }) - } -} -public extension Api.functions.account { - static func getChannelRestrictedStatusEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(900325589) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getChannelRestrictedStatusEmojis", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in - let reader = BufferReader(buffer) - var result: Api.EmojiList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiList - } - return result - }) - } -} -public extension Api.functions.account { - static func getChatThemes(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-700916087) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in - let reader = BufferReader(buffer) - var result: Api.account.Themes? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Themes - } - return result - }) - } -} -public extension Api.functions.account { - static func getConnectedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1319421967) - - return (FunctionDescription(name: "account.getConnectedBots", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ConnectedBots? in - let reader = BufferReader(buffer) - var result: Api.account.ConnectedBots? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.ConnectedBots - } - return result - }) - } -} -public extension Api.functions.account { - static func getContactSignUpNotification() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1626880216) - - return (FunctionDescription(name: "account.getContactSignUpNotification", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func getContentSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1952756306) - - return (FunctionDescription(name: "account.getContentSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ContentSettings? in - let reader = BufferReader(buffer) - var result: Api.account.ContentSettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.ContentSettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getDefaultBackgroundEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1509246514) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getDefaultBackgroundEmojis", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in - let reader = BufferReader(buffer) - var result: Api.EmojiList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiList - } - return result - }) - } -} -public extension Api.functions.account { - static func getDefaultEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-696962170) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getDefaultEmojiStatuses", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in - let reader = BufferReader(buffer) - var result: Api.account.EmojiStatuses? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses - } - return result - }) - } -} -public extension Api.functions.account { - static func getDefaultGroupPhotoEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1856479058) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getDefaultGroupPhotoEmojis", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in - let reader = BufferReader(buffer) - var result: Api.EmojiList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiList - } - return result - }) - } -} -public extension Api.functions.account { - static func getDefaultProfilePhotoEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-495647960) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getDefaultProfilePhotoEmojis", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in - let reader = BufferReader(buffer) - var result: Api.EmojiList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiList - } - return result - }) - } -} -public extension Api.functions.account { - static func getGlobalPrivacySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-349483786) - - return (FunctionDescription(name: "account.getGlobalPrivacySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.GlobalPrivacySettings? in - let reader = BufferReader(buffer) - var result: Api.GlobalPrivacySettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.GlobalPrivacySettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getMultiWallPapers(wallpapers: [Api.InputWallPaper]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.WallPaper]>) { - let buffer = Buffer() - buffer.appendInt32(1705865692) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(wallpapers.count)) - for item in wallpapers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "account.getMultiWallPapers", parameters: [("wallpapers", String(describing: wallpapers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.WallPaper]? in - let reader = BufferReader(buffer) - var result: [Api.WallPaper]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.WallPaper.self) - } - return result - }) - } -} -public extension Api.functions.account { - static func getNotifyExceptions(flags: Int32, peer: Api.InputNotifyPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1398240377) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} - return (FunctionDescription(name: "account.getNotifyExceptions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), 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.account { - static func getNotifySettings(peer: Api.InputNotifyPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(313765169) - peer.serialize(buffer, true) - return (FunctionDescription(name: "account.getNotifySettings", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.PeerNotifySettings? in - let reader = BufferReader(buffer) - var result: Api.PeerNotifySettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getPassword() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1418342645) - - return (FunctionDescription(name: "account.getPassword", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Password? in - let reader = BufferReader(buffer) - var result: Api.account.Password? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Password - } - return result - }) - } -} -public extension Api.functions.account { - static func getPasswordSettings(password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1663767815) - password.serialize(buffer, true) - return (FunctionDescription(name: "account.getPasswordSettings", parameters: [("password", String(describing: password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PasswordSettings? in - let reader = BufferReader(buffer) - var result: Api.account.PasswordSettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.PasswordSettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getPrivacy(key: Api.InputPrivacyKey) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-623130288) - key.serialize(buffer, true) - return (FunctionDescription(name: "account.getPrivacy", parameters: [("key", String(describing: key))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PrivacyRules? in - let reader = BufferReader(buffer) - var result: Api.account.PrivacyRules? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.PrivacyRules - } - return result - }) - } -} -public extension Api.functions.account { - static func getReactionsNotifySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(115172684) - - return (FunctionDescription(name: "account.getReactionsNotifySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReactionsNotifySettings? in - let reader = BufferReader(buffer) - var result: Api.ReactionsNotifySettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ReactionsNotifySettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getRecentEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(257392901) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getRecentEmojiStatuses", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in - let reader = BufferReader(buffer) - var result: Api.account.EmojiStatuses? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses - } - return result - }) - } -} -public extension Api.functions.account { - static func getSavedRingtones(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-510647672) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getSavedRingtones", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SavedRingtones? in - let reader = BufferReader(buffer) - var result: Api.account.SavedRingtones? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.SavedRingtones - } - return result - }) - } -} -public extension Api.functions.account { - static func getSecureValue(types: [Api.SecureValueType]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SecureValue]>) { - let buffer = Buffer() - buffer.appendInt32(1936088002) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(types.count)) - for item in types { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "account.getSecureValue", parameters: [("types", String(describing: types))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SecureValue]? in - let reader = BufferReader(buffer) - var result: [Api.SecureValue]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) - } - return result - }) - } -} -public extension Api.functions.account { - static func getTheme(format: String, theme: Api.InputTheme) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(978872812) - serializeString(format, buffer: buffer, boxed: false) - theme.serialize(buffer, true) - return (FunctionDescription(name: "account.getTheme", parameters: [("format", String(describing: format)), ("theme", String(describing: theme))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in - let reader = BufferReader(buffer) - var result: Api.Theme? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Theme - } - return result - }) - } -} -public extension Api.functions.account { - static func getThemes(format: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1913054296) - serializeString(format, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getThemes", parameters: [("format", String(describing: format)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in - let reader = BufferReader(buffer) - var result: Api.account.Themes? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Themes - } - return result - }) - } -} -public extension Api.functions.account { - static func getTmpPassword(password: Api.InputCheckPasswordSRP, period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1151208273) - password.serialize(buffer, true) - serializeInt32(period, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getTmpPassword", parameters: [("password", String(describing: password)), ("period", String(describing: period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.TmpPassword? in - let reader = BufferReader(buffer) - var result: Api.account.TmpPassword? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.TmpPassword - } - return result - }) - } -} -public extension Api.functions.account { - static func getWallPaper(wallpaper: Api.InputWallPaper) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-57811990) - wallpaper.serialize(buffer, true) - return (FunctionDescription(name: "account.getWallPaper", parameters: [("wallpaper", String(describing: wallpaper))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WallPaper? in - let reader = BufferReader(buffer) - var result: Api.WallPaper? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WallPaper - } - return result - }) - } -} -public extension Api.functions.account { - static func getWallPapers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(127302966) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getWallPapers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WallPapers? in - let reader = BufferReader(buffer) - var result: Api.account.WallPapers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.WallPapers - } - return result - }) - } -} -public extension Api.functions.account { - static func getWebAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(405695855) - - return (FunctionDescription(name: "account.getWebAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WebAuthorizations? in - let reader = BufferReader(buffer) - var result: Api.account.WebAuthorizations? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.WebAuthorizations - } - return result - }) - } -} -public extension Api.functions.account { - static func initTakeoutSession(flags: Int32, fileMaxSize: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1896617296) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 5) != 0 {serializeInt64(fileMaxSize!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "account.initTakeoutSession", parameters: [("flags", String(describing: flags)), ("fileMaxSize", String(describing: fileMaxSize))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Takeout? in - let reader = BufferReader(buffer) - var result: Api.account.Takeout? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Takeout - } - return result - }) - } -} -public extension Api.functions.account { - static func installTheme(flags: Int32, theme: Api.InputTheme?, format: String?, baseTheme: Api.BaseTheme?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-953697477) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {theme!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(format!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {baseTheme!.serialize(buffer, true)} - return (FunctionDescription(name: "account.installTheme", parameters: [("flags", String(describing: flags)), ("theme", String(describing: theme)), ("format", String(describing: format)), ("baseTheme", String(describing: baseTheme))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func installWallPaper(wallpaper: Api.InputWallPaper, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-18000023) - wallpaper.serialize(buffer, true) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.installWallPaper", parameters: [("wallpaper", String(describing: wallpaper)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func invalidateSignInCodes(codes: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-896866118) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(codes.count)) - for item in codes { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.invalidateSignInCodes", parameters: [("codes", String(describing: codes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func registerDevice(flags: Int32, tokenType: Int32, token: String, appSandbox: Api.Bool, secret: Buffer, otherUids: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-326762118) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(tokenType, buffer: buffer, boxed: false) - serializeString(token, buffer: buffer, boxed: false) - appSandbox.serialize(buffer, true) - serializeBytes(secret, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(otherUids.count)) - for item in otherUids { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.registerDevice", parameters: [("flags", String(describing: flags)), ("tokenType", String(describing: tokenType)), ("token", String(describing: token)), ("appSandbox", String(describing: appSandbox)), ("secret", String(describing: secret)), ("otherUids", String(describing: otherUids))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func reorderUsernames(order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-279966037) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.reorderUsernames", parameters: [("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func reportPeer(peer: Api.InputPeer, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-977650298) - peer.serialize(buffer, true) - reason.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.reportPeer", parameters: [("peer", String(describing: peer)), ("reason", String(describing: reason)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func reportProfilePhoto(peer: Api.InputPeer, photoId: Api.InputPhoto, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-91437323) - peer.serialize(buffer, true) - photoId.serialize(buffer, true) - reason.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.reportProfilePhoto", parameters: [("peer", String(describing: peer)), ("photoId", String(describing: photoId)), ("reason", String(describing: reason)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resendPasswordEmail() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2055154197) - - return (FunctionDescription(name: "account.resendPasswordEmail", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resetAuthorization(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-545786948) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.resetAuthorization", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resetNotifySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-612493497) - - return (FunctionDescription(name: "account.resetNotifySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resetPassword() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1828139493) - - return (FunctionDescription(name: "account.resetPassword", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ResetPasswordResult? in - let reader = BufferReader(buffer) - var result: Api.account.ResetPasswordResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.ResetPasswordResult - } - return result - }) - } -} -public extension Api.functions.account { - static func resetWallPapers() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1153722364) - - return (FunctionDescription(name: "account.resetWallPapers", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resetWebAuthorization(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(755087855) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.resetWebAuthorization", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resetWebAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1747789204) - - return (FunctionDescription(name: "account.resetWebAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resolveBusinessChatLink(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1418913262) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.resolveBusinessChatLink", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ResolvedBusinessChatLinks? in - let reader = BufferReader(buffer) - var result: Api.account.ResolvedBusinessChatLinks? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.ResolvedBusinessChatLinks - } - return result - }) - } -} -public extension Api.functions.account { - static func saveAutoDownloadSettings(flags: Int32, settings: Api.AutoDownloadSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1995661875) - serializeInt32(flags, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.saveAutoDownloadSettings", parameters: [("flags", String(describing: flags)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func saveAutoSaveSettings(flags: Int32, peer: Api.InputPeer?, settings: Api.AutoSaveSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-694451359) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {peer!.serialize(buffer, true)} - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.saveAutoSaveSettings", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func saveRingtone(id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1038768899) - id.serialize(buffer, true) - unsave.serialize(buffer, true) - return (FunctionDescription(name: "account.saveRingtone", parameters: [("id", String(describing: id)), ("unsave", String(describing: unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SavedRingtone? in - let reader = BufferReader(buffer) - var result: Api.account.SavedRingtone? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.SavedRingtone - } - return result - }) - } -} -public extension Api.functions.account { - static func saveSecureValue(value: Api.InputSecureValue, secureSecretId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1986010339) - value.serialize(buffer, true) - serializeInt64(secureSecretId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.saveSecureValue", parameters: [("value", String(describing: value)), ("secureSecretId", String(describing: secureSecretId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SecureValue? in - let reader = BufferReader(buffer) - var result: Api.SecureValue? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.SecureValue - } - return result - }) - } -} -public extension Api.functions.account { - static func saveTheme(theme: Api.InputTheme, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-229175188) - theme.serialize(buffer, true) - unsave.serialize(buffer, true) - return (FunctionDescription(name: "account.saveTheme", parameters: [("theme", String(describing: theme)), ("unsave", String(describing: unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func saveWallPaper(wallpaper: Api.InputWallPaper, unsave: Api.Bool, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1817860919) - wallpaper.serialize(buffer, true) - unsave.serialize(buffer, true) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.saveWallPaper", parameters: [("wallpaper", String(describing: wallpaper)), ("unsave", String(describing: unsave)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func sendChangePhoneCode(phoneNumber: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2108208411) - serializeString(phoneNumber, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.sendChangePhoneCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.account { - static func sendConfirmPhoneCode(hash: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(457157256) - serializeString(hash, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.sendConfirmPhoneCode", parameters: [("hash", String(describing: hash)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.account { - static func sendVerifyEmailCode(purpose: Api.EmailVerifyPurpose, email: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1730136133) - purpose.serialize(buffer, true) - serializeString(email, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.sendVerifyEmailCode", parameters: [("purpose", String(describing: purpose)), ("email", String(describing: email))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SentEmailCode? in - let reader = BufferReader(buffer) - var result: Api.account.SentEmailCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.SentEmailCode - } - return result - }) - } -} -public extension Api.functions.account { - static func sendVerifyPhoneCode(phoneNumber: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1516022023) - serializeString(phoneNumber, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.sendVerifyPhoneCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.account { - static func setAccountTTL(ttl: Api.AccountDaysTTL) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(608323678) - ttl.serialize(buffer, true) - return (FunctionDescription(name: "account.setAccountTTL", parameters: [("ttl", String(describing: ttl))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func setAuthorizationTTL(authorizationTtlDays: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1081501024) - serializeInt32(authorizationTtlDays, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.setAuthorizationTTL", parameters: [("authorizationTtlDays", String(describing: authorizationTtlDays))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func setContactSignUpNotification(silent: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-806076575) - silent.serialize(buffer, true) - return (FunctionDescription(name: "account.setContactSignUpNotification", parameters: [("silent", String(describing: silent))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func setContentSettings(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1250643605) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.setContentSettings", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func setGlobalPrivacySettings(settings: Api.GlobalPrivacySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(517647042) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.setGlobalPrivacySettings", parameters: [("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.GlobalPrivacySettings? in - let reader = BufferReader(buffer) - var result: Api.GlobalPrivacySettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.GlobalPrivacySettings - } - return result - }) - } -} -public extension Api.functions.account { - static func setPrivacy(key: Api.InputPrivacyKey, rules: [Api.InputPrivacyRule]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-906486552) - key.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(rules.count)) - for item in rules { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "account.setPrivacy", parameters: [("key", String(describing: key)), ("rules", String(describing: rules))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PrivacyRules? in - let reader = BufferReader(buffer) - var result: Api.account.PrivacyRules? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.PrivacyRules - } - return result - }) - } -} -public extension Api.functions.account { - static func setReactionsNotifySettings(settings: Api.ReactionsNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(829220168) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.setReactionsNotifySettings", parameters: [("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReactionsNotifySettings? in - let reader = BufferReader(buffer) - var result: Api.ReactionsNotifySettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ReactionsNotifySettings - } - return result - }) - } -} -public extension Api.functions.account { - static func toggleConnectedBotPaused(peer: Api.InputPeer, paused: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1684934807) - peer.serialize(buffer, true) - paused.serialize(buffer, true) - return (FunctionDescription(name: "account.toggleConnectedBotPaused", parameters: [("peer", String(describing: peer)), ("paused", String(describing: paused))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func toggleSponsoredMessages(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1176919155) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "account.toggleSponsoredMessages", parameters: [("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func toggleUsername(username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1490465654) - serializeString(username, buffer: buffer, boxed: false) - active.serialize(buffer, true) - return (FunctionDescription(name: "account.toggleUsername", parameters: [("username", String(describing: username)), ("active", String(describing: active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func unregisterDevice(tokenType: Int32, token: String, otherUids: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1779249670) - serializeInt32(tokenType, buffer: buffer, boxed: false) - serializeString(token, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(otherUids.count)) - for item in otherUids { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.unregisterDevice", parameters: [("tokenType", String(describing: tokenType)), ("token", String(describing: token)), ("otherUids", String(describing: otherUids))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBirthday(flags: Int32, birthday: Api.Birthday?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-865203183) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {birthday!.serialize(buffer, true)} - return (FunctionDescription(name: "account.updateBirthday", parameters: [("flags", String(describing: flags)), ("birthday", String(describing: birthday))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBusinessAwayMessage(flags: Int32, message: Api.InputBusinessAwayMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1570078811) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {message!.serialize(buffer, true)} - return (FunctionDescription(name: "account.updateBusinessAwayMessage", parameters: [("flags", String(describing: flags)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBusinessGreetingMessage(flags: Int32, message: Api.InputBusinessGreetingMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1724755908) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {message!.serialize(buffer, true)} - return (FunctionDescription(name: "account.updateBusinessGreetingMessage", parameters: [("flags", String(describing: flags)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBusinessIntro(flags: Int32, intro: Api.InputBusinessIntro?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1508585420) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {intro!.serialize(buffer, true)} - return (FunctionDescription(name: "account.updateBusinessIntro", parameters: [("flags", String(describing: flags)), ("intro", String(describing: intro))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBusinessLocation(flags: Int32, geoPoint: Api.InputGeoPoint?, address: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1637149926) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {geoPoint!.serialize(buffer, true)} - if Int(flags) & Int(1 << 0) != 0 {serializeString(address!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "account.updateBusinessLocation", parameters: [("flags", String(describing: flags)), ("geoPoint", String(describing: geoPoint)), ("address", String(describing: address))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBusinessWorkHours(flags: Int32, businessWorkHours: Api.BusinessWorkHours?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1258348646) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {businessWorkHours!.serialize(buffer, true)} - return (FunctionDescription(name: "account.updateBusinessWorkHours", parameters: [("flags", String(describing: flags)), ("businessWorkHours", String(describing: businessWorkHours))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateColor(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2096079197) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "account.updateColor", parameters: [("flags", String(describing: flags)), ("color", String(describing: color)), ("backgroundEmojiId", String(describing: backgroundEmojiId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateConnectedBot(flags: Int32, bot: Api.InputUser, recipients: Api.InputBusinessBotRecipients) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1138250269) - serializeInt32(flags, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - recipients.serialize(buffer, true) - return (FunctionDescription(name: "account.updateConnectedBot", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("recipients", String(describing: recipients))]), 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.account { - static func updateDeviceLocked(period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(954152242) - serializeInt32(period, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.updateDeviceLocked", parameters: [("period", String(describing: period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateEmojiStatus(emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-70001045) - emojiStatus.serialize(buffer, true) - return (FunctionDescription(name: "account.updateEmojiStatus", parameters: [("emojiStatus", String(describing: emojiStatus))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateNotifySettings(peer: Api.InputNotifyPeer, settings: Api.InputPeerNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2067899501) - peer.serialize(buffer, true) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.updateNotifySettings", parameters: [("peer", String(describing: peer)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updatePasswordSettings(password: Api.InputCheckPasswordSRP, newSettings: Api.account.PasswordInputSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1516564433) - password.serialize(buffer, true) - newSettings.serialize(buffer, true) - return (FunctionDescription(name: "account.updatePasswordSettings", parameters: [("password", String(describing: password)), ("newSettings", String(describing: newSettings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updatePersonalChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-649919008) - channel.serialize(buffer, true) - return (FunctionDescription(name: "account.updatePersonalChannel", parameters: [("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateProfile(flags: Int32, firstName: String?, lastName: String?, about: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2018596725) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(firstName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(lastName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(about!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "account.updateProfile", parameters: [("flags", String(describing: flags)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("about", String(describing: about))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User - } - return result - }) - } -} -public extension Api.functions.account { - static func updateStatus(offline: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1713919532) - offline.serialize(buffer, true) - return (FunctionDescription(name: "account.updateStatus", parameters: [("offline", String(describing: offline))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateTheme(flags: Int32, format: String, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(737414348) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(format, buffer: buffer, boxed: false) - theme.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeString(slug!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(settings!.count)) - for item in settings! { - item.serialize(buffer, true) - }} - return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", String(describing: flags)), ("format", String(describing: format)), ("theme", String(describing: theme)), ("slug", String(describing: slug)), ("title", String(describing: title)), ("document", String(describing: document)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in - let reader = BufferReader(buffer) - var result: Api.Theme? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Theme - } - return result - }) - } -} -public extension Api.functions.account { - static func updateUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1040964988) - serializeString(username, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.updateUsername", parameters: [("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User - } - return result - }) - } -} -public extension Api.functions.account { - static func uploadRingtone(file: Api.InputFile, fileName: String, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2095414366) - file.serialize(buffer, true) - serializeString(fileName, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.uploadRingtone", parameters: [("file", String(describing: file)), ("fileName", String(describing: fileName)), ("mimeType", String(describing: mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in - let reader = BufferReader(buffer) - var result: Api.Document? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Document - } - return result - }) - } -} -public extension Api.functions.account { - static func uploadTheme(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, fileName: String, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(473805619) - serializeInt32(flags, buffer: buffer, boxed: false) - file.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {thumb!.serialize(buffer, true)} - serializeString(fileName, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.uploadTheme", parameters: [("flags", String(describing: flags)), ("file", String(describing: file)), ("thumb", String(describing: thumb)), ("fileName", String(describing: fileName)), ("mimeType", String(describing: mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in - let reader = BufferReader(buffer) - var result: Api.Document? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Document - } - return result - }) - } -} -public extension Api.functions.account { - static func uploadWallPaper(flags: Int32, file: Api.InputFile, mimeType: String, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-476410109) - serializeInt32(flags, buffer: buffer, boxed: false) - file.serialize(buffer, true) - serializeString(mimeType, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.uploadWallPaper", parameters: [("flags", String(describing: flags)), ("file", String(describing: file)), ("mimeType", String(describing: mimeType)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WallPaper? in - let reader = BufferReader(buffer) - var result: Api.WallPaper? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WallPaper - } - return result - }) - } -} -public extension Api.functions.account { - static func verifyEmail(purpose: Api.EmailVerifyPurpose, verification: Api.EmailVerification) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(53322959) - purpose.serialize(buffer, true) - verification.serialize(buffer, true) - return (FunctionDescription(name: "account.verifyEmail", parameters: [("purpose", String(describing: purpose)), ("verification", String(describing: verification))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmailVerified? in - let reader = BufferReader(buffer) - var result: Api.account.EmailVerified? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.EmailVerified - } - return result - }) - } -} -public extension Api.functions.account { - static func verifyPhone(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1305716726) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeString(phoneCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.verifyPhone", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("phoneCode", String(describing: phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func acceptLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-392909491) - serializeBytes(token, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.acceptLoginToken", parameters: [("token", String(describing: token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Authorization? in - let reader = BufferReader(buffer) - var result: Api.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func bindTempAuthKey(permAuthKeyId: Int64, nonce: Int64, expiresAt: Int32, encryptedMessage: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-841733627) - serializeInt64(permAuthKeyId, buffer: buffer, boxed: false) - serializeInt64(nonce, buffer: buffer, boxed: false) - serializeInt32(expiresAt, buffer: buffer, boxed: false) - serializeBytes(encryptedMessage, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.bindTempAuthKey", parameters: [("permAuthKeyId", String(describing: permAuthKeyId)), ("nonce", String(describing: nonce)), ("expiresAt", String(describing: expiresAt)), ("encryptedMessage", String(describing: encryptedMessage))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func cancelCode(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(520357240) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.cancelCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func checkPassword(password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-779399914) - password.serialize(buffer, true) - return (FunctionDescription(name: "auth.checkPassword", parameters: [("password", String(describing: password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func checkRecoveryPassword(code: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(221691769) - serializeString(code, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.checkRecoveryPassword", parameters: [("code", String(describing: code))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func dropTempAuthKeys(exceptAuthKeys: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1907842680) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(exceptAuthKeys.count)) - for item in exceptAuthKeys { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "auth.dropTempAuthKeys", parameters: [("exceptAuthKeys", String(describing: exceptAuthKeys))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func exportAuthorization(dcId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-440401971) - serializeInt32(dcId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.exportAuthorization", parameters: [("dcId", String(describing: dcId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.ExportedAuthorization? in - let reader = BufferReader(buffer) - var result: Api.auth.ExportedAuthorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.ExportedAuthorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func exportLoginToken(apiId: Int32, apiHash: String, exceptIds: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1210022402) - serializeInt32(apiId, buffer: buffer, boxed: false) - serializeString(apiHash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(exceptIds.count)) - for item in exceptIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "auth.exportLoginToken", parameters: [("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("exceptIds", String(describing: exceptIds))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in - let reader = BufferReader(buffer) - var result: Api.auth.LoginToken? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken - } - return result - }) - } -} -public extension Api.functions.auth { - static func importAuthorization(id: Int64, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1518699091) - serializeInt64(id, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.importAuthorization", parameters: [("id", String(describing: id)), ("bytes", String(describing: bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func importBotAuthorization(flags: Int32, apiId: Int32, apiHash: String, botAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1738800940) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(apiId, buffer: buffer, boxed: false) - serializeString(apiHash, buffer: buffer, boxed: false) - serializeString(botAuthToken, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.importBotAuthorization", parameters: [("flags", String(describing: flags)), ("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("botAuthToken", String(describing: botAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func importLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1783866140) - serializeBytes(token, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.importLoginToken", parameters: [("token", String(describing: token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in - let reader = BufferReader(buffer) - var result: Api.auth.LoginToken? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken - } - return result - }) - } -} -public extension Api.functions.auth { - static func importWebTokenAuthorization(apiId: Int32, apiHash: String, webAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(767062953) - serializeInt32(apiId, buffer: buffer, boxed: false) - serializeString(apiHash, buffer: buffer, boxed: false) - serializeString(webAuthToken, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.importWebTokenAuthorization", parameters: [("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("webAuthToken", String(describing: webAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func logOut() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1047706137) - - return (FunctionDescription(name: "auth.logOut", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoggedOut? in - let reader = BufferReader(buffer) - var result: Api.auth.LoggedOut? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.LoggedOut - } - return result - }) - } -} -public extension Api.functions.auth { - static func recoverPassword(flags: Int32, code: String, newSettings: Api.account.PasswordInputSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(923364464) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(code, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {newSettings!.serialize(buffer, true)} - return (FunctionDescription(name: "auth.recoverPassword", parameters: [("flags", String(describing: flags)), ("code", String(describing: code)), ("newSettings", String(describing: newSettings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func reportMissingCode(phoneNumber: String, phoneCodeHash: String, mnc: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-878841866) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeString(mnc, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.reportMissingCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("mnc", String(describing: mnc))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func requestFirebaseSms(flags: Int32, phoneNumber: String, phoneCodeHash: String, safetyNetToken: String?, iosPushSecret: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1991881904) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(safetyNetToken!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(iosPushSecret!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "auth.requestFirebaseSms", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("safetyNetToken", String(describing: safetyNetToken)), ("iosPushSecret", String(describing: iosPushSecret))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func requestPasswordRecovery() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-661144474) - - return (FunctionDescription(name: "auth.requestPasswordRecovery", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.PasswordRecovery? in - let reader = BufferReader(buffer) - var result: Api.auth.PasswordRecovery? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.PasswordRecovery - } - return result - }) - } -} -public extension Api.functions.auth { - static func resendCode(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1056025023) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.resendCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.auth { - static func resetAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1616179942) - - return (FunctionDescription(name: "auth.resetAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func resetLoginEmail(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2123760019) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.resetLoginEmail", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.auth { - static func sendCode(phoneNumber: String, apiId: Int32, apiHash: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1502141361) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeInt32(apiId, buffer: buffer, boxed: false) - serializeString(apiHash, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "auth.sendCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.auth { - static func signIn(flags: Int32, phoneNumber: String, phoneCodeHash: String, phoneCode: String?, emailVerification: Api.EmailVerification?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1923962543) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(phoneCode!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {emailVerification!.serialize(buffer, true)} - return (FunctionDescription(name: "auth.signIn", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("phoneCode", String(describing: phoneCode)), ("emailVerification", String(describing: emailVerification))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func signUp(flags: Int32, phoneNumber: String, phoneCodeHash: String, firstName: String, lastName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1429752041) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.signUp", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.bots { - static func allowSendMessage(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-248323089) - bot.serialize(buffer, true) - return (FunctionDescription(name: "bots.allowSendMessage", parameters: [("bot", String(describing: bot))]), 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.bots { - static func answerWebhookJSONQuery(queryId: Int64, data: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-434028723) - serializeInt64(queryId, buffer: buffer, boxed: false) - data.serialize(buffer, true) - return (FunctionDescription(name: "bots.answerWebhookJSONQuery", parameters: [("queryId", String(describing: queryId)), ("data", String(describing: data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func canSendMessage(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(324662502) - bot.serialize(buffer, true) - return (FunctionDescription(name: "bots.canSendMessage", parameters: [("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func getBotCommands(scope: Api.BotCommandScope, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.BotCommand]>) { - let buffer = Buffer() - buffer.appendInt32(-481554986) - scope.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.getBotCommands", parameters: [("scope", String(describing: scope)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.BotCommand]? in - let reader = BufferReader(buffer) - var result: [Api.BotCommand]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) - } - return result - }) - } -} -public extension Api.functions.bots { - static func getBotInfo(flags: Int32, bot: Api.InputUser?, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-589753091) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {bot!.serialize(buffer, true)} - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.getBotInfo", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.BotInfo? in - let reader = BufferReader(buffer) - var result: Api.bots.BotInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.bots.BotInfo - } - return result - }) - } -} -public extension Api.functions.bots { - static func getBotMenuButton(userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1671369944) - userId.serialize(buffer, true) - return (FunctionDescription(name: "bots.getBotMenuButton", parameters: [("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BotMenuButton? in - let reader = BufferReader(buffer) - var result: Api.BotMenuButton? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.BotMenuButton - } - return result - }) - } -} -public extension Api.functions.bots { - static func invokeWebViewCustomMethod(bot: Api.InputUser, customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(142591463) - bot.serialize(buffer, true) - serializeString(customMethod, buffer: buffer, boxed: false) - params.serialize(buffer, true) - return (FunctionDescription(name: "bots.invokeWebViewCustomMethod", parameters: [("bot", String(describing: bot)), ("customMethod", String(describing: customMethod)), ("params", String(describing: params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in - let reader = BufferReader(buffer) - var result: Api.DataJSON? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.DataJSON - } - return result - }) - } -} -public extension Api.functions.bots { - static func reorderUsernames(bot: Api.InputUser, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1760972350) - bot.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "bots.reorderUsernames", parameters: [("bot", String(describing: bot)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func resetBotCommands(scope: Api.BotCommandScope, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1032708345) - scope.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.resetBotCommands", parameters: [("scope", String(describing: scope)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func sendCustomRequest(customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1440257555) - serializeString(customMethod, buffer: buffer, boxed: false) - params.serialize(buffer, true) - return (FunctionDescription(name: "bots.sendCustomRequest", parameters: [("customMethod", String(describing: customMethod)), ("params", String(describing: params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in - let reader = BufferReader(buffer) - var result: Api.DataJSON? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.DataJSON - } - return result - }) - } -} -public extension Api.functions.bots { - static func setBotBroadcastDefaultAdminRights(adminRights: Api.ChatAdminRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2021942497) - adminRights.serialize(buffer, true) - return (FunctionDescription(name: "bots.setBotBroadcastDefaultAdminRights", parameters: [("adminRights", String(describing: adminRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func setBotCommands(scope: Api.BotCommandScope, langCode: String, commands: [Api.BotCommand]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(85399130) - scope.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(commands.count)) - for item in commands { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "bots.setBotCommands", parameters: [("scope", String(describing: scope)), ("langCode", String(describing: langCode)), ("commands", String(describing: commands))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func setBotGroupDefaultAdminRights(adminRights: Api.ChatAdminRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1839281686) - adminRights.serialize(buffer, true) - return (FunctionDescription(name: "bots.setBotGroupDefaultAdminRights", parameters: [("adminRights", String(describing: adminRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func setBotInfo(flags: Int32, bot: Api.InputUser?, langCode: String, name: String?, about: String?, description: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(282013987) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {bot!.serialize(buffer, true)} - serializeString(langCode, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {serializeString(name!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeString(about!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(description!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "bots.setBotInfo", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("langCode", String(describing: langCode)), ("name", String(describing: name)), ("about", String(describing: about)), ("description", String(describing: description))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func setBotMenuButton(userId: Api.InputUser, button: Api.BotMenuButton) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1157944655) - userId.serialize(buffer, true) - button.serialize(buffer, true) - return (FunctionDescription(name: "bots.setBotMenuButton", parameters: [("userId", String(describing: userId)), ("button", String(describing: button))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func toggleUsername(bot: Api.InputUser, username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(87861619) - bot.serialize(buffer, true) - serializeString(username, buffer: buffer, boxed: false) - active.serialize(buffer, true) - return (FunctionDescription(name: "bots.toggleUsername", parameters: [("bot", String(describing: bot)), ("username", String(describing: username)), ("active", String(describing: active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func checkUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(283557164) - channel.serialize(buffer, true) - serializeString(username, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.checkUsername", parameters: [("channel", String(describing: channel)), ("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func clickSponsoredMessage(channel: Api.InputChannel, randomId: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(414170259) - channel.serialize(buffer, true) - serializeBytes(randomId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.clickSponsoredMessage", parameters: [("channel", String(describing: channel)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func convertToGigagroup(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(187239529) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.convertToGigagroup", parameters: [("channel", String(describing: channel))]), 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.channels { - static func createChannel(flags: Int32, title: String, about: String, geoPoint: Api.InputGeoPoint?, address: String?, ttlPeriod: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1862244601) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(about, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {geoPoint!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(address!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "channels.createChannel", parameters: [("flags", String(describing: flags)), ("title", String(describing: title)), ("about", String(describing: about)), ("geoPoint", String(describing: geoPoint)), ("address", String(describing: address)), ("ttlPeriod", String(describing: ttlPeriod))]), 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.channels { - static func createForumTopic(flags: Int32, channel: Api.InputChannel, title: String, iconColor: Int32?, iconEmojiId: Int64?, randomId: Int64, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-200539612) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(iconColor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {sendAs!.serialize(buffer, true)} - return (FunctionDescription(name: "channels.createForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId)), ("randomId", String(describing: randomId)), ("sendAs", String(describing: sendAs))]), 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.channels { - static func deactivateAllUsernames(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(170155475) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.deactivateAllUsernames", parameters: [("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func deleteChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1072619549) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.deleteChannel", parameters: [("channel", String(describing: channel))]), 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.channels { - static func deleteHistory(flags: Int32, channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1683319225) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.deleteHistory", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("maxId", String(describing: maxId))]), 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.channels { - static func deleteMessages(channel: Api.InputChannel, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2067661490) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.deleteMessages", parameters: [("channel", String(describing: channel)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages - } - return result - }) - } -} -public extension Api.functions.channels { - static func deleteParticipantHistory(channel: Api.InputChannel, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(913655003) - channel.serialize(buffer, true) - participant.serialize(buffer, true) - return (FunctionDescription(name: "channels.deleteParticipantHistory", parameters: [("channel", String(describing: channel)), ("participant", String(describing: participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.channels { - static func deleteTopicHistory(channel: Api.InputChannel, topMsgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(876830509) - channel.serialize(buffer, true) - serializeInt32(topMsgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.deleteTopicHistory", parameters: [("channel", String(describing: channel)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.channels { - static func editAdmin(channel: Api.InputChannel, userId: Api.InputUser, adminRights: Api.ChatAdminRights, rank: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-751007486) - channel.serialize(buffer, true) - userId.serialize(buffer, true) - adminRights.serialize(buffer, true) - serializeString(rank, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.editAdmin", parameters: [("channel", String(describing: channel)), ("userId", String(describing: userId)), ("adminRights", String(describing: adminRights)), ("rank", String(describing: rank))]), 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.channels { - static func editBanned(channel: Api.InputChannel, participant: Api.InputPeer, bannedRights: Api.ChatBannedRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1763259007) - channel.serialize(buffer, true) - participant.serialize(buffer, true) - bannedRights.serialize(buffer, true) - return (FunctionDescription(name: "channels.editBanned", parameters: [("channel", String(describing: channel)), ("participant", String(describing: participant)), ("bannedRights", String(describing: bannedRights))]), 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.channels { - static func editCreator(channel: Api.InputChannel, userId: Api.InputUser, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1892102881) - channel.serialize(buffer, true) - userId.serialize(buffer, true) - password.serialize(buffer, true) - return (FunctionDescription(name: "channels.editCreator", parameters: [("channel", String(describing: channel)), ("userId", String(describing: userId)), ("password", String(describing: password))]), 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.channels { - static func editForumTopic(flags: Int32, channel: Api.InputChannel, topicId: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-186670715) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeInt32(topicId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {hidden!.serialize(buffer, true)} - return (FunctionDescription(name: "channels.editForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed)), ("hidden", String(describing: hidden))]), 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.channels { - static func editLocation(channel: Api.InputChannel, geoPoint: Api.InputGeoPoint, address: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1491484525) - channel.serialize(buffer, true) - geoPoint.serialize(buffer, true) - serializeString(address, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.editLocation", parameters: [("channel", String(describing: channel)), ("geoPoint", String(describing: geoPoint)), ("address", String(describing: address))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func editPhoto(channel: Api.InputChannel, photo: Api.InputChatPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-248621111) - channel.serialize(buffer, true) - photo.serialize(buffer, true) - return (FunctionDescription(name: "channels.editPhoto", parameters: [("channel", String(describing: channel)), ("photo", String(describing: photo))]), 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.channels { - static func editTitle(channel: Api.InputChannel, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1450044624) - channel.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.editTitle", parameters: [("channel", String(describing: channel)), ("title", String(describing: title))]), 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.channels { - static func exportMessageLink(flags: Int32, channel: Api.InputChannel, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-432034325) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.exportMessageLink", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedMessageLink? in - let reader = BufferReader(buffer) - var result: Api.ExportedMessageLink? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ExportedMessageLink - } - return result - }) - } -} -public extension Api.functions.channels { - static func getAdminLog(flags: Int32, channel: Api.InputChannel, q: String, eventsFilter: Api.ChannelAdminLogEventsFilter?, admins: [Api.InputUser]?, maxId: Int64, minId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(870184064) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeString(q, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {eventsFilter!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(admins!.count)) - for item in admins! { - item.serialize(buffer, true) - }} - serializeInt64(maxId, buffer: buffer, boxed: false) - serializeInt64(minId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.getAdminLog", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("q", String(describing: q)), ("eventsFilter", String(describing: eventsFilter)), ("admins", String(describing: admins)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.AdminLogResults? in - let reader = BufferReader(buffer) - var result: Api.channels.AdminLogResults? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.channels.AdminLogResults - } - return result - }) - } -} -public extension Api.functions.channels { - static func getAdminedPublicChannels(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-122669393) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.getAdminedPublicChannels", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getChannelRecommendations(flags: Int32, channel: Api.InputChannel?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(631707458) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {channel!.serialize(buffer, true)} - return (FunctionDescription(name: "channels.getChannelRecommendations", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getChannels(id: [Api.InputChannel]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(176122811) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "channels.getChannels", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getForumTopics(flags: Int32, channel: Api.InputChannel, q: String?, offsetDate: Int32, offsetId: Int32, offsetTopic: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(233136337) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeString(q!, buffer: buffer, boxed: false)} - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(offsetTopic, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.getForumTopics", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("q", String(describing: q)), ("offsetDate", String(describing: offsetDate)), ("offsetId", String(describing: offsetId)), ("offsetTopic", String(describing: offsetTopic)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ForumTopics? in - let reader = BufferReader(buffer) - var result: Api.messages.ForumTopics? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ForumTopics - } - return result - }) - } -} -public extension Api.functions.channels { - static func getForumTopicsByID(channel: Api.InputChannel, topics: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1333584199) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(topics.count)) - for item in topics { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.getForumTopicsByID", parameters: [("channel", String(describing: channel)), ("topics", String(describing: topics))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ForumTopics? in - let reader = BufferReader(buffer) - var result: Api.messages.ForumTopics? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ForumTopics - } - return result - }) - } -} -public extension Api.functions.channels { - static func getFullChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(141781513) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.getFullChannel", parameters: [("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatFull? in - let reader = BufferReader(buffer) - var result: Api.messages.ChatFull? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ChatFull - } - return result - }) - } -} -public extension Api.functions.channels { - static func getGroupsForDiscussion() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-170208392) - - return (FunctionDescription(name: "channels.getGroupsForDiscussion", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getInactiveChannels() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(300429806) - - return (FunctionDescription(name: "channels.getInactiveChannels", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InactiveChats? in - let reader = BufferReader(buffer) - var result: Api.messages.InactiveChats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.InactiveChats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getLeftChannels(offset: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2092831552) - serializeInt32(offset, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.getLeftChannels", parameters: [("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getMessages(channel: Api.InputChannel, id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1383294429) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "channels.getMessages", parameters: [("channel", String(describing: channel)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.channels { - static func getParticipant(channel: Api.InputChannel, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1599378234) - channel.serialize(buffer, true) - participant.serialize(buffer, true) - return (FunctionDescription(name: "channels.getParticipant", parameters: [("channel", String(describing: channel)), ("participant", String(describing: participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.ChannelParticipant? in - let reader = BufferReader(buffer) - var result: Api.channels.ChannelParticipant? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.channels.ChannelParticipant - } - return result - }) - } -} -public extension Api.functions.channels { - static func getParticipants(channel: Api.InputChannel, filter: Api.ChannelParticipantsFilter, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2010044880) - channel.serialize(buffer, true) - filter.serialize(buffer, true) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.getParticipants", parameters: [("channel", String(describing: channel)), ("filter", String(describing: filter)), ("offset", String(describing: offset)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.ChannelParticipants? in - let reader = BufferReader(buffer) - var result: Api.channels.ChannelParticipants? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.channels.ChannelParticipants - } - return result - }) - } -} -public extension Api.functions.channels { - static func getSendAs(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(231174382) - peer.serialize(buffer, true) - return (FunctionDescription(name: "channels.getSendAs", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.SendAsPeers? in - let reader = BufferReader(buffer) - var result: Api.channels.SendAsPeers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.channels.SendAsPeers - } - return result - }) - } -} -public extension Api.functions.channels { - static func getSponsoredMessages(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-333377601) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.getSponsoredMessages", parameters: [("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SponsoredMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.SponsoredMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SponsoredMessages - } - return result - }) - } -} -public extension Api.functions.channels { - static func inviteToChannel(channel: Api.InputChannel, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-907854508) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "channels.inviteToChannel", parameters: [("channel", String(describing: channel)), ("users", String(describing: users))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in - let reader = BufferReader(buffer) - var result: Api.messages.InvitedUsers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers - } - return result - }) - } -} -public extension Api.functions.channels { - static func joinChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(615851205) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.joinChannel", parameters: [("channel", String(describing: channel))]), 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.channels { - static func leaveChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-130635115) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.leaveChannel", parameters: [("channel", String(describing: channel))]), 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.channels { - static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-871347913) - channel.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.readHistory", parameters: [("channel", String(describing: channel)), ("maxId", String(describing: maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func readMessageContents(channel: Api.InputChannel, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-357180360) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.readMessageContents", parameters: [("channel", String(describing: channel)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func reorderPinnedForumTopics(flags: Int32, channel: Api.InputChannel, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(693150095) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.reorderPinnedForumTopics", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("order", String(describing: order))]), 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.channels { - static func reorderUsernames(channel: Api.InputChannel, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1268978403) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.reorderUsernames", parameters: [("channel", String(describing: channel)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func reportAntiSpamFalsePositive(channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1471109485) - channel.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.reportAntiSpamFalsePositive", parameters: [("channel", String(describing: channel)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func reportSpam(channel: Api.InputChannel, participant: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-196443371) - channel.serialize(buffer, true) - participant.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.reportSpam", parameters: [("channel", String(describing: channel)), ("participant", String(describing: participant)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func reportSponsoredMessage(channel: Api.InputChannel, randomId: Buffer, option: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1349519687) - channel.serialize(buffer, true) - serializeBytes(randomId, buffer: buffer, boxed: false) - serializeBytes(option, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.reportSponsoredMessage", parameters: [("channel", String(describing: channel)), ("randomId", String(describing: randomId)), ("option", String(describing: option))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.SponsoredMessageReportResult? in - let reader = BufferReader(buffer) - var result: Api.channels.SponsoredMessageReportResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.channels.SponsoredMessageReportResult - } - return result - }) - } -} -public extension Api.functions.channels { - static func restrictSponsoredMessages(channel: Api.InputChannel, restricted: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1696000743) - channel.serialize(buffer, true) - restricted.serialize(buffer, true) - return (FunctionDescription(name: "channels.restrictSponsoredMessages", parameters: [("channel", String(describing: channel)), ("restricted", String(describing: restricted))]), 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.channels { - static func setBoostsToUnblockRestrictions(channel: Api.InputChannel, boosts: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1388733202) - channel.serialize(buffer, true) - serializeInt32(boosts, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.setBoostsToUnblockRestrictions", parameters: [("channel", String(describing: channel)), ("boosts", String(describing: boosts))]), 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.channels { - static func setDiscussionGroup(broadcast: Api.InputChannel, group: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1079520178) - broadcast.serialize(buffer, true) - group.serialize(buffer, true) - return (FunctionDescription(name: "channels.setDiscussionGroup", parameters: [("broadcast", String(describing: broadcast)), ("group", String(describing: group))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func setEmojiStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1020866743) - channel.serialize(buffer, true) - stickerset.serialize(buffer, true) - return (FunctionDescription(name: "channels.setEmojiStickers", parameters: [("channel", String(describing: channel)), ("stickerset", String(describing: stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func setStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-359881479) - channel.serialize(buffer, true) - stickerset.serialize(buffer, true) - return (FunctionDescription(name: "channels.setStickers", parameters: [("channel", String(describing: channel)), ("stickerset", String(describing: stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleAntiSpam(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1760814315) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleAntiSpam", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { - static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1540781271) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleForum", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { - static func toggleJoinRequest(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1277789622) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleJoinRequest", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { - static func toggleJoinToSend(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-456419968) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleJoinToSend", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { - static func toggleParticipantsHidden(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1785624660) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleParticipantsHidden", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { - static func togglePreHistoryHidden(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-356796084) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.togglePreHistoryHidden", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { - static func toggleSignatures(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(527021574) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleSignatures", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { - static func toggleSlowMode(channel: Api.InputChannel, seconds: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-304832784) - channel.serialize(buffer, true) - serializeInt32(seconds, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.toggleSlowMode", parameters: [("channel", String(describing: channel)), ("seconds", String(describing: seconds))]), 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.channels { - static func toggleUsername(channel: Api.InputChannel, username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1358053637) - channel.serialize(buffer, true) - serializeString(username, buffer: buffer, boxed: false) - active.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleUsername", parameters: [("channel", String(describing: channel)), ("username", String(describing: username)), ("active", String(describing: active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleViewForumAsMessages(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1757889771) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleViewForumAsMessages", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { - static func updateColor(flags: Int32, channel: Api.InputChannel, color: Int32?, backgroundEmojiId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-659933583) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "channels.updateColor", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("color", String(describing: color)), ("backgroundEmojiId", String(describing: backgroundEmojiId))]), 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.channels { - static func updateEmojiStatus(channel: Api.InputChannel, emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-254548312) - channel.serialize(buffer, true) - emojiStatus.serialize(buffer, true) - return (FunctionDescription(name: "channels.updateEmojiStatus", parameters: [("channel", String(describing: channel)), ("emojiStatus", String(describing: emojiStatus))]), 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.channels { - static func updatePinnedForumTopic(channel: Api.InputChannel, topicId: Int32, pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1814925350) - channel.serialize(buffer, true) - serializeInt32(topicId, buffer: buffer, boxed: false) - pinned.serialize(buffer, true) - return (FunctionDescription(name: "channels.updatePinnedForumTopic", parameters: [("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("pinned", String(describing: pinned))]), 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.channels { - static func updateUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(890549214) - channel.serialize(buffer, true) - serializeString(username, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.updateUsername", parameters: [("channel", String(describing: channel)), ("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func viewSponsoredMessage(channel: Api.InputChannel, randomId: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1095836780) - channel.serialize(buffer, true) - serializeBytes(randomId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.viewSponsoredMessage", parameters: [("channel", String(describing: channel)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func checkChatlistInvite(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1103171583) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "chatlists.checkChatlistInvite", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ChatlistInvite? in - let reader = BufferReader(buffer) - var result: Api.chatlists.ChatlistInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.chatlists.ChatlistInvite - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func deleteExportedInvite(chatlist: Api.InputChatlist, slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1906072670) - chatlist.serialize(buffer, true) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "chatlists.deleteExportedInvite", parameters: [("chatlist", String(describing: chatlist)), ("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func editExportedInvite(flags: Int32, chatlist: Api.InputChatlist, slug: String, title: String?, peers: [Api.InputPeer]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1698543165) - serializeInt32(flags, buffer: buffer, boxed: false) - chatlist.serialize(buffer, true) - serializeString(slug, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers!.count)) - for item in peers! { - item.serialize(buffer, true) - }} - return (FunctionDescription(name: "chatlists.editExportedInvite", parameters: [("flags", String(describing: flags)), ("chatlist", String(describing: chatlist)), ("slug", String(describing: slug)), ("title", String(describing: title)), ("peers", String(describing: peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedChatlistInvite? in - let reader = BufferReader(buffer) - var result: Api.ExportedChatlistInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ExportedChatlistInvite - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func exportChatlistInvite(chatlist: Api.InputChatlist, title: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2072885362) - chatlist.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "chatlists.exportChatlistInvite", parameters: [("chatlist", String(describing: chatlist)), ("title", String(describing: title)), ("peers", String(describing: peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ExportedChatlistInvite? in - let reader = BufferReader(buffer) - var result: Api.chatlists.ExportedChatlistInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.chatlists.ExportedChatlistInvite - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func getChatlistUpdates(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1992190687) - chatlist.serialize(buffer, true) - return (FunctionDescription(name: "chatlists.getChatlistUpdates", parameters: [("chatlist", String(describing: chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ChatlistUpdates? in - let reader = BufferReader(buffer) - var result: Api.chatlists.ChatlistUpdates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.chatlists.ChatlistUpdates - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func getExportedInvites(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-838608253) - chatlist.serialize(buffer, true) - return (FunctionDescription(name: "chatlists.getExportedInvites", parameters: [("chatlist", String(describing: chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ExportedInvites? in - let reader = BufferReader(buffer) - var result: Api.chatlists.ExportedInvites? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.chatlists.ExportedInvites - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func getLeaveChatlistSuggestions(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Peer]>) { - let buffer = Buffer() - buffer.appendInt32(-37955820) - chatlist.serialize(buffer, true) - return (FunctionDescription(name: "chatlists.getLeaveChatlistSuggestions", parameters: [("chatlist", String(describing: chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Peer]? in - let reader = BufferReader(buffer) - var result: [Api.Peer]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func hideChatlistUpdates(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1726252795) - chatlist.serialize(buffer, true) - return (FunctionDescription(name: "chatlists.hideChatlistUpdates", parameters: [("chatlist", String(describing: chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func joinChatlistInvite(slug: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1498291302) - serializeString(slug, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "chatlists.joinChatlistInvite", parameters: [("slug", String(describing: slug)), ("peers", String(describing: peers))]), 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.chatlists { - static func joinChatlistUpdates(chatlist: Api.InputChatlist, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-527828747) - chatlist.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "chatlists.joinChatlistUpdates", parameters: [("chatlist", String(describing: chatlist)), ("peers", String(describing: peers))]), 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.chatlists { - static func leaveChatlist(chatlist: Api.InputChatlist, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1962598714) - chatlist.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "chatlists.leaveChatlist", parameters: [("chatlist", String(describing: chatlist)), ("peers", String(describing: peers))]), 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.contacts { - static func acceptContact(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-130964977) - id.serialize(buffer, true) - return (FunctionDescription(name: "contacts.acceptContact", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.contacts { - static func addContact(flags: Int32, id: Api.InputUser, firstName: String, lastName: String, phone: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-386636848) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - serializeString(phone, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.addContact", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("phone", String(describing: phone))]), 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.contacts { - static func block(flags: Int32, id: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(774801204) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - return (FunctionDescription(name: "contacts.block", parameters: [("flags", String(describing: flags)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func blockFromReplies(flags: Int32, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(698914348) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.blockFromReplies", parameters: [("flags", String(describing: flags)), ("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.contacts { - static func deleteByPhones(phones: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(269745566) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(phones.count)) - for item in phones { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "contacts.deleteByPhones", parameters: [("phones", String(describing: phones))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func deleteContacts(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(157945344) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "contacts.deleteContacts", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.contacts { - static func editCloseFriends(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1167653392) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "contacts.editCloseFriends", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func exportContactToken() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-127582169) - - return (FunctionDescription(name: "contacts.exportContactToken", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedContactToken? in - let reader = BufferReader(buffer) - var result: Api.ExportedContactToken? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ExportedContactToken - } - return result - }) - } -} -public extension Api.functions.contacts { - static func getBirthdays() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-621959068) - - return (FunctionDescription(name: "contacts.getBirthdays", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ContactBirthdays? in - let reader = BufferReader(buffer) - var result: Api.contacts.ContactBirthdays? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.ContactBirthdays - } - return result - }) - } -} -public extension Api.functions.contacts { - static func getBlocked(flags: Int32, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1702457472) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.getBlocked", parameters: [("flags", String(describing: flags)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Blocked? in - let reader = BufferReader(buffer) - var result: Api.contacts.Blocked? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.Blocked - } - return result - }) - } -} -public extension Api.functions.contacts { - static func getContactIDs(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(2061264541) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.getContactIDs", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - return result - }) - } -} -public extension Api.functions.contacts { - static func getContacts(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1574346258) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.getContacts", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Contacts? in - let reader = BufferReader(buffer) - var result: Api.contacts.Contacts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.Contacts - } - return result - }) - } -} -public extension Api.functions.contacts { - static func getLocated(flags: Int32, geoPoint: Api.InputGeoPoint, selfExpires: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-750207932) - serializeInt32(flags, buffer: buffer, boxed: false) - geoPoint.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(selfExpires!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "contacts.getLocated", parameters: [("flags", String(describing: flags)), ("geoPoint", String(describing: geoPoint)), ("selfExpires", String(describing: selfExpires))]), 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.contacts { - static func getSaved() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SavedContact]>) { - let buffer = Buffer() - buffer.appendInt32(-2098076769) - - return (FunctionDescription(name: "contacts.getSaved", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SavedContact]? in - let reader = BufferReader(buffer) - var result: [Api.SavedContact]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedContact.self) - } - return result - }) - } -} -public extension Api.functions.contacts { - static func getStatuses() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ContactStatus]>) { - let buffer = Buffer() - buffer.appendInt32(-995929106) - - return (FunctionDescription(name: "contacts.getStatuses", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ContactStatus]? in - let reader = BufferReader(buffer) - var result: [Api.ContactStatus]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ContactStatus.self) - } - return result - }) - } -} -public extension Api.functions.contacts { - static func getTopPeers(flags: Int32, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1758168906) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.getTopPeers", parameters: [("flags", String(describing: flags)), ("offset", String(describing: offset)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.TopPeers? in - let reader = BufferReader(buffer) - var result: Api.contacts.TopPeers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.TopPeers - } - return result - }) - } -} -public extension Api.functions.contacts { - static func importContactToken(token: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(318789512) - serializeString(token, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.importContactToken", parameters: [("token", String(describing: token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User - } - return result - }) - } -} -public extension Api.functions.contacts { - static func importContacts(contacts: [Api.InputContact]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(746589157) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(contacts.count)) - for item in contacts { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "contacts.importContacts", parameters: [("contacts", String(describing: contacts))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ImportedContacts? in - let reader = BufferReader(buffer) - var result: Api.contacts.ImportedContacts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.ImportedContacts - } - return result - }) - } -} -public extension Api.functions.contacts { - static func resetSaved() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2020263951) - - return (FunctionDescription(name: "contacts.resetSaved", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func resetTopPeerRating(category: Api.TopPeerCategory, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(451113900) - category.serialize(buffer, true) - peer.serialize(buffer, true) - return (FunctionDescription(name: "contacts.resetTopPeerRating", parameters: [("category", String(describing: category)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func resolvePhone(phone: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1963375804) - serializeString(phone, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.resolvePhone", parameters: [("phone", String(describing: phone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ResolvedPeer? in - let reader = BufferReader(buffer) - var result: Api.contacts.ResolvedPeer? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.ResolvedPeer - } - return result - }) - } -} -public extension Api.functions.contacts { - static func resolveUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-113456221) - serializeString(username, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.resolveUsername", parameters: [("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ResolvedPeer? in - let reader = BufferReader(buffer) - var result: Api.contacts.ResolvedPeer? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.ResolvedPeer - } - return result - }) - } -} -public extension Api.functions.contacts { - static func search(q: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(301470424) - serializeString(q, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.search", parameters: [("q", String(describing: q)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Found? in - let reader = BufferReader(buffer) - var result: Api.contacts.Found? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.Found - } - return result - }) - } -} -public extension Api.functions.contacts { - static func setBlocked(flags: Int32, id: [Api.InputPeer], limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1798939530) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.setBlocked", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func toggleTopPeers(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2062238246) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "contacts.toggleTopPeers", parameters: [("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func unblock(flags: Int32, id: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1252994264) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - return (FunctionDescription(name: "contacts.unblock", parameters: [("flags", String(describing: flags)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.folders { - static func editPeerFolders(folderPeers: [Api.InputFolderPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1749536939) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(folderPeers.count)) - for item in folderPeers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "folders.editPeerFolders", parameters: [("folderPeers", String(describing: folderPeers))]), 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.fragment { - static func getCollectibleInfo(collectible: Api.InputCollectible) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1105295942) - collectible.serialize(buffer, true) - return (FunctionDescription(name: "fragment.getCollectibleInfo", parameters: [("collectible", String(describing: collectible))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.fragment.CollectibleInfo? in - let reader = BufferReader(buffer) - var result: Api.fragment.CollectibleInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.fragment.CollectibleInfo - } - return result - }) - } -} -public extension Api.functions.help { - static func acceptTermsOfService(id: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-294455398) - id.serialize(buffer, true) - return (FunctionDescription(name: "help.acceptTermsOfService", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.help { - static func dismissSuggestion(peer: Api.InputPeer, suggestion: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-183649631) - peer.serialize(buffer, true) - serializeString(suggestion, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.dismissSuggestion", parameters: [("peer", String(describing: peer)), ("suggestion", String(describing: suggestion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.help { - static func editUserInfo(userId: Api.InputUser, message: String, entities: [Api.MessageEntity]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1723407216) - userId.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities.count)) - for item in entities { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "help.editUserInfo", parameters: [("userId", String(describing: userId)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.UserInfo? in - let reader = BufferReader(buffer) - var result: Api.help.UserInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.UserInfo - } - return result - }) - } -} -public extension Api.functions.help { - static func getAppConfig(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1642330196) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getAppConfig", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.AppConfig? in - let reader = BufferReader(buffer) - var result: Api.help.AppConfig? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.AppConfig - } - return result - }) - } -} -public extension Api.functions.help { - static func getAppUpdate(source: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1378703997) - serializeString(source, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getAppUpdate", parameters: [("source", String(describing: source))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.AppUpdate? in - let reader = BufferReader(buffer) - var result: Api.help.AppUpdate? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.AppUpdate - } - return result - }) - } -} -public extension Api.functions.help { - static func getCdnConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1375900482) - - return (FunctionDescription(name: "help.getCdnConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.CdnConfig? in - let reader = BufferReader(buffer) - var result: Api.CdnConfig? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.CdnConfig - } - return result - }) - } -} -public extension Api.functions.help { - static func getConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-990308245) - - return (FunctionDescription(name: "help.getConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Config? in - let reader = BufferReader(buffer) - var result: Api.Config? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Config - } - return result - }) - } -} -public extension Api.functions.help { - static func getCountriesList(langCode: String, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1935116200) - serializeString(langCode, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getCountriesList", parameters: [("langCode", String(describing: langCode)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.CountriesList? in - let reader = BufferReader(buffer) - var result: Api.help.CountriesList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.CountriesList - } - return result - }) - } -} -public extension Api.functions.help { - static func getDeepLinkInfo(path: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1072547679) - serializeString(path, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getDeepLinkInfo", parameters: [("path", String(describing: path))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.DeepLinkInfo? in - let reader = BufferReader(buffer) - var result: Api.help.DeepLinkInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.DeepLinkInfo - } - return result - }) - } -} -public extension Api.functions.help { - static func getInviteText() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1295590211) - - return (FunctionDescription(name: "help.getInviteText", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.InviteText? in - let reader = BufferReader(buffer) - var result: Api.help.InviteText? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.InviteText - } - return result - }) - } -} -public extension Api.functions.help { - static func getNearestDc() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(531836966) - - return (FunctionDescription(name: "help.getNearestDc", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.NearestDc? in - let reader = BufferReader(buffer) - var result: Api.NearestDc? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.NearestDc - } - return result - }) - } -} -public extension Api.functions.help { - static func getPassportConfig(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-966677240) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getPassportConfig", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PassportConfig? in - let reader = BufferReader(buffer) - var result: Api.help.PassportConfig? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PassportConfig - } - return result - }) - } -} -public extension Api.functions.help { - static func getPeerColors(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-629083089) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getPeerColors", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PeerColors? in - let reader = BufferReader(buffer) - var result: Api.help.PeerColors? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PeerColors - } - return result - }) - } -} -public extension Api.functions.help { - static func getPeerProfileColors(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1412453891) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getPeerProfileColors", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PeerColors? in - let reader = BufferReader(buffer) - var result: Api.help.PeerColors? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PeerColors - } - return result - }) - } -} -public extension Api.functions.help { - static func getPremiumPromo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1206152236) - - return (FunctionDescription(name: "help.getPremiumPromo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PremiumPromo? in - let reader = BufferReader(buffer) - var result: Api.help.PremiumPromo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PremiumPromo - } - return result - }) - } -} -public extension Api.functions.help { - static func getPromoData() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1063816159) - - return (FunctionDescription(name: "help.getPromoData", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PromoData? in - let reader = BufferReader(buffer) - var result: Api.help.PromoData? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PromoData - } - return result - }) - } -} -public extension Api.functions.help { - static func getRecentMeUrls(referer: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1036054804) - serializeString(referer, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getRecentMeUrls", parameters: [("referer", String(describing: referer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.RecentMeUrls? in - let reader = BufferReader(buffer) - var result: Api.help.RecentMeUrls? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.RecentMeUrls - } - return result - }) - } -} -public extension Api.functions.help { - static func getSupport() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1663104819) - - return (FunctionDescription(name: "help.getSupport", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.Support? in - let reader = BufferReader(buffer) - var result: Api.help.Support? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.Support - } - return result - }) - } -} -public extension Api.functions.help { - static func getSupportName() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-748624084) - - return (FunctionDescription(name: "help.getSupportName", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.SupportName? in - let reader = BufferReader(buffer) - var result: Api.help.SupportName? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.SupportName - } - return result - }) - } -} -public extension Api.functions.help { - static func getTermsOfServiceUpdate() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(749019089) - - return (FunctionDescription(name: "help.getTermsOfServiceUpdate", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.TermsOfServiceUpdate? in - let reader = BufferReader(buffer) - var result: Api.help.TermsOfServiceUpdate? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.TermsOfServiceUpdate - } - return result - }) - } -} -public extension Api.functions.help { - static func getTimezonesList(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1236468288) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getTimezonesList", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.TimezonesList? in - let reader = BufferReader(buffer) - var result: Api.help.TimezonesList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.TimezonesList - } - return result - }) - } -} -public extension Api.functions.help { - static func getUserInfo(userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(59377875) - userId.serialize(buffer, true) - return (FunctionDescription(name: "help.getUserInfo", parameters: [("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.UserInfo? in - let reader = BufferReader(buffer) - var result: Api.help.UserInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.UserInfo - } - return result - }) - } -} -public extension Api.functions.help { - static func hidePromoData(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(505748629) - peer.serialize(buffer, true) - return (FunctionDescription(name: "help.hidePromoData", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.help { - static func saveAppLog(events: [Api.InputAppEvent]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1862465352) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(events.count)) - for item in events { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "help.saveAppLog", parameters: [("events", String(describing: events))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.help { - static func setBotUpdatesStatus(pendingUpdatesCount: Int32, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-333262899) - serializeInt32(pendingUpdatesCount, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.setBotUpdatesStatus", parameters: [("pendingUpdatesCount", String(describing: pendingUpdatesCount)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.help { - static func test() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1058929929) - - return (FunctionDescription(name: "help.test", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.langpack { - static func getDifference(langPack: String, langCode: String, fromVersion: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-845657435) - serializeString(langPack, buffer: buffer, boxed: false) - serializeString(langCode, buffer: buffer, boxed: false) - serializeInt32(fromVersion, buffer: buffer, boxed: false) - return (FunctionDescription(name: "langpack.getDifference", parameters: [("langPack", String(describing: langPack)), ("langCode", String(describing: langCode)), ("fromVersion", String(describing: fromVersion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackDifference? in - let reader = BufferReader(buffer) - var result: Api.LangPackDifference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.LangPackDifference - } - return result - }) - } -} -public extension Api.functions.langpack { - static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-219008246) - serializeString(langPack, buffer: buffer, boxed: false) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "langpack.getLangPack", parameters: [("langPack", String(describing: langPack)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackDifference? in - let reader = BufferReader(buffer) - var result: Api.LangPackDifference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.LangPackDifference - } - return result - }) - } -} -public extension Api.functions.langpack { - static func getLanguage(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1784243458) - serializeString(langPack, buffer: buffer, boxed: false) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "langpack.getLanguage", parameters: [("langPack", String(describing: langPack)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackLanguage? in - let reader = BufferReader(buffer) - var result: Api.LangPackLanguage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.LangPackLanguage - } - return result - }) - } -} -public extension Api.functions.langpack { - static func getLanguages(langPack: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.LangPackLanguage]>) { - let buffer = Buffer() - buffer.appendInt32(1120311183) - serializeString(langPack, buffer: buffer, boxed: false) - return (FunctionDescription(name: "langpack.getLanguages", parameters: [("langPack", String(describing: langPack))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.LangPackLanguage]? in - let reader = BufferReader(buffer) - var result: [Api.LangPackLanguage]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackLanguage.self) - } - return result - }) - } -} -public extension Api.functions.langpack { - static func getStrings(langPack: String, langCode: String, keys: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.LangPackString]>) { - let buffer = Buffer() - buffer.appendInt32(-269862909) - serializeString(langPack, buffer: buffer, boxed: false) - serializeString(langCode, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(keys.count)) - for item in keys { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "langpack.getStrings", parameters: [("langPack", String(describing: langPack)), ("langCode", String(describing: langCode)), ("keys", String(describing: keys))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.LangPackString]? in - let reader = BufferReader(buffer) - var result: [Api.LangPackString]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func acceptEncryption(peer: Api.InputEncryptedChat, gB: Buffer, keyFingerprint: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1035731989) - peer.serialize(buffer, true) - serializeBytes(gB, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.acceptEncryption", parameters: [("peer", String(describing: peer)), ("gB", String(describing: gB)), ("keyFingerprint", String(describing: keyFingerprint))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedChat? in - let reader = BufferReader(buffer) - var result: Api.EncryptedChat? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EncryptedChat - } - return result - }) - } -} -public extension Api.functions.messages { - static func acceptUrlAuth(flags: Int32, peer: Api.InputPeer?, msgId: Int32?, buttonId: Int32?, url: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1322487515) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {peer!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(msgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(buttonId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.acceptUrlAuth", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("buttonId", String(describing: buttonId)), ("url", String(describing: url))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.UrlAuthResult? in - let reader = BufferReader(buffer) - var result: Api.UrlAuthResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.UrlAuthResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func addChatUser(chatId: Int64, userId: Api.InputUser, fwdLimit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-876162809) - serializeInt64(chatId, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - serializeInt32(fwdLimit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.addChatUser", parameters: [("chatId", String(describing: chatId)), ("userId", String(describing: userId)), ("fwdLimit", String(describing: fwdLimit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in - let reader = BufferReader(buffer) - var result: Api.messages.InvitedUsers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers - } - return result - }) - } -} -public extension Api.functions.messages { - static func checkChatInvite(hash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1051570619) - serializeString(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.checkChatInvite", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ChatInvite? in - let reader = BufferReader(buffer) - var result: Api.ChatInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ChatInvite - } - return result - }) - } -} -public extension Api.functions.messages { - static func checkHistoryImport(importHead: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1140726259) - serializeString(importHead, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.checkHistoryImport", parameters: [("importHead", String(describing: importHead))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImportParsed? in - let reader = BufferReader(buffer) - var result: Api.messages.HistoryImportParsed? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImportParsed - } - return result - }) - } -} -public extension Api.functions.messages { - static func checkHistoryImportPeer(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1573261059) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.checkHistoryImportPeer", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.CheckedHistoryImportPeer? in - let reader = BufferReader(buffer) - var result: Api.messages.CheckedHistoryImportPeer? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.CheckedHistoryImportPeer - } - return result - }) - } -} -public extension Api.functions.messages { - static func checkQuickReplyShortcut(shortcut: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-237962285) - serializeString(shortcut, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.checkQuickReplyShortcut", parameters: [("shortcut", String(describing: shortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func clearAllDrafts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2119757468) - - return (FunctionDescription(name: "messages.clearAllDrafts", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func clearRecentReactions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1644236876) - - return (FunctionDescription(name: "messages.clearRecentReactions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func clearRecentStickers(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1986437075) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.clearRecentStickers", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func createChat(flags: Int32, users: [Api.InputUser], title: String, ttlPeriod: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1831936556) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - serializeString(title, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.createChat", parameters: [("flags", String(describing: flags)), ("users", String(describing: users)), ("title", String(describing: title)), ("ttlPeriod", String(describing: ttlPeriod))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in - let reader = BufferReader(buffer) - var result: Api.messages.InvitedUsers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1540419152) - serializeInt64(chatId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deleteChat", parameters: [("chatId", String(describing: chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteChatUser(flags: Int32, chatId: Int64, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1575461717) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - return (FunctionDescription(name: "messages.deleteChatUser", parameters: [("flags", String(describing: flags)), ("chatId", String(describing: chatId)), ("userId", String(describing: userId))]), 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 deleteExportedChatInvite(peer: Api.InputPeer, link: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-731601877) - peer.serialize(buffer, true) - serializeString(link, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deleteExportedChatInvite", parameters: [("peer", String(describing: peer)), ("link", String(describing: link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - 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() - buffer.appendInt32(-1332768214) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(minDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(maxDate!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.deleteHistory", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("maxId", String(describing: maxId)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteMessages(flags: Int32, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-443640366) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.deleteMessages", parameters: [("flags", String(describing: flags)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages - } - return result - }) - } -} -public extension Api.functions.messages { - static func deletePhoneCallHistory(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-104078327) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deletePhoneCallHistory", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedFoundMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedFoundMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedFoundMessages - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteQuickReplyMessages(shortcutId: Int32, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-519706352) - serializeInt32(shortcutId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.deleteQuickReplyMessages", parameters: [("shortcutId", String(describing: shortcutId)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteQuickReplyShortcut(shortcutId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1019234112) - serializeInt32(shortcutId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deleteQuickReplyShortcut", parameters: [("shortcutId", String(describing: shortcutId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteRevokedExportedChatInvites(peer: Api.InputPeer, adminId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1452833749) - peer.serialize(buffer, true) - adminId.serialize(buffer, true) - return (FunctionDescription(name: "messages.deleteRevokedExportedChatInvites", parameters: [("peer", String(describing: peer)), ("adminId", String(describing: adminId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteSavedHistory(flags: Int32, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1855459371) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(minDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(maxDate!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.deleteSavedHistory", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("maxId", String(describing: maxId)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1504586518) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.deleteScheduledMessages", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func discardEncryption(flags: Int32, chatId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-208425312) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(chatId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.discardEncryption", parameters: [("flags", String(describing: flags)), ("chatId", String(describing: chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func editChatAbout(peer: Api.InputPeer, about: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-554301545) - peer.serialize(buffer, true) - serializeString(about, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.editChatAbout", parameters: [("peer", String(describing: peer)), ("about", String(describing: about))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func editChatAdmin(chatId: Int64, userId: Api.InputUser, isAdmin: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1470377534) - serializeInt64(chatId, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - isAdmin.serialize(buffer, true) - return (FunctionDescription(name: "messages.editChatAdmin", parameters: [("chatId", String(describing: chatId)), ("userId", String(describing: userId)), ("isAdmin", String(describing: isAdmin))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func editChatDefaultBannedRights(peer: Api.InputPeer, bannedRights: Api.ChatBannedRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1517917375) - peer.serialize(buffer, true) - bannedRights.serialize(buffer, true) - return (FunctionDescription(name: "messages.editChatDefaultBannedRights", parameters: [("peer", String(describing: peer)), ("bannedRights", String(describing: bannedRights))]), 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 editChatPhoto(chatId: Int64, photo: Api.InputChatPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(903730804) - serializeInt64(chatId, buffer: buffer, boxed: false) - photo.serialize(buffer, true) - return (FunctionDescription(name: "messages.editChatPhoto", parameters: [("chatId", String(describing: chatId)), ("photo", String(describing: photo))]), 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 editChatTitle(chatId: Int64, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1937260541) - serializeInt64(chatId, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.editChatTitle", parameters: [("chatId", String(describing: chatId)), ("title", String(describing: title))]), 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 editExportedChatInvite(flags: Int32, peer: Api.InputPeer, link: String, expireDate: Int32?, usageLimit: Int32?, requestNeeded: Api.Bool?, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1110823051) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(link, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(expireDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(usageLimit!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {requestNeeded!.serialize(buffer, true)} - if Int(flags) & Int(1 << 4) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.editExportedChatInvite", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("link", String(describing: link)), ("expireDate", String(describing: expireDate)), ("usageLimit", String(describing: usageLimit)), ("requestNeeded", String(describing: requestNeeded)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvite? in - let reader = BufferReader(buffer) - var result: Api.messages.ExportedChatInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvite - } - 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() - buffer.appendInt32(-2091549254) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - return (FunctionDescription(name: "messages.editInlineBotMessage", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("message", String(describing: message)), ("media", String(describing: media)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, quickReplyShortcutId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-539934715) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 17) != 0 {serializeInt32(quickReplyShortcutId!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("message", String(describing: message)), ("media", String(describing: media)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("quickReplyShortcutId", String(describing: quickReplyShortcutId))]), 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 editQuickReplyShortcut(shortcutId: Int32, shortcut: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1543519471) - serializeInt32(shortcutId, buffer: buffer, boxed: false) - serializeString(shortcut, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.editQuickReplyShortcut", parameters: [("shortcutId", String(describing: shortcutId)), ("shortcut", String(describing: shortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func exportChatInvite(flags: Int32, peer: Api.InputPeer, expireDate: Int32?, usageLimit: Int32?, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1607670315) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(expireDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(usageLimit!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.exportChatInvite", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("expireDate", String(describing: expireDate)), ("usageLimit", String(describing: usageLimit)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedChatInvite? in - let reader = BufferReader(buffer) - var result: Api.ExportedChatInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } - return result - }) - } -} -public extension Api.functions.messages { - static func faveSticker(id: Api.InputDocument, unfave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1174420133) - id.serialize(buffer, true) - unfave.serialize(buffer, true) - return (FunctionDescription(name: "messages.faveSticker", parameters: [("id", String(describing: id)), ("unfave", String(describing: unfave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-721186296) - serializeInt32(flags, buffer: buffer, boxed: false) - fromPeer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomId.count)) - for item in randomId { - serializeInt64(item, buffer: buffer, boxed: false) - } - toPeer.serialize(buffer, true) - if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", String(describing: flags)), ("fromPeer", String(describing: fromPeer)), ("id", String(describing: id)), ("randomId", String(describing: randomId)), ("toPeer", String(describing: toPeer)), ("topMsgId", String(describing: topMsgId)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), 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 getAdminsWithInvites(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(958457583) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.getAdminsWithInvites", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatAdminsWithInvites? in - let reader = BufferReader(buffer) - var result: Api.messages.ChatAdminsWithInvites? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ChatAdminsWithInvites - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAllDrafts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1782549861) - - return (FunctionDescription(name: "messages.getAllDrafts", parameters: []), 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 getAllStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1197432408) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getAllStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.AllStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getArchivedStickers(flags: Int32, offsetId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1475442322) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getArchivedStickers", parameters: [("flags", String(describing: flags)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ArchivedStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.ArchivedStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ArchivedStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAttachMenuBot(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1998676370) - bot.serialize(buffer, true) - return (FunctionDescription(name: "messages.getAttachMenuBot", parameters: [("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AttachMenuBotsBot? in - let reader = BufferReader(buffer) - var result: Api.AttachMenuBotsBot? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.AttachMenuBotsBot - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAttachMenuBots(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(385663691) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getAttachMenuBots", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AttachMenuBots? in - let reader = BufferReader(buffer) - var result: Api.AttachMenuBots? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.AttachMenuBots - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAttachedStickers(media: Api.InputStickeredMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StickerSetCovered]>) { - let buffer = Buffer() - buffer.appendInt32(-866424884) - media.serialize(buffer, true) - return (FunctionDescription(name: "messages.getAttachedStickers", parameters: [("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StickerSetCovered]? in - let reader = BufferReader(buffer) - var result: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAvailableReactions(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(417243308) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getAvailableReactions", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AvailableReactions? in - let reader = BufferReader(buffer) - var result: Api.messages.AvailableReactions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AvailableReactions - } - return result - }) - } -} -public extension Api.functions.messages { - static func getBotApp(app: Api.InputBotApp, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(889046467) - app.serialize(buffer, true) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getBotApp", parameters: [("app", String(describing: app)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotApp? in - let reader = BufferReader(buffer) - var result: Api.messages.BotApp? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.BotApp - } - return result - }) - } -} -public extension Api.functions.messages { - static func getBotCallbackAnswer(flags: Int32, peer: Api.InputPeer, msgId: Int32, data: Buffer?, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1824339449) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {password!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.getBotCallbackAnswer", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("data", String(describing: data)), ("password", String(describing: password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotCallbackAnswer? in - let reader = BufferReader(buffer) - var result: Api.messages.BotCallbackAnswer? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.BotCallbackAnswer - } - return result - }) - } -} -public extension Api.functions.messages { - static func getChatInviteImporters(flags: Int32, peer: Api.InputPeer, link: String?, q: String?, offsetDate: Int32, offsetUser: Api.InputUser, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-553329330) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 {serializeString(link!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(q!, buffer: buffer, boxed: false)} - serializeInt32(offsetDate, buffer: buffer, boxed: false) - offsetUser.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getChatInviteImporters", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("link", String(describing: link)), ("q", String(describing: q)), ("offsetDate", String(describing: offsetDate)), ("offsetUser", String(describing: offsetUser)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteImporters? in - let reader = BufferReader(buffer) - var result: Api.messages.ChatInviteImporters? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ChatInviteImporters - } - return result - }) - } -} -public extension Api.functions.messages { - static func getChats(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1240027791) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getChats", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.messages { - static func getCommonChats(userId: Api.InputUser, maxId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-468934396) - userId.serialize(buffer, true) - serializeInt64(maxId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getCommonChats", parameters: [("userId", String(describing: userId)), ("maxId", String(describing: maxId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.messages { - static func getCustomEmojiDocuments(documentId: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Document]>) { - let buffer = Buffer() - buffer.appendInt32(-643100844) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documentId.count)) - for item in documentId { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getCustomEmojiDocuments", parameters: [("documentId", String(describing: documentId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Document]? in - let reader = BufferReader(buffer) - var result: [Api.Document]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDefaultHistoryTTL() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1703637384) - - return (FunctionDescription(name: "messages.getDefaultHistoryTTL", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DefaultHistoryTTL? in - let reader = BufferReader(buffer) - var result: Api.DefaultHistoryTTL? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.DefaultHistoryTTL - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDefaultTagReactions(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1107741656) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDefaultTagReactions", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in - let reader = BufferReader(buffer) - var result: Api.messages.Reactions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Reactions - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDhConfig(version: Int32, randomLength: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(651135312) - serializeInt32(version, buffer: buffer, boxed: false) - serializeInt32(randomLength, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDhConfig", parameters: [("version", String(describing: version)), ("randomLength", String(describing: randomLength))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DhConfig? in - let reader = BufferReader(buffer) - var result: Api.messages.DhConfig? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.DhConfig - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-271283063) - - return (FunctionDescription(name: "messages.getDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DialogFilters? in - let reader = BufferReader(buffer) - var result: Api.messages.DialogFilters? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.DialogFilters - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDialogUnreadMarks() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogPeer]>) { - let buffer = Buffer() - buffer.appendInt32(585256482) - - return (FunctionDescription(name: "messages.getDialogUnreadMarks", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogPeer]? in - let reader = BufferReader(buffer) - var result: [Api.DialogPeer]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDialogs(flags: Int32, folderId: Int32?, offsetDate: Int32, offsetId: Int32, offsetPeer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1594569905) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDialogs", parameters: [("flags", String(describing: flags)), ("folderId", String(describing: folderId)), ("offsetDate", String(describing: offsetDate)), ("offsetId", String(describing: offsetId)), ("offsetPeer", String(describing: offsetPeer)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Dialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.Dialogs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Dialogs - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1147761405) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDiscussionMessage", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DiscussionMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.DiscussionMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.DiscussionMessage - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDocumentByHash(sha256: Buffer, size: Int64, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1309538785) - serializeBytes(sha256, buffer: buffer, boxed: false) - serializeInt64(size, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDocumentByHash", parameters: [("sha256", String(describing: sha256)), ("size", String(describing: size)), ("mimeType", String(describing: mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in - let reader = BufferReader(buffer) - var result: Api.Document? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Document - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1955122779) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiGroups", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in - let reader = BufferReader(buffer) - var result: Api.messages.EmojiGroups? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiKeywords(langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(899735650) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiKeywords", parameters: [("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiKeywordsDifference? in - let reader = BufferReader(buffer) - var result: Api.EmojiKeywordsDifference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiKeywordsDifference - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiKeywordsDifference(langCode: String, fromVersion: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(352892591) - serializeString(langCode, buffer: buffer, boxed: false) - serializeInt32(fromVersion, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiKeywordsDifference", parameters: [("langCode", String(describing: langCode)), ("fromVersion", String(describing: fromVersion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiKeywordsDifference? in - let reader = BufferReader(buffer) - var result: Api.EmojiKeywordsDifference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiKeywordsDifference - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiKeywordsLanguages(langCodes: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.EmojiLanguage]>) { - let buffer = Buffer() - buffer.appendInt32(1318675378) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(langCodes.count)) - for item in langCodes { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getEmojiKeywordsLanguages", parameters: [("langCodes", String(describing: langCodes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.EmojiLanguage]? in - let reader = BufferReader(buffer) - var result: [Api.EmojiLanguage]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiLanguage.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiProfilePhotoGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(564480243) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiProfilePhotoGroups", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in - let reader = BufferReader(buffer) - var result: Api.messages.EmojiGroups? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiStatusGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(785209037) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiStatusGroups", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in - let reader = BufferReader(buffer) - var result: Api.messages.EmojiGroups? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiStickerGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(500711669) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiStickerGroups", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in - let reader = BufferReader(buffer) - var result: Api.messages.EmojiGroups? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-67329649) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.AllStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiURL(langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-709817306) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiURL", parameters: [("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiURL? in - let reader = BufferReader(buffer) - var result: Api.EmojiURL? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiURL - } - return result - }) - } -} -public extension Api.functions.messages { - static func getExportedChatInvite(peer: Api.InputPeer, link: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1937010524) - peer.serialize(buffer, true) - serializeString(link, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getExportedChatInvite", parameters: [("peer", String(describing: peer)), ("link", String(describing: link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvite? in - let reader = BufferReader(buffer) - var result: Api.messages.ExportedChatInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvite - } - return result - }) - } -} -public extension Api.functions.messages { - static func getExportedChatInvites(flags: Int32, peer: Api.InputPeer, adminId: Api.InputUser, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1565154314) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - adminId.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(offsetLink!, buffer: buffer, boxed: false)} - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getExportedChatInvites", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("adminId", String(describing: adminId)), ("offsetDate", String(describing: offsetDate)), ("offsetLink", String(describing: offsetLink)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvites? in - let reader = BufferReader(buffer) - var result: Api.messages.ExportedChatInvites? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvites - } - return result - }) - } -} -public extension Api.functions.messages { - static func getExtendedMedia(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2064119788) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getExtendedMedia", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func getFavedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(82946729) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getFavedStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FavedStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.FavedStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FavedStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getFeaturedEmojiStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(248473398) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getFeaturedEmojiStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.FeaturedStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getFeaturedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1685588756) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getFeaturedStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.FeaturedStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getFullChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1364194508) - serializeInt64(chatId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getFullChat", parameters: [("chatId", String(describing: chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatFull? in - let reader = BufferReader(buffer) - var result: Api.messages.ChatFull? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ChatFull - } - return result - }) - } -} -public extension Api.functions.messages { - static func getGameHighScores(peer: Api.InputPeer, id: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-400399203) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - return (FunctionDescription(name: "messages.getGameHighScores", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HighScores? in - let reader = BufferReader(buffer) - var result: Api.messages.HighScores? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.HighScores - } - return result - }) - } -} -public extension Api.functions.messages { - static func getHistory(peer: Api.InputPeer, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1143203525) - peer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getHistory", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("offsetDate", String(describing: offsetDate)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getInlineBotResults(flags: Int32, bot: Api.InputUser, peer: Api.InputPeer, geoPoint: Api.InputGeoPoint?, query: String, offset: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1364105629) - serializeInt32(flags, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {geoPoint!.serialize(buffer, true)} - serializeString(query, buffer: buffer, boxed: false) - serializeString(offset, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getInlineBotResults", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("peer", String(describing: peer)), ("geoPoint", String(describing: geoPoint)), ("query", String(describing: query)), ("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotResults? in - let reader = BufferReader(buffer) - var result: Api.messages.BotResults? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.BotResults - } - return result - }) - } -} -public extension Api.functions.messages { - static func getInlineGameHighScores(id: Api.InputBotInlineMessageID, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(258170395) - id.serialize(buffer, true) - userId.serialize(buffer, true) - return (FunctionDescription(name: "messages.getInlineGameHighScores", parameters: [("id", String(describing: id)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HighScores? in - let reader = BufferReader(buffer) - var result: Api.messages.HighScores? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.HighScores - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMaskStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1678738104) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getMaskStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.AllStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessageEditData(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-39416522) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getMessageEditData", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageEditData? in - let reader = BufferReader(buffer) - var result: Api.messages.MessageEditData? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.MessageEditData - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessageReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: Api.Reaction?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1176190792) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {reaction!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(offset!, buffer: buffer, boxed: false)} - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getMessageReactionsList", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("reaction", String(describing: reaction)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageReactionsList? in - let reader = BufferReader(buffer) - var result: Api.messages.MessageReactionsList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.MessageReactionsList - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessageReadParticipants(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ReadParticipantDate]>) { - let buffer = Buffer() - buffer.appendInt32(834782287) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getMessageReadParticipants", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ReadParticipantDate]? in - let reader = BufferReader(buffer) - var result: [Api.ReadParticipantDate]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReadParticipantDate.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessages(id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1673946374) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.getMessages", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessagesReactions(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1950707482) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getMessagesReactions", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1468322785) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - increment.serialize(buffer, true) - return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("increment", String(describing: increment))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageViews? in - let reader = BufferReader(buffer) - var result: Api.messages.MessageViews? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.MessageViews - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMyStickers(offsetId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-793386500) - serializeInt64(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getMyStickers", parameters: [("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MyStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.MyStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.MyStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getOldFeaturedStickers(offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2127598753) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getOldFeaturedStickers", parameters: [("offset", String(describing: offset)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.FeaturedStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getOnlines(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1848369232) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.getOnlines", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ChatOnlines? in - let reader = BufferReader(buffer) - var result: Api.ChatOnlines? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ChatOnlines - } - return result - }) - } -} -public extension Api.functions.messages { - static func getOutboxReadDate(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1941176739) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getOutboxReadDate", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.OutboxReadDate? in - let reader = BufferReader(buffer) - var result: Api.OutboxReadDate? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.OutboxReadDate - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPeerDialogs(peers: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-462373635) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.getPeerDialogs", parameters: [("peers", String(describing: peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerDialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.PeerDialogs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.PeerDialogs - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPeerSettings(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-270948702) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.getPeerSettings", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerSettings? in - let reader = BufferReader(buffer) - var result: Api.messages.PeerSettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.PeerSettings - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPinnedDialogs(folderId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-692498958) - serializeInt32(folderId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getPinnedDialogs", parameters: [("folderId", String(describing: folderId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerDialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.PeerDialogs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.PeerDialogs - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPinnedSavedDialogs() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-700607264) - - return (FunctionDescription(name: "messages.getPinnedSavedDialogs", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedDialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.SavedDialogs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SavedDialogs - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPollResults(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1941660731) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getPollResults", 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 getPollVotes(flags: Int32, peer: Api.InputPeer, id: Int32, option: Buffer?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1200736242) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeBytes(option!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(offset!, buffer: buffer, boxed: false)} - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getPollVotes", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("option", String(describing: option)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.VotesList? in - let reader = BufferReader(buffer) - var result: Api.messages.VotesList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.VotesList - } - return result - }) - } -} -public extension Api.functions.messages { - static func getQuickReplies(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-729550168) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getQuickReplies", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.QuickReplies? in - let reader = BufferReader(buffer) - var result: Api.messages.QuickReplies? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.QuickReplies - } - return result - }) - } -} -public extension Api.functions.messages { - static func getQuickReplyMessages(flags: Int32, shortcutId: Int32, id: [Int32]?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1801153085) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(shortcutId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id!.count)) - for item in id! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getQuickReplyMessages", parameters: [("flags", String(describing: flags)), ("shortcutId", String(describing: shortcutId)), ("id", String(describing: id)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getRecentLocations(peer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1881817312) - peer.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getRecentLocations", parameters: [("peer", String(describing: peer)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getRecentReactions(limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(960896434) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getRecentReactions", parameters: [("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in - let reader = BufferReader(buffer) - var result: Api.messages.Reactions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Reactions - } - return result - }) - } -} -public extension Api.functions.messages { - static func getRecentStickers(flags: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1649852357) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getRecentStickers", parameters: [("flags", String(describing: flags)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.RecentStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.RecentStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.RecentStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getReplies(peer: Api.InputPeer, msgId: Int32, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(584962828) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getReplies", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("offsetId", String(describing: offsetId)), ("offsetDate", String(describing: offsetDate)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSavedDialogs(flags: Int32, offsetDate: Int32, offsetId: Int32, offsetPeer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1401016858) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSavedDialogs", parameters: [("flags", String(describing: flags)), ("offsetDate", String(describing: offsetDate)), ("offsetId", String(describing: offsetId)), ("offsetPeer", String(describing: offsetPeer)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedDialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.SavedDialogs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SavedDialogs - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSavedGifs(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1559270965) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSavedGifs", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedGifs? in - let reader = BufferReader(buffer) - var result: Api.messages.SavedGifs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SavedGifs - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSavedHistory(peer: Api.InputPeer, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1033519437) - peer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSavedHistory", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("offsetDate", String(describing: offsetDate)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSavedReactionTags(flags: Int32, peer: Api.InputPeer?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(909631579) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSavedReactionTags", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedReactionTags? in - let reader = BufferReader(buffer) - var result: Api.messages.SavedReactionTags? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SavedReactionTags - } - return result - }) - } -} -public extension Api.functions.messages { - static func getScheduledHistory(peer: Api.InputPeer, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-183077365) - peer.serialize(buffer, true) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getScheduledHistory", parameters: [("peer", String(describing: peer)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1111817116) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getScheduledMessages", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSearchCounters(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, topMsgId: Int32?, filters: [Api.MessagesFilter]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.messages.SearchCounter]>) { - let buffer = Buffer() - buffer.appendInt32(465367808) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {savedPeerId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(filters.count)) - for item in filters { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.getSearchCounters", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("savedPeerId", String(describing: savedPeerId)), ("topMsgId", String(describing: topMsgId)), ("filters", String(describing: filters))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.messages.SearchCounter]? in - let reader = BufferReader(buffer) - var result: [Api.messages.SearchCounter]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.messages.SearchCounter.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSearchResultsCalendar(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, filter: Api.MessagesFilter, offsetId: Int32, offsetDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1789130429) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {savedPeerId!.serialize(buffer, true)} - filter.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(offsetDate, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSearchResultsCalendar", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("savedPeerId", String(describing: savedPeerId)), ("filter", String(describing: filter)), ("offsetId", String(describing: offsetId)), ("offsetDate", String(describing: offsetDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsCalendar? in - let reader = BufferReader(buffer) - var result: Api.messages.SearchResultsCalendar? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsCalendar - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSearchResultsPositions(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, filter: Api.MessagesFilter, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1669386480) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {savedPeerId!.serialize(buffer, true)} - filter.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSearchResultsPositions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("savedPeerId", String(describing: savedPeerId)), ("filter", String(describing: filter)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsPositions? in - let reader = BufferReader(buffer) - var result: Api.messages.SearchResultsPositions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsPositions - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSplitRanges() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.MessageRange]>) { - let buffer = Buffer() - buffer.appendInt32(486505992) - - return (FunctionDescription(name: "messages.getSplitRanges", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.MessageRange]? in - let reader = BufferReader(buffer) - var result: [Api.MessageRange]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageRange.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func getStickerSet(stickerset: Api.InputStickerSet, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-928977804) - stickerset.serialize(buffer, true) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getStickerSet", parameters: [("stickerset", String(describing: stickerset)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.messages { - static func getStickers(emoticon: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-710552671) - serializeString(emoticon, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getStickers", parameters: [("emoticon", String(describing: emoticon)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Stickers? in - let reader = BufferReader(buffer) - var result: Api.messages.Stickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Stickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSuggestedDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogFilterSuggested]>) { - let buffer = Buffer() - buffer.appendInt32(-1566780372) - - return (FunctionDescription(name: "messages.getSuggestedDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogFilterSuggested]? in - let reader = BufferReader(buffer) - var result: [Api.DialogFilterSuggested]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilterSuggested.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func getTopReactions(limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1149164102) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getTopReactions", parameters: [("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in - let reader = BufferReader(buffer) - var result: Api.messages.Reactions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Reactions - } - return result - }) - } -} -public extension Api.functions.messages { - static func getUnreadMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-251140208) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getUnreadMentions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getUnreadReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(841173339) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getUnreadReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getWebPage(url: String, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1919511901) - serializeString(url, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getWebPage", parameters: [("url", String(describing: url)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.WebPage? in - let reader = BufferReader(buffer) - var result: Api.messages.WebPage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.WebPage - } - return result - }) - } -} -public extension Api.functions.messages { - static func getWebPagePreview(flags: Int32, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1956073268) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - return (FunctionDescription(name: "messages.getWebPagePreview", parameters: [("flags", String(describing: flags)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in - let reader = BufferReader(buffer) - var result: Api.MessageMedia? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - return result - }) - } -} -public extension Api.functions.messages { - static func hideAllChatJoinRequests(flags: Int32, peer: Api.InputPeer, link: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-528091926) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 {serializeString(link!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.hideAllChatJoinRequests", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("link", String(describing: link))]), 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 hideChatJoinRequest(flags: Int32, peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2145904661) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - userId.serialize(buffer, true) - return (FunctionDescription(name: "messages.hideChatJoinRequest", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("userId", String(describing: userId))]), 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 hidePeerSettingsBar(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1336717624) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.hidePeerSettingsBar", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func importChatInvite(hash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1817183516) - serializeString(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.importChatInvite", parameters: [("hash", String(describing: hash))]), 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 initHistoryImport(peer: Api.InputPeer, file: Api.InputFile, mediaCount: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(873008187) - peer.serialize(buffer, true) - file.serialize(buffer, true) - serializeInt32(mediaCount, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.initHistoryImport", parameters: [("peer", String(describing: peer)), ("file", String(describing: file)), ("mediaCount", String(describing: mediaCount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImport? in - let reader = BufferReader(buffer) - var result: Api.messages.HistoryImport? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImport - } - return result - }) - } -} -public extension Api.functions.messages { - static func installStickerSet(stickerset: Api.InputStickerSet, archived: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-946871200) - stickerset.serialize(buffer, true) - archived.serialize(buffer, true) - return (FunctionDescription(name: "messages.installStickerSet", parameters: [("stickerset", String(describing: stickerset)), ("archived", String(describing: archived))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSetInstallResult? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSetInstallResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSetInstallResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func markDialogUnread(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1031349873) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.markDialogUnread", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func migrateChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1568189671) - serializeInt64(chatId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.migrateChat", parameters: [("chatId", String(describing: chatId))]), 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 prolongWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, queryId: Int64, replyTo: Api.InputReplyTo?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1328014717) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - bot.serialize(buffer, true) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.prolongWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("queryId", String(describing: queryId)), ("replyTo", String(describing: replyTo)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func rateTranscribedAudio(peer: Api.InputPeer, msgId: Int32, transcriptionId: Int64, good: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2132608815) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(transcriptionId, buffer: buffer, boxed: false) - good.serialize(buffer, true) - return (FunctionDescription(name: "messages.rateTranscribedAudio", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("transcriptionId", String(describing: transcriptionId)), ("good", String(describing: good))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func readDiscussion(peer: Api.InputPeer, msgId: Int32, readMaxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-147740172) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(readMaxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.readDiscussion", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("readMaxId", String(describing: readMaxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func readEncryptedHistory(peer: Api.InputEncryptedChat, maxDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2135648522) - peer.serialize(buffer, true) - serializeInt32(maxDate, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.readEncryptedHistory", parameters: [("peer", String(describing: peer)), ("maxDate", String(describing: maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func readFeaturedStickers(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1527873830) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.readFeaturedStickers", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func readHistory(peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(238054714) - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.readHistory", parameters: [("peer", String(describing: peer)), ("maxId", String(describing: maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages - } - return result - }) - } -} -public extension Api.functions.messages { - static func readMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(921026381) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.readMentions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func readMessageContents(id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(916930423) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.readMessageContents", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages - } - return result - }) - } -} -public extension Api.functions.messages { - static func readReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1420459918) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.readReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func receivedMessages(maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ReceivedNotifyMessage]>) { - let buffer = Buffer() - buffer.appendInt32(94983360) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.receivedMessages", parameters: [("maxId", String(describing: maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ReceivedNotifyMessage]? in - let reader = BufferReader(buffer) - var result: [Api.ReceivedNotifyMessage]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReceivedNotifyMessage.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func receivedQueue(maxQts: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int64]>) { - let buffer = Buffer() - buffer.appendInt32(1436924774) - serializeInt32(maxQts, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.receivedQueue", parameters: [("maxQts", String(describing: maxQts))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int64]? in - let reader = BufferReader(buffer) - var result: [Int64]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func reorderPinnedDialogs(flags: Int32, folderId: Int32, order: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(991616823) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(folderId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.reorderPinnedDialogs", parameters: [("flags", String(describing: flags)), ("folderId", String(describing: folderId)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reorderPinnedSavedDialogs(flags: Int32, order: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1955502713) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.reorderPinnedSavedDialogs", parameters: [("flags", String(describing: flags)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reorderQuickReplies(order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1613961479) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.reorderQuickReplies", parameters: [("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reorderStickerSets(flags: Int32, order: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2016638777) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.reorderStickerSets", parameters: [("flags", String(describing: flags)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func report(peer: Api.InputPeer, id: [Int32], reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1991005362) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) +public extension Api.updates { + enum Difference: TypeConstructorDescription { + case difference(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) + case differenceEmpty(date: Int32, seq: Int32) + case differenceSlice(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], intermediateState: Api.updates.State) + case differenceTooLong(pts: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state): + if boxed { + buffer.appendInt32(16030880) } - reason.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.report", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("reason", String(describing: reason)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reportEncryptedSpam(peer: Api.InputEncryptedChat) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1259113487) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.reportEncryptedSpam", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reportReaction(peer: Api.InputPeer, id: Int32, reactionPeer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1063567478) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - reactionPeer.serialize(buffer, true) - return (FunctionDescription(name: "messages.reportReaction", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("reactionPeer", String(describing: reactionPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reportSpam(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-820669733) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.reportSpam", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestAppWebView(flags: Int32, peer: Api.InputPeer, app: Api.InputBotApp, startParam: String?, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1940243652) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - app.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {themeParams!.serialize(buffer, true)} - serializeString(platform, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.requestAppWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("app", String(describing: app)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AppWebViewResult? in - let reader = BufferReader(buffer) - var result: Api.AppWebViewResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.AppWebViewResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestEncryption(userId: Api.InputUser, randomId: Int32, gA: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-162681021) - userId.serialize(buffer, true) - serializeInt32(randomId, buffer: buffer, boxed: false) - serializeBytes(gA, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.requestEncryption", parameters: [("userId", String(describing: userId)), ("randomId", String(describing: randomId)), ("gA", String(describing: gA))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedChat? in - let reader = BufferReader(buffer) - var result: Api.EncryptedChat? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EncryptedChat - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestSimpleWebView(flags: Int32, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(440815626) - serializeInt32(flags, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - if Int(flags) & Int(1 << 3) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {themeParams!.serialize(buffer, true)} - serializeString(platform, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.requestSimpleWebView", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SimpleWebViewResult? in - let reader = BufferReader(buffer) - var result: Api.SimpleWebViewResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.SimpleWebViewResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestUrlAuth(flags: Int32, peer: Api.InputPeer?, msgId: Int32?, buttonId: Int32?, url: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(428848198) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {peer!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(msgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(buttonId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.requestUrlAuth", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("buttonId", String(describing: buttonId)), ("url", String(describing: url))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.UrlAuthResult? in - let reader = BufferReader(buffer) - var result: Api.UrlAuthResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.UrlAuthResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String, replyTo: Api.InputReplyTo?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(647873217) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - bot.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {themeParams!.serialize(buffer, true)} - serializeString(platform, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform)), ("replyTo", String(describing: replyTo)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in - let reader = BufferReader(buffer) - var result: Api.WebViewResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WebViewResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func saveDefaultSendAs(peer: Api.InputPeer, sendAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-855777386) - peer.serialize(buffer, true) - sendAs.serialize(buffer, true) - return (FunctionDescription(name: "messages.saveDefaultSendAs", parameters: [("peer", String(describing: peer)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func saveDraft(flags: Int32, replyTo: Api.InputReplyTo?, peer: Api.InputPeer, message: String, entities: [Api.MessageEntity]?, media: Api.InputMedia?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2146678790) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 4) != 0 {replyTo!.serialize(buffer, true)} - peer.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 5) != 0 {media!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.saveDraft", parameters: [("flags", String(describing: flags)), ("replyTo", String(describing: replyTo)), ("peer", String(describing: peer)), ("message", String(describing: message)), ("entities", String(describing: entities)), ("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func saveGif(id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(846868683) - id.serialize(buffer, true) - unsave.serialize(buffer, true) - return (FunctionDescription(name: "messages.saveGif", parameters: [("id", String(describing: id)), ("unsave", String(describing: unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func saveRecentSticker(flags: Int32, id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(958863608) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - unsave.serialize(buffer, true) - return (FunctionDescription(name: "messages.saveRecentSticker", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("unsave", String(describing: unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputPeer?, savedPeerId: Api.InputPeer?, savedReaction: [Api.Reaction]?, topMsgId: Int32?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(703497338) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(q, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {savedPeerId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(savedReaction!.count)) - for item in savedReaction! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - filter.serialize(buffer, true) - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.search", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("q", String(describing: q)), ("fromId", String(describing: fromId)), ("savedPeerId", String(describing: savedPeerId)), ("savedReaction", String(describing: savedReaction)), ("topMsgId", String(describing: topMsgId)), ("filter", String(describing: filter)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchCustomEmoji(emoticon: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(739360983) - serializeString(emoticon, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchCustomEmoji", parameters: [("emoticon", String(describing: emoticon)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in - let reader = BufferReader(buffer) - var result: Api.EmojiList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiList - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchEmojiStickerSets(flags: Int32, q: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1833678516) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(q, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchEmojiStickerSets", parameters: [("flags", String(describing: flags)), ("q", String(describing: q)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickerSets? in - let reader = BufferReader(buffer) - var result: Api.messages.FoundStickerSets? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickerSets - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchGlobal(flags: Int32, folderId: Int32?, q: String, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1271290010) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - serializeString(q, buffer: buffer, boxed: false) - filter.serialize(buffer, true) - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) - serializeInt32(offsetRate, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", String(describing: flags)), ("folderId", String(describing: folderId)), ("q", String(describing: q)), ("filter", String(describing: filter)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate)), ("offsetRate", String(describing: offsetRate)), ("offsetPeer", String(describing: offsetPeer)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchSentMedia(q: String, filter: Api.MessagesFilter, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(276705696) - serializeString(q, buffer: buffer, boxed: false) - filter.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchSentMedia", parameters: [("q", String(describing: q)), ("filter", String(describing: filter)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchStickerSets(flags: Int32, q: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(896555914) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(q, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchStickerSets", parameters: [("flags", String(describing: flags)), ("q", String(describing: q)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickerSets? in - let reader = BufferReader(buffer) - var result: Api.messages.FoundStickerSets? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickerSets - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendBotRequestedPeer(peer: Api.InputPeer, msgId: Int32, buttonId: Int32, requestedPeers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1850552224) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(buttonId, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(requestedPeers.count)) - for item in requestedPeers { + buffer.appendInt32(Int32(newMessages.count)) + for item in newMessages { item.serialize(buffer, true) } - return (FunctionDescription(name: "messages.sendBotRequestedPeer", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("buttonId", String(describing: buttonId)), ("requestedPeers", String(describing: requestedPeers))]), 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 sendEncrypted(flags: Int32, peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1157265941) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeBytes(data, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendEncrypted", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("randomId", String(describing: randomId)), ("data", String(describing: data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.SentEncryptedMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendEncryptedFile(flags: Int32, peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1431914525) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeBytes(data, buffer: buffer, boxed: false) - file.serialize(buffer, true) - return (FunctionDescription(name: "messages.sendEncryptedFile", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("randomId", String(describing: randomId)), ("data", String(describing: data)), ("file", String(describing: file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.SentEncryptedMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendEncryptedService(peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(852769188) - peer.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeBytes(data, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendEncryptedService", parameters: [("peer", String(describing: peer)), ("randomId", String(describing: randomId)), ("data", String(describing: data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.SentEncryptedMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1052698730) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - serializeString(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("randomId", String(describing: randomId)), ("queryId", String(describing: queryId)), ("id", String(describing: id)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), 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 sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2077646913) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} - media.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("media", String(describing: media)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), 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 sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-537394132) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} - serializeString(message, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), 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 sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(211175177) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} buffer.appendInt32(481674261) - buffer.appendInt32(Int32(multiMedia.count)) - for item in multiMedia { + buffer.appendInt32(Int32(newEncryptedMessages.count)) + for item in newEncryptedMessages { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("multiMedia", String(describing: multiMedia)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), 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 sendQuickReplyMessages(peer: Api.InputPeer, shortcutId: Int32, id: [Int32], randomId: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1819610593) - peer.serialize(buffer, true) - serializeInt32(shortcutId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomId.count)) - for item in randomId { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.sendQuickReplyMessages", parameters: [("peer", String(describing: peer)), ("shortcutId", String(describing: shortcutId)), ("id", String(describing: id)), ("randomId", String(describing: randomId))]), 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 sendReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, reaction: [Api.Reaction]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-754091820) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reaction!.count)) - for item in reaction! { + buffer.appendInt32(Int32(otherUpdates.count)) + for item in otherUpdates { item.serialize(buffer, true) - }} - return (FunctionDescription(name: "messages.sendReaction", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("reaction", String(describing: reaction))]), 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 sendScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1120369398) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.sendScheduledMessages", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendScreenshotNotification(peer: Api.InputPeer, replyTo: Api.InputReplyTo, randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1589618665) - peer.serialize(buffer, true) - replyTo.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendScreenshotNotification", parameters: [("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("randomId", String(describing: randomId))]), 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 sendVote(peer: Api.InputPeer, msgId: Int32, options: [Buffer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(283795844) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(options.count)) - for item in options { - serializeBytes(item, buffer: buffer, boxed: false) } - return (FunctionDescription(name: "messages.sendVote", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("options", String(describing: options))]), 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 sendWebViewData(bot: Api.InputUser, randomId: Int64, buttonText: String, data: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-603831608) - bot.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeString(buttonText, buffer: buffer, boxed: false) - serializeString(data, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendWebViewData", parameters: [("bot", String(describing: bot)), ("randomId", String(describing: randomId)), ("buttonText", String(describing: buttonText)), ("data", String(describing: data))]), 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 sendWebViewResultMessage(botQueryId: String, result: Api.InputBotInlineResult) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(172168437) - serializeString(botQueryId, buffer: buffer, boxed: false) - result.serialize(buffer, true) - return (FunctionDescription(name: "messages.sendWebViewResultMessage", parameters: [("botQueryId", String(describing: botQueryId)), ("result", String(describing: result))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewMessageSent? in - let reader = BufferReader(buffer) - var result: Api.WebViewMessageSent? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WebViewMessageSent - } - return result - }) - } -} -public extension Api.functions.messages { - static func setBotCallbackAnswer(flags: Int32, queryId: Int64, message: String?, url: String?, cacheTime: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-712043766) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(message!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - serializeInt32(cacheTime, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setBotCallbackAnswer", parameters: [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("message", String(describing: message)), ("url", String(describing: url)), ("cacheTime", String(describing: cacheTime))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setBotPrecheckoutResults(flags: Int32, queryId: Int64, error: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(163765653) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(error!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.setBotPrecheckoutResults", parameters: [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("error", String(describing: error))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setBotShippingResults(flags: Int32, queryId: Int64, error: String?, shippingOptions: [Api.ShippingOption]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-436833542) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(error!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(shippingOptions!.count)) - for item in shippingOptions! { - item.serialize(buffer, true) - }} - return (FunctionDescription(name: "messages.setBotShippingResults", parameters: [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("error", String(describing: error)), ("shippingOptions", String(describing: shippingOptions))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setChatAvailableReactions(flags: Int32, peer: Api.InputPeer, availableReactions: Api.ChatReactions, reactionsLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1511328724) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - availableReactions.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(reactionsLimit!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.setChatAvailableReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("availableReactions", String(describing: availableReactions)), ("reactionsLimit", String(describing: reactionsLimit))]), 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 setChatTheme(peer: Api.InputPeer, emoticon: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-432283329) - peer.serialize(buffer, true) - serializeString(emoticon, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setChatTheme", parameters: [("peer", String(describing: peer)), ("emoticon", String(describing: emoticon))]), 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 setChatWallPaper(flags: Int32, peer: Api.InputPeer, wallpaper: Api.InputWallPaper?, settings: Api.WallPaperSettings?, id: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1879389471) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {wallpaper!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {settings!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(id!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.setChatWallPaper", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("wallpaper", String(describing: wallpaper)), ("settings", String(describing: settings)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func setDefaultHistoryTTL(period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1632299963) - serializeInt32(period, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setDefaultHistoryTTL", parameters: [("period", String(describing: period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setDefaultReaction(reaction: Api.Reaction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1330094102) - reaction.serialize(buffer, true) - return (FunctionDescription(name: "messages.setDefaultReaction", parameters: [("reaction", String(describing: reaction))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setEncryptedTyping(peer: Api.InputEncryptedChat, typing: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2031374829) - peer.serialize(buffer, true) - typing.serialize(buffer, true) - return (FunctionDescription(name: "messages.setEncryptedTyping", parameters: [("peer", String(describing: peer)), ("typing", String(describing: typing))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setGameScore(flags: Int32, peer: Api.InputPeer, id: Int32, userId: Api.InputUser, score: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1896289088) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - serializeInt32(score, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setGameScore", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("userId", String(describing: userId)), ("score", String(describing: score))]), 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 setHistoryTTL(peer: Api.InputPeer, period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1207017500) - peer.serialize(buffer, true) - serializeInt32(period, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setHistoryTTL", parameters: [("peer", String(describing: peer)), ("period", String(describing: period))]), 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 setInlineBotResults(flags: Int32, queryId: Int64, results: [Api.InputBotInlineResult], cacheTime: Int32, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, switchWebview: Api.InlineBotWebView?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1156406247) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(results.count)) - for item in results { + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } - serializeInt32(cacheTime, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {switchPm!.serialize(buffer, true)} - if Int(flags) & Int(1 << 4) != 0 {switchWebview!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.setInlineBotResults", parameters: [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("results", String(describing: results)), ("cacheTime", String(describing: cacheTime)), ("nextOffset", String(describing: nextOffset)), ("switchPm", String(describing: switchPm)), ("switchWebview", String(describing: switchWebview))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setInlineGameScore(flags: Int32, id: Api.InputBotInlineMessageID, userId: Api.InputUser, score: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(363700068) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - userId.serialize(buffer, true) - serializeInt32(score, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setInlineGameScore", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("userId", String(describing: userId)), ("score", String(describing: score))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setTyping(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, action: Api.SendMessageAction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1486110434) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - action.serialize(buffer, true) - return (FunctionDescription(name: "messages.setTyping", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("action", String(describing: action))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func startBot(bot: Api.InputUser, peer: Api.InputPeer, randomId: Int64, startParam: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-421563528) - bot.serialize(buffer, true) - peer.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeString(startParam, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.startBot", parameters: [("bot", String(describing: bot)), ("peer", String(describing: peer)), ("randomId", String(describing: randomId)), ("startParam", String(describing: startParam))]), 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 startHistoryImport(peer: Api.InputPeer, importId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1271008444) - peer.serialize(buffer, true) - serializeInt64(importId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.startHistoryImport", parameters: [("peer", String(describing: peer)), ("importId", String(describing: importId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleBotInAttachMenu(flags: Int32, bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1777704297) - serializeInt32(flags, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "messages.toggleBotInAttachMenu", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleDialogFilterTags(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-47326647) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "messages.toggleDialogFilterTags", parameters: [("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1489903017) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.toggleDialogPin", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleNoForwards(peer: Api.InputPeer, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1323389022) - peer.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "messages.toggleNoForwards", parameters: [("peer", String(describing: peer)), ("enabled", String(describing: enabled))]), 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 togglePeerTranslations(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-461589127) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.togglePeerTranslations", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleSavedDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1400783906) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.toggleSavedDialogPin", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleStickerSets(flags: Int32, stickersets: [Api.InputStickerSet]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1257951254) - serializeInt32(flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickersets.count)) - for item in stickersets { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.toggleStickerSets", parameters: [("flags", String(describing: flags)), ("stickersets", String(describing: stickersets))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func transcribeAudio(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(647928393) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.transcribeAudio", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.TranscribedAudio? in - let reader = BufferReader(buffer) - var result: Api.messages.TranscribedAudio? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.TranscribedAudio - } - return result - }) - } -} -public extension Api.functions.messages { - static func translateText(flags: Int32, peer: Api.InputPeer?, id: [Int32]?, text: [Api.TextWithEntities]?, toLang: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1662529584) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id!.count)) - for item in id! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(text!.count)) - for item in text! { + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) - }} - serializeString(toLang, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.translateText", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("text", String(describing: text)), ("toLang", String(describing: toLang))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.TranslatedText? in - let reader = BufferReader(buffer) - var result: Api.messages.TranslatedText? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.TranslatedText - } - return result - }) - } -} -public extension Api.functions.messages { - static func uninstallStickerSet(stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-110209570) - stickerset.serialize(buffer, true) - return (FunctionDescription(name: "messages.uninstallStickerSet", parameters: [("stickerset", String(describing: stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func unpinAllMessages(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-299714136) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.unpinAllMessages", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(450142282) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {filter!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.updateDialogFilter", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("filter", String(describing: filter))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func updateDialogFiltersOrder(order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-983318044) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.updateDialogFiltersOrder", parameters: [("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func updatePinnedMessage(flags: Int32, peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-760547348) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.updatePinnedMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func updateSavedReactionTag(flags: Int32, reaction: Api.Reaction, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1613331948) - serializeInt32(flags, buffer: buffer, boxed: false) - reaction.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.updateSavedReactionTag", parameters: [("flags", String(describing: flags)), ("reaction", String(describing: reaction)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func uploadEncryptedFile(peer: Api.InputEncryptedChat, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1347929239) - peer.serialize(buffer, true) - file.serialize(buffer, true) - return (FunctionDescription(name: "messages.uploadEncryptedFile", parameters: [("peer", String(describing: peer)), ("file", String(describing: file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedFile? in - let reader = BufferReader(buffer) - var result: Api.EncryptedFile? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EncryptedFile - } - return result - }) - } -} -public extension Api.functions.messages { - static func uploadImportedMedia(peer: Api.InputPeer, importId: Int64, fileName: String, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(713433234) - peer.serialize(buffer, true) - serializeInt64(importId, buffer: buffer, boxed: false) - serializeString(fileName, buffer: buffer, boxed: false) - media.serialize(buffer, true) - return (FunctionDescription(name: "messages.uploadImportedMedia", parameters: [("peer", String(describing: peer)), ("importId", String(describing: importId)), ("fileName", String(describing: fileName)), ("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in - let reader = BufferReader(buffer) - var result: Api.MessageMedia? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - return result - }) - } -} -public extension Api.functions.messages { - static func uploadMedia(flags: Int32, businessConnectionId: String?, peer: Api.InputPeer, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(345405816) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(businessConnectionId!, buffer: buffer, boxed: false)} - peer.serialize(buffer, true) - media.serialize(buffer, true) - return (FunctionDescription(name: "messages.uploadMedia", parameters: [("flags", String(describing: flags)), ("businessConnectionId", String(describing: businessConnectionId)), ("peer", String(describing: peer)), ("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in - let reader = BufferReader(buffer) - var result: Api.MessageMedia? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - return result - }) - } -} -public extension Api.functions.payments { - static func applyGiftCode(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-152934316) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.applyGiftCode", parameters: [("slug", String(describing: slug))]), 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.payments { - static func assignAppStoreTransaction(receipt: Buffer, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2131921795) - serializeBytes(receipt, buffer: buffer, boxed: false) - purpose.serialize(buffer, true) - return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("receipt", String(describing: receipt)), ("purpose", String(describing: purpose))]), 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.payments { - static func assignPlayMarketTransaction(receipt: Api.DataJSON, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-537046829) - receipt.serialize(buffer, true) - purpose.serialize(buffer, true) - return (FunctionDescription(name: "payments.assignPlayMarketTransaction", parameters: [("receipt", String(describing: receipt)), ("purpose", String(describing: purpose))]), 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.payments { - static func canPurchasePremium(purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1614700874) - purpose.serialize(buffer, true) - return (FunctionDescription(name: "payments.canPurchasePremium", parameters: [("purpose", String(describing: purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func checkGiftCode(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1907247935) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.checkGiftCode", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.CheckedGiftCode? in - let reader = BufferReader(buffer) - var result: Api.payments.CheckedGiftCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.CheckedGiftCode - } - return result - }) - } -} -public extension Api.functions.payments { - static func clearSavedInfo(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-667062079) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.clearSavedInfo", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func exportInvoice(invoiceMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(261206117) - invoiceMedia.serialize(buffer, true) - return (FunctionDescription(name: "payments.exportInvoice", parameters: [("invoiceMedia", String(describing: invoiceMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ExportedInvoice? in - let reader = BufferReader(buffer) - var result: Api.payments.ExportedInvoice? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.ExportedInvoice - } - return result - }) - } -} -public extension Api.functions.payments { - static func getBankCardData(number: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(779736953) - serializeString(number, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getBankCardData", parameters: [("number", String(describing: number))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.BankCardData? in - let reader = BufferReader(buffer) - var result: Api.payments.BankCardData? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.BankCardData - } - return result - }) - } -} -public extension Api.functions.payments { - static func getGiveawayInfo(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-198994907) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getGiveawayInfo", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.GiveawayInfo? in - let reader = BufferReader(buffer) - var result: Api.payments.GiveawayInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.GiveawayInfo - } - return result - }) - } -} -public extension Api.functions.payments { - static func getPaymentForm(flags: Int32, invoice: Api.InputInvoice, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(924093883) - serializeInt32(flags, buffer: buffer, boxed: false) - invoice.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {themeParams!.serialize(buffer, true)} - return (FunctionDescription(name: "payments.getPaymentForm", parameters: [("flags", String(describing: flags)), ("invoice", String(describing: invoice)), ("themeParams", String(describing: themeParams))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentForm? in - let reader = BufferReader(buffer) - var result: Api.payments.PaymentForm? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.PaymentForm - } - return result - }) - } -} -public extension Api.functions.payments { - static func getPaymentReceipt(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(611897804) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getPaymentReceipt", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentReceipt? in - let reader = BufferReader(buffer) - var result: Api.payments.PaymentReceipt? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.PaymentReceipt - } - return result - }) - } -} -public extension Api.functions.payments { - static func getPremiumGiftCodeOptions(flags: Int32, boostPeer: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.PremiumGiftCodeOption]>) { - let buffer = Buffer() - buffer.appendInt32(660060756) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {boostPeer!.serialize(buffer, true)} - return (FunctionDescription(name: "payments.getPremiumGiftCodeOptions", parameters: [("flags", String(describing: flags)), ("boostPeer", String(describing: boostPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.PremiumGiftCodeOption]? in - let reader = BufferReader(buffer) - var result: [Api.PremiumGiftCodeOption]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumGiftCodeOption.self) - } - return result - }) - } -} -public extension Api.functions.payments { - static func getSavedInfo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(578650699) - - return (FunctionDescription(name: "payments.getSavedInfo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SavedInfo? in - let reader = BufferReader(buffer) - var result: Api.payments.SavedInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.SavedInfo - } - return result - }) - } -} -public extension Api.functions.payments { - static func launchPrepaidGiveaway(peer: Api.InputPeer, giveawayId: Int64, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1609928480) - peer.serialize(buffer, true) - serializeInt64(giveawayId, buffer: buffer, boxed: false) - purpose.serialize(buffer, true) - return (FunctionDescription(name: "payments.launchPrepaidGiveaway", parameters: [("peer", String(describing: peer)), ("giveawayId", String(describing: giveawayId)), ("purpose", String(describing: purpose))]), 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.payments { - static func sendPaymentForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(755192367) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(formId, buffer: buffer, boxed: false) - invoice.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeString(requestedInfoId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(shippingOptionId!, buffer: buffer, boxed: false)} - credentials.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "payments.sendPaymentForm", parameters: [("flags", String(describing: flags)), ("formId", String(describing: formId)), ("invoice", String(describing: invoice)), ("requestedInfoId", String(describing: requestedInfoId)), ("shippingOptionId", String(describing: shippingOptionId)), ("credentials", String(describing: credentials)), ("tipAmount", String(describing: tipAmount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in - let reader = BufferReader(buffer) - var result: Api.payments.PaymentResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.PaymentResult - } - return result - }) - } -} -public extension Api.functions.payments { - static func validateRequestedInfo(flags: Int32, invoice: Api.InputInvoice, info: Api.PaymentRequestedInfo) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1228345045) - serializeInt32(flags, buffer: buffer, boxed: false) - invoice.serialize(buffer, true) - info.serialize(buffer, true) - return (FunctionDescription(name: "payments.validateRequestedInfo", parameters: [("flags", String(describing: flags)), ("invoice", String(describing: invoice)), ("info", String(describing: info))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ValidatedRequestedInfo? in - let reader = BufferReader(buffer) - var result: Api.payments.ValidatedRequestedInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.ValidatedRequestedInfo - } - return result - }) - } -} -public extension Api.functions.phone { - static func acceptCall(peer: Api.InputPhoneCall, gB: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1003664544) - peer.serialize(buffer, true) - serializeBytes(gB, buffer: buffer, boxed: false) - `protocol`.serialize(buffer, true) - return (FunctionDescription(name: "phone.acceptCall", parameters: [("peer", String(describing: peer)), ("gB", String(describing: gB)), ("`protocol`", String(describing: `protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in - let reader = BufferReader(buffer) - var result: Api.phone.PhoneCall? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall - } - return result - }) - } -} -public extension Api.functions.phone { - static func checkGroupCall(call: Api.InputGroupCall, sources: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(-1248003721) - call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sources.count)) - for item in sources { - serializeInt32(item, buffer: buffer, boxed: false) } - return (FunctionDescription(name: "phone.checkGroupCall", parameters: [("call", String(describing: call)), ("sources", String(describing: sources))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - return result - }) - } -} -public extension Api.functions.phone { - static func confirmCall(peer: Api.InputPhoneCall, gA: Buffer, keyFingerprint: Int64, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(788404002) - peer.serialize(buffer, true) - serializeBytes(gA, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - `protocol`.serialize(buffer, true) - return (FunctionDescription(name: "phone.confirmCall", parameters: [("peer", String(describing: peer)), ("gA", String(describing: gA)), ("keyFingerprint", String(describing: keyFingerprint)), ("`protocol`", String(describing: `protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in - let reader = BufferReader(buffer) - var result: Api.phone.PhoneCall? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall - } - return result - }) - } -} -public extension Api.functions.phone { - static func createGroupCall(flags: Int32, peer: Api.InputPeer, randomId: Int32, title: String?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1221445336) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "phone.createGroupCall", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("randomId", String(describing: randomId)), ("title", String(describing: title)), ("scheduleDate", String(describing: scheduleDate))]), 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.phone { - static func discardCall(flags: Int32, peer: Api.InputPhoneCall, duration: Int32, reason: Api.PhoneCallDiscardReason, connectionId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1295269440) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(duration, buffer: buffer, boxed: false) - reason.serialize(buffer, true) - serializeInt64(connectionId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.discardCall", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("duration", String(describing: duration)), ("reason", String(describing: reason)), ("connectionId", String(describing: connectionId))]), 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.phone { - static func discardGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2054648117) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.discardGroupCall", parameters: [("call", String(describing: call))]), 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.phone { - static func editGroupCallParticipant(flags: Int32, call: Api.InputGroupCall, participant: Api.InputPeer, muted: Api.Bool?, volume: Int32?, raiseHand: Api.Bool?, videoStopped: Api.Bool?, videoPaused: Api.Bool?, presentationPaused: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1524155713) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - participant.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {muted!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(volume!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {raiseHand!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {videoStopped!.serialize(buffer, true)} - if Int(flags) & Int(1 << 4) != 0 {videoPaused!.serialize(buffer, true)} - if Int(flags) & Int(1 << 5) != 0 {presentationPaused!.serialize(buffer, true)} - return (FunctionDescription(name: "phone.editGroupCallParticipant", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("participant", String(describing: participant)), ("muted", String(describing: muted)), ("volume", String(describing: volume)), ("raiseHand", String(describing: raiseHand)), ("videoStopped", String(describing: videoStopped)), ("videoPaused", String(describing: videoPaused)), ("presentationPaused", String(describing: presentationPaused))]), 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.phone { - static func editGroupCallTitle(call: Api.InputGroupCall, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(480685066) - call.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.editGroupCallTitle", parameters: [("call", String(describing: call)), ("title", String(describing: title))]), 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.phone { - static func exportGroupCallInvite(flags: Int32, call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-425040769) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.exportGroupCallInvite", parameters: [("flags", String(describing: flags)), ("call", String(describing: call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.ExportedGroupCallInvite? in - let reader = BufferReader(buffer) - var result: Api.phone.ExportedGroupCallInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.ExportedGroupCallInvite - } - return result - }) - } -} -public extension Api.functions.phone { - static func getCallConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1430593449) - - return (FunctionDescription(name: "phone.getCallConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in - let reader = BufferReader(buffer) - var result: Api.DataJSON? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.DataJSON - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupCall(call: Api.InputGroupCall, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(68699611) - call.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.getGroupCall", parameters: [("call", String(describing: call)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCall? in - let reader = BufferReader(buffer) - var result: Api.phone.GroupCall? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.GroupCall - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupCallJoinAs(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-277077702) - peer.serialize(buffer, true) - return (FunctionDescription(name: "phone.getGroupCallJoinAs", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.JoinAsPeers? in - let reader = BufferReader(buffer) - var result: Api.phone.JoinAsPeers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.JoinAsPeers - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupCallStreamChannels(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(447879488) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.getGroupCallStreamChannels", parameters: [("call", String(describing: call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamChannels? in - let reader = BufferReader(buffer) - var result: Api.phone.GroupCallStreamChannels? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStreamChannels - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupCallStreamRtmpUrl(peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-558650433) - peer.serialize(buffer, true) - revoke.serialize(buffer, true) - return (FunctionDescription(name: "phone.getGroupCallStreamRtmpUrl", parameters: [("peer", String(describing: peer)), ("revoke", String(describing: revoke))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamRtmpUrl? in - let reader = BufferReader(buffer) - var result: Api.phone.GroupCallStreamRtmpUrl? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStreamRtmpUrl - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupParticipants(call: Api.InputGroupCall, ids: [Api.InputPeer], sources: [Int32], offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-984033109) - call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(ids.count)) - for item in ids { - item.serialize(buffer, true) + state.serialize(buffer, true) + break + case .differenceEmpty(let date, let seq): + if boxed { + buffer.appendInt32(1567990072) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sources.count)) - for item in sources { - serializeInt32(item, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(seq, buffer: buffer, boxed: false) + break + case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState): + if boxed { + buffer.appendInt32(-1459938943) } - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.getGroupParticipants", parameters: [("call", String(describing: call)), ("ids", String(describing: ids)), ("sources", String(describing: sources)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupParticipants? in - let reader = BufferReader(buffer) - var result: Api.phone.GroupParticipants? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.GroupParticipants - } - return result - }) - } -} -public extension Api.functions.phone { - static func inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2067345760) - call.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { + buffer.appendInt32(Int32(newMessages.count)) + for item in newMessages { item.serialize(buffer, true) } - return (FunctionDescription(name: "phone.inviteToGroupCall", parameters: [("call", String(describing: call)), ("users", String(describing: users))]), 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.phone { - static func joinGroupCall(flags: Int32, call: Api.InputGroupCall, joinAs: Api.InputPeer, inviteHash: String?, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1322057861) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - joinAs.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 {serializeString(inviteHash!, buffer: buffer, boxed: false)} - params.serialize(buffer, true) - return (FunctionDescription(name: "phone.joinGroupCall", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("joinAs", String(describing: joinAs)), ("inviteHash", String(describing: inviteHash)), ("params", String(describing: params))]), 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.phone { - static func joinGroupCallPresentation(call: Api.InputGroupCall, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-873829436) - call.serialize(buffer, true) - params.serialize(buffer, true) - return (FunctionDescription(name: "phone.joinGroupCallPresentation", parameters: [("call", String(describing: call)), ("params", String(describing: params))]), 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.phone { - static func leaveGroupCall(call: Api.InputGroupCall, source: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1342404601) - call.serialize(buffer, true) - serializeInt32(source, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.leaveGroupCall", parameters: [("call", String(describing: call)), ("source", String(describing: source))]), 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.phone { - static func leaveGroupCallPresentation(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(475058500) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.leaveGroupCallPresentation", parameters: [("call", String(describing: call))]), 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.phone { - static func receivedCall(peer: Api.InputPhoneCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(399855457) - peer.serialize(buffer, true) - return (FunctionDescription(name: "phone.receivedCall", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func requestCall(flags: Int32, userId: Api.InputUser, randomId: Int32, gAHash: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1124046573) - serializeInt32(flags, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - serializeInt32(randomId, buffer: buffer, boxed: false) - serializeBytes(gAHash, buffer: buffer, boxed: false) - `protocol`.serialize(buffer, true) - return (FunctionDescription(name: "phone.requestCall", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("randomId", String(describing: randomId)), ("gAHash", String(describing: gAHash)), ("`protocol`", String(describing: `protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in - let reader = BufferReader(buffer) - var result: Api.phone.PhoneCall? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall - } - return result - }) - } -} -public extension Api.functions.phone { - static func saveCallDebug(peer: Api.InputPhoneCall, debug: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(662363518) - peer.serialize(buffer, true) - debug.serialize(buffer, true) - return (FunctionDescription(name: "phone.saveCallDebug", parameters: [("peer", String(describing: peer)), ("debug", String(describing: debug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func saveCallLog(peer: Api.InputPhoneCall, file: Api.InputFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1092913030) - peer.serialize(buffer, true) - file.serialize(buffer, true) - return (FunctionDescription(name: "phone.saveCallLog", parameters: [("peer", String(describing: peer)), ("file", String(describing: file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func saveDefaultGroupCallJoinAs(peer: Api.InputPeer, joinAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1465786252) - peer.serialize(buffer, true) - joinAs.serialize(buffer, true) - return (FunctionDescription(name: "phone.saveDefaultGroupCallJoinAs", parameters: [("peer", String(describing: peer)), ("joinAs", String(describing: joinAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func sendSignalingData(peer: Api.InputPhoneCall, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-8744061) - peer.serialize(buffer, true) - serializeBytes(data, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.sendSignalingData", parameters: [("peer", String(describing: peer)), ("data", String(describing: data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func setCallRating(flags: Int32, peer: Api.InputPhoneCall, rating: Int32, comment: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1508562471) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(rating, buffer: buffer, boxed: false) - serializeString(comment, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.setCallRating", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("rating", String(describing: rating)), ("comment", String(describing: comment))]), 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.phone { - static func startScheduledGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1451287362) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.startScheduledGroupCall", parameters: [("call", String(describing: call))]), 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.phone { - static func toggleGroupCallRecord(flags: Int32, call: Api.InputGroupCall, title: String?, videoPortrait: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-248985848) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {videoPortrait!.serialize(buffer, true)} - return (FunctionDescription(name: "phone.toggleGroupCallRecord", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("title", String(describing: title)), ("videoPortrait", String(describing: videoPortrait))]), 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.phone { - static func toggleGroupCallSettings(flags: Int32, call: Api.InputGroupCall, joinMuted: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1958458429) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {joinMuted!.serialize(buffer, true)} - return (FunctionDescription(name: "phone.toggleGroupCallSettings", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("joinMuted", String(describing: joinMuted))]), 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.phone { - static func toggleGroupCallStartSubscription(call: Api.InputGroupCall, subscribed: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(563885286) - call.serialize(buffer, true) - subscribed.serialize(buffer, true) - return (FunctionDescription(name: "phone.toggleGroupCallStartSubscription", parameters: [("call", String(describing: call)), ("subscribed", String(describing: subscribed))]), 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.photos { - static func deletePhotos(id: [Api.InputPhoto]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int64]>) { - let buffer = Buffer() - buffer.appendInt32(-2016444625) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { + buffer.appendInt32(Int32(newEncryptedMessages.count)) + for item in newEncryptedMessages { item.serialize(buffer, true) } - return (FunctionDescription(name: "photos.deletePhotos", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int64]? in - let reader = BufferReader(buffer) - var result: [Int64]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - return result - }) - } -} -public extension Api.functions.photos { - static func getUserPhotos(userId: Api.InputUser, offset: Int32, maxId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1848823128) - userId.serialize(buffer, true) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt64(maxId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "photos.getUserPhotos", parameters: [("userId", String(describing: userId)), ("offset", String(describing: offset)), ("maxId", String(describing: maxId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photos? in - let reader = BufferReader(buffer) - var result: Api.photos.Photos? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.photos.Photos - } - return result - }) - } -} -public extension Api.functions.photos { - static func updateProfilePhoto(flags: Int32, bot: Api.InputUser?, id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(166207545) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {bot!.serialize(buffer, true)} - id.serialize(buffer, true) - return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in - let reader = BufferReader(buffer) - var result: Api.photos.Photo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.photos.Photo - } - return result - }) - } -} -public extension Api.functions.photos { - static func uploadContactProfilePhoto(flags: Int32, userId: Api.InputUser, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-515093903) - serializeInt32(flags, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 5) != 0 {videoEmojiMarkup!.serialize(buffer, true)} - return (FunctionDescription(name: "photos.uploadContactProfilePhoto", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("file", String(describing: file)), ("video", String(describing: video)), ("videoStartTs", String(describing: videoStartTs)), ("videoEmojiMarkup", String(describing: videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in - let reader = BufferReader(buffer) - var result: Api.photos.Photo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.photos.Photo - } - return result - }) - } -} -public extension Api.functions.photos { - static func uploadProfilePhoto(flags: Int32, bot: Api.InputUser?, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(59286453) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 5) != 0 {bot!.serialize(buffer, true)} - if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {videoEmojiMarkup!.serialize(buffer, true)} - return (FunctionDescription(name: "photos.uploadProfilePhoto", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("file", String(describing: file)), ("video", String(describing: video)), ("videoStartTs", String(describing: videoStartTs)), ("videoEmojiMarkup", String(describing: videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in - let reader = BufferReader(buffer) - var result: Api.photos.Photo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.photos.Photo - } - return result - }) - } -} -public extension Api.functions.premium { - static func applyBoost(flags: Int32, slots: [Int32]?, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1803396934) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(slots!.count)) - for item in slots! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - peer.serialize(buffer, true) - return (FunctionDescription(name: "premium.applyBoost", parameters: [("flags", String(describing: flags)), ("slots", String(describing: slots)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.MyBoosts? in - let reader = BufferReader(buffer) - var result: Api.premium.MyBoosts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.premium.MyBoosts - } - return result - }) - } -} -public extension Api.functions.premium { - static func getBoostsList(flags: Int32, peer: Api.InputPeer, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1626764896) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "premium.getBoostsList", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsList? in - let reader = BufferReader(buffer) - var result: Api.premium.BoostsList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.premium.BoostsList - } - return result - }) - } -} -public extension Api.functions.premium { - static func getBoostsStatus(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(70197089) - peer.serialize(buffer, true) - return (FunctionDescription(name: "premium.getBoostsStatus", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsStatus? in - let reader = BufferReader(buffer) - var result: Api.premium.BoostsStatus? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.premium.BoostsStatus - } - return result - }) - } -} -public extension Api.functions.premium { - static func getMyBoosts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(199719754) - - return (FunctionDescription(name: "premium.getMyBoosts", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.MyBoosts? in - let reader = BufferReader(buffer) - var result: Api.premium.MyBoosts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.premium.MyBoosts - } - return result - }) - } -} -public extension Api.functions.premium { - static func getUserBoosts(peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(965037343) - peer.serialize(buffer, true) - userId.serialize(buffer, true) - return (FunctionDescription(name: "premium.getUserBoosts", parameters: [("peer", String(describing: peer)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsList? in - let reader = BufferReader(buffer) - var result: Api.premium.BoostsList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.premium.BoostsList - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func finishJob(flags: Int32, jobId: String, error: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1327415076) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(jobId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(error!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "smsjobs.finishJob", parameters: [("flags", String(describing: flags)), ("jobId", String(describing: jobId)), ("error", String(describing: error))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func getSmsJob(jobId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2005766191) - serializeString(jobId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "smsjobs.getSmsJob", parameters: [("jobId", String(describing: jobId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SmsJob? in - let reader = BufferReader(buffer) - var result: Api.SmsJob? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.SmsJob - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func getStatus() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(279353576) - - return (FunctionDescription(name: "smsjobs.getStatus", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.smsjobs.Status? in - let reader = BufferReader(buffer) - var result: Api.smsjobs.Status? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.smsjobs.Status - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func isEligibleToJoin() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(249313744) - - return (FunctionDescription(name: "smsjobs.isEligibleToJoin", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.smsjobs.EligibilityToJoin? in - let reader = BufferReader(buffer) - var result: Api.smsjobs.EligibilityToJoin? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.smsjobs.EligibilityToJoin - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func join() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1488007635) - - return (FunctionDescription(name: "smsjobs.join", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func leave() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1734824589) - - return (FunctionDescription(name: "smsjobs.leave", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func updateSettings(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(155164863) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "smsjobs.updateSettings", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stats { - static func getBroadcastRevenueStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1977595505) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - return (FunctionDescription(name: "stats.getBroadcastRevenueStats", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastRevenueStats? in - let reader = BufferReader(buffer) - var result: Api.stats.BroadcastRevenueStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastRevenueStats - } - return result - }) - } -} -public extension Api.functions.stats { - static func getBroadcastRevenueTransactions(channel: Api.InputChannel, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(6891535) - channel.serialize(buffer, true) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getBroadcastRevenueTransactions", parameters: [("channel", String(describing: channel)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastRevenueTransactions? in - let reader = BufferReader(buffer) - var result: Api.stats.BroadcastRevenueTransactions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastRevenueTransactions - } - return result - }) - } -} -public extension Api.functions.stats { - static func getBroadcastRevenueWithdrawalUrl(channel: Api.InputChannel, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(711323507) - channel.serialize(buffer, true) - password.serialize(buffer, true) - return (FunctionDescription(name: "stats.getBroadcastRevenueWithdrawalUrl", parameters: [("channel", String(describing: channel)), ("password", String(describing: password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastRevenueWithdrawalUrl? in - let reader = BufferReader(buffer) - var result: Api.stats.BroadcastRevenueWithdrawalUrl? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastRevenueWithdrawalUrl - } - return result - }) - } -} -public extension Api.functions.stats { - static func getBroadcastStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1421720550) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - return (FunctionDescription(name: "stats.getBroadcastStats", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastStats? in - let reader = BufferReader(buffer) - var result: Api.stats.BroadcastStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastStats - } - return result - }) - } -} -public extension Api.functions.stats { - static func getMegagroupStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-589330937) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - return (FunctionDescription(name: "stats.getMegagroupStats", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MegagroupStats? in - let reader = BufferReader(buffer) - var result: Api.stats.MegagroupStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.MegagroupStats - } - return result - }) - } -} -public extension Api.functions.stats { - static func getMessagePublicForwards(channel: Api.InputChannel, msgId: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1595212100) - channel.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getMessagePublicForwards", parameters: [("channel", String(describing: channel)), ("msgId", String(describing: msgId)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.PublicForwards? in - let reader = BufferReader(buffer) - var result: Api.stats.PublicForwards? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.PublicForwards - } - return result - }) - } -} -public extension Api.functions.stats { - static func getMessageStats(flags: Int32, channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1226791947) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getMessageStats", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MessageStats? in - let reader = BufferReader(buffer) - var result: Api.stats.MessageStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.MessageStats - } - return result - }) - } -} -public extension Api.functions.stats { - static func getStoryPublicForwards(peer: Api.InputPeer, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1505526026) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getStoryPublicForwards", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.PublicForwards? in - let reader = BufferReader(buffer) - var result: Api.stats.PublicForwards? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.PublicForwards - } - return result - }) - } -} -public extension Api.functions.stats { - static func getStoryStats(flags: Int32, peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(927985472) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getStoryStats", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.StoryStats? in - let reader = BufferReader(buffer) - var result: Api.stats.StoryStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.StoryStats - } - return result - }) - } -} -public extension Api.functions.stats { - static func loadAsyncGraph(flags: Int32, token: String, x: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1646092192) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(token, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(x!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "stats.loadAsyncGraph", parameters: [("flags", String(describing: flags)), ("token", String(describing: token)), ("x", String(describing: x))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StatsGraph? in - let reader = BufferReader(buffer) - var result: Api.StatsGraph? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - return result - }) - } -} -public extension Api.functions.stickers { - static func addStickerToSet(stickerset: Api.InputStickerSet, sticker: Api.InputStickerSetItem) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2041315650) - stickerset.serialize(buffer, true) - sticker.serialize(buffer, true) - return (FunctionDescription(name: "stickers.addStickerToSet", parameters: [("stickerset", String(describing: stickerset)), ("sticker", String(describing: sticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func changeSticker(flags: Int32, sticker: Api.InputDocument, emoji: String?, maskCoords: Api.MaskCoords?, keywords: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-179077444) - serializeInt32(flags, buffer: buffer, boxed: false) - sticker.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeString(emoji!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {maskCoords!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(keywords!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "stickers.changeSticker", parameters: [("flags", String(describing: flags)), ("sticker", String(describing: sticker)), ("emoji", String(describing: emoji)), ("maskCoords", String(describing: maskCoords)), ("keywords", String(describing: keywords))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func changeStickerPosition(sticker: Api.InputDocument, position: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-4795190) - sticker.serialize(buffer, true) - serializeInt32(position, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stickers.changeStickerPosition", parameters: [("sticker", String(describing: sticker)), ("position", String(describing: position))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func checkShortName(shortName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(676017721) - serializeString(shortName, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stickers.checkShortName", parameters: [("shortName", String(describing: shortName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stickers { - static func createStickerSet(flags: Int32, userId: Api.InputUser, title: String, shortName: String, thumb: Api.InputDocument?, stickers: [Api.InputStickerSetItem], software: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1876841625) - serializeInt32(flags, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - serializeString(shortName, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {thumb!.serialize(buffer, true)} buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers.count)) - for item in stickers { + buffer.appendInt32(Int32(otherUpdates.count)) + for item in otherUpdates { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 3) != 0 {serializeString(software!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "stickers.createStickerSet", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("title", String(describing: title)), ("shortName", String(describing: shortName)), ("thumb", String(describing: thumb)), ("stickers", String(describing: stickers)), ("software", String(describing: software))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func deleteStickerSet(stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2022685804) - stickerset.serialize(buffer, true) - return (FunctionDescription(name: "stickers.deleteStickerSet", parameters: [("stickerset", String(describing: stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stickers { - static func removeStickerFromSet(sticker: Api.InputDocument) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-143257775) - sticker.serialize(buffer, true) - return (FunctionDescription(name: "stickers.removeStickerFromSet", parameters: [("sticker", String(describing: sticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func renameStickerSet(stickerset: Api.InputStickerSet, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(306912256) - stickerset.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stickers.renameStickerSet", parameters: [("stickerset", String(describing: stickerset)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func replaceSticker(sticker: Api.InputDocument, newSticker: Api.InputStickerSetItem) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1184253338) - sticker.serialize(buffer, true) - newSticker.serialize(buffer, true) - return (FunctionDescription(name: "stickers.replaceSticker", parameters: [("sticker", String(describing: sticker)), ("newSticker", String(describing: newSticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func setStickerSetThumb(flags: Int32, stickerset: Api.InputStickerSet, thumb: Api.InputDocument?, thumbDocumentId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1486204014) - serializeInt32(flags, buffer: buffer, boxed: false) - stickerset.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {thumb!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt64(thumbDocumentId!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "stickers.setStickerSetThumb", parameters: [("flags", String(describing: flags)), ("stickerset", String(describing: stickerset)), ("thumb", String(describing: thumb)), ("thumbDocumentId", String(describing: thumbDocumentId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func suggestShortName(title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1303364867) - serializeString(title, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stickers.suggestShortName", parameters: [("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stickers.SuggestedShortName? in - let reader = BufferReader(buffer) - var result: Api.stickers.SuggestedShortName? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stickers.SuggestedShortName - } - return result - }) - } -} -public extension Api.functions.stories { - static func activateStealthMode(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1471926630) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.activateStealthMode", parameters: [("flags", String(describing: flags))]), 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.stories { - static func canSendStory(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-941629475) - peer.serialize(buffer, true) - return (FunctionDescription(name: "stories.canSendStory", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func deleteStories(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(-1369842849) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.deleteStories", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - return result - }) - } -} -public extension Api.functions.stories { - static func editStory(flags: Int32, peer: Api.InputPeer, id: Int32, media: Api.InputMedia?, mediaAreas: [Api.MediaArea]?, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1249658298) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(mediaAreas!.count)) - for item in mediaAreas! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 1) != 0 {serializeString(caption!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(privacyRules!.count)) - for item in privacyRules! { - item.serialize(buffer, true) - }} - return (FunctionDescription(name: "stories.editStory", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("media", String(describing: media)), ("mediaAreas", String(describing: mediaAreas)), ("caption", String(describing: caption)), ("entities", String(describing: entities)), ("privacyRules", String(describing: privacyRules))]), 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.stories { - static func exportStoryLink(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2072899360) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.exportStoryLink", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedStoryLink? in - let reader = BufferReader(buffer) - var result: Api.ExportedStoryLink? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ExportedStoryLink - } - return result - }) - } -} -public extension Api.functions.stories { - static func getAllReadPeerStories() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1688541191) - - return (FunctionDescription(name: "stories.getAllReadPeerStories", parameters: []), 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.stories { - static func getAllStories(flags: Int32, state: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-290400731) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(state!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "stories.getAllStories", parameters: [("flags", String(describing: flags)), ("state", String(describing: state))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.AllStories? in - let reader = BufferReader(buffer) - var result: Api.stories.AllStories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.AllStories - } - return result - }) - } -} -public extension Api.functions.stories { - static func getChatsToSend() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1519744160) - - return (FunctionDescription(name: "stories.getChatsToSend", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.stories { - static func getPeerMaxIDs(id: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(1398375363) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } - return (FunctionDescription(name: "stories.getPeerMaxIDs", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - return result - }) - } -} -public extension Api.functions.stories { - static func getPeerStories(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(743103056) - peer.serialize(buffer, true) - return (FunctionDescription(name: "stories.getPeerStories", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.PeerStories? in - let reader = BufferReader(buffer) - var result: Api.stories.PeerStories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.PeerStories - } - return result - }) - } -} -public extension Api.functions.stories { - static func getPinnedStories(peer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1478600156) - peer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getPinnedStories", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in - let reader = BufferReader(buffer) - var result: Api.stories.Stories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.Stories - } - return result - }) - } -} -public extension Api.functions.stories { - static func getStoriesArchive(peer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1271586794) - peer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getStoriesArchive", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in - let reader = BufferReader(buffer) - var result: Api.stories.Stories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.Stories - } - return result - }) - } -} -public extension Api.functions.stories { - static func getStoriesByID(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1467271796) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.getStoriesByID", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in - let reader = BufferReader(buffer) - var result: Api.stories.Stories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.Stories - } - return result - }) - } -} -public extension Api.functions.stories { - static func getStoriesViews(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(685862088) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.getStoriesViews", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViews? in - let reader = BufferReader(buffer) - var result: Api.stories.StoryViews? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.StoryViews - } - return result - }) - } -} -public extension Api.functions.stories { - static func getStoryReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: Api.Reaction?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1179482081) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {reaction!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(offset!, buffer: buffer, boxed: false)} - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getStoryReactionsList", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("reaction", String(describing: reaction)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryReactionsList? in - let reader = BufferReader(buffer) - var result: Api.stories.StoryReactionsList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.StoryReactionsList - } - return result - }) - } -} -public extension Api.functions.stories { - static func getStoryViewsList(flags: Int32, peer: Api.InputPeer, q: String?, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2127707223) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 {serializeString(q!, buffer: buffer, boxed: false)} - serializeInt32(id, buffer: buffer, boxed: false) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getStoryViewsList", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("q", String(describing: q)), ("id", String(describing: id)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViewsList? in - let reader = BufferReader(buffer) - var result: Api.stories.StoryViewsList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.StoryViewsList - } - return result - }) - } -} -public extension Api.functions.stories { - static func incrementStoryViews(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1308456197) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.incrementStoryViews", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func readStories(peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(-1521034552) - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.readStories", parameters: [("peer", String(describing: peer)), ("maxId", String(describing: maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - return result - }) - } -} -public extension Api.functions.stories { - static func report(peer: Api.InputPeer, id: [Int32], reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(421788300) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - reason.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.report", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("reason", String(describing: reason)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func sendReaction(flags: Int32, peer: Api.InputPeer, storyId: Int32, reaction: Api.Reaction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2144810674) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(storyId, buffer: buffer, boxed: false) - reaction.serialize(buffer, true) - return (FunctionDescription(name: "stories.sendReaction", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("storyId", String(describing: storyId)), ("reaction", String(describing: reaction))]), 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.stories { - static func sendStory(flags: Int32, peer: Api.InputPeer, media: Api.InputMedia, mediaAreas: [Api.MediaArea]?, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule], randomId: Int64, period: Int32?, fwdFromId: Api.InputPeer?, fwdFromStory: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-454661813) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - media.serialize(buffer, true) - if Int(flags) & Int(1 << 5) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(mediaAreas!.count)) - for item in mediaAreas! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 0) != 0 {serializeString(caption!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} buffer.appendInt32(481674261) - buffer.appendInt32(Int32(privacyRules.count)) - for item in privacyRules { + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) } - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(period!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 6) != 0 {fwdFromId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 6) != 0 {serializeInt32(fwdFromStory!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "stories.sendStory", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("media", String(describing: media)), ("mediaAreas", String(describing: mediaAreas)), ("caption", String(describing: caption)), ("entities", String(describing: entities)), ("privacyRules", String(describing: privacyRules)), ("randomId", String(describing: randomId)), ("period", String(describing: period)), ("fwdFromId", String(describing: fwdFromId)), ("fwdFromStory", String(describing: fwdFromStory))]), 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.stories { - static func toggleAllStoriesHidden(hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2082822084) - hidden.serialize(buffer, true) - return (FunctionDescription(name: "stories.toggleAllStoriesHidden", parameters: [("hidden", String(describing: hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func togglePeerStoriesHidden(peer: Api.InputPeer, hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1123805756) - peer.serialize(buffer, true) - hidden.serialize(buffer, true) - return (FunctionDescription(name: "stories.togglePeerStoriesHidden", parameters: [("peer", String(describing: peer)), ("hidden", String(describing: hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func togglePinned(peer: Api.InputPeer, id: [Int32], pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(-1703566865) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - pinned.serialize(buffer, true) - return (FunctionDescription(name: "stories.togglePinned", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("pinned", String(describing: pinned))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - return result - }) - } -} -public extension Api.functions.stories { - static func togglePinnedToTop(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(187268763) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) + intermediateState.serialize(buffer, true) + break + case .differenceTooLong(let pts): + if boxed { + buffer.appendInt32(1258196845) } - return (FunctionDescription(name: "stories.togglePinnedToTop", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.updates { - static func getChannelDifference(flags: Int32, channel: Api.InputChannel, filter: Api.ChannelMessagesFilter, pts: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(51854712) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - filter.serialize(buffer, true) serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "updates.getChannelDifference", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("filter", String(describing: filter)), ("pts", String(describing: pts)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.ChannelDifference? in - let reader = BufferReader(buffer) - var result: Api.updates.ChannelDifference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.updates.ChannelDifference - } - return result - }) - } -} -public extension Api.functions.updates { - static func getDifference(flags: Int32, pts: Int32, ptsLimit: Int32?, ptsTotalLimit: Int32?, date: Int32, qts: Int32, qtsLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(432207715) - serializeInt32(flags, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state): + return ("difference", [("newMessages", newMessages as Any), ("newEncryptedMessages", newEncryptedMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any), ("state", state as Any)]) + case .differenceEmpty(let date, let seq): + return ("differenceEmpty", [("date", date as Any), ("seq", seq as Any)]) + case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState): + return ("differenceSlice", [("newMessages", newMessages as Any), ("newEncryptedMessages", newEncryptedMessages as Any), ("otherUpdates", otherUpdates as Any), ("chats", chats as Any), ("users", users as Any), ("intermediateState", intermediateState as Any)]) + case .differenceTooLong(let pts): + return ("differenceTooLong", [("pts", pts as Any)]) + } + } + + public static func parse_difference(_ reader: BufferReader) -> Difference? { + var _1: [Api.Message]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _2: [Api.EncryptedMessage]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self) + } + var _3: [Api.Update]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: Api.updates.State? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.updates.State + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.updates.Difference.difference(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, state: _6!) + } + else { + return nil + } + } + public static func parse_differenceEmpty(_ reader: BufferReader) -> Difference? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.updates.Difference.differenceEmpty(date: _1!, seq: _2!) + } + else { + return nil + } + } + public static func parse_differenceSlice(_ reader: BufferReader) -> Difference? { + var _1: [Api.Message]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _2: [Api.EncryptedMessage]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self) + } + var _3: [Api.Update]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: Api.updates.State? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.updates.State + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.updates.Difference.differenceSlice(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, intermediateState: _6!) + } + else { + return nil + } + } + public static func parse_differenceTooLong(_ reader: BufferReader) -> Difference? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.updates.Difference.differenceTooLong(pts: _1!) + } + else { + return nil + } + } + + } +} +public extension Api.updates { + enum State: TypeConstructorDescription { + case state(pts: Int32, qts: Int32, date: Int32, seq: Int32, unreadCount: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .state(let pts, let qts, let date, let seq, let unreadCount): + if boxed { + buffer.appendInt32(-1519637954) + } serializeInt32(pts, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(ptsLimit!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ptsTotalLimit!, buffer: buffer, boxed: false)} - serializeInt32(date, buffer: buffer, boxed: false) serializeInt32(qts, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(qtsLimit!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "updates.getDifference", parameters: [("flags", String(describing: flags)), ("pts", String(describing: pts)), ("ptsLimit", String(describing: ptsLimit)), ("ptsTotalLimit", String(describing: ptsTotalLimit)), ("date", String(describing: date)), ("qts", String(describing: qts)), ("qtsLimit", String(describing: qtsLimit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.Difference? in - let reader = BufferReader(buffer) - var result: Api.updates.Difference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.updates.Difference - } - return result - }) - } -} -public extension Api.functions.updates { - static func getState() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-304838614) - - return (FunctionDescription(name: "updates.getState", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.State? in - let reader = BufferReader(buffer) - var result: Api.updates.State? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.updates.State - } - return result - }) - } -} -public extension Api.functions.upload { - static func getCdnFile(fileToken: Buffer, offset: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(962554330) - serializeBytes(fileToken, buffer: buffer, boxed: false) - serializeInt64(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getCdnFile", parameters: [("fileToken", String(describing: fileToken)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.CdnFile? in - let reader = BufferReader(buffer) - var result: Api.upload.CdnFile? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.upload.CdnFile - } - return result - }) - } -} -public extension Api.functions.upload { - static func getCdnFileHashes(fileToken: Buffer, offset: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { - let buffer = Buffer() - buffer.appendInt32(-1847836879) - serializeBytes(fileToken, buffer: buffer, boxed: false) - serializeInt64(offset, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getCdnFileHashes", parameters: [("fileToken", String(describing: fileToken)), ("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in - let reader = BufferReader(buffer) - var result: [Api.FileHash]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) - } - return result - }) - } -} -public extension Api.functions.upload { - static func getFile(flags: Int32, location: Api.InputFileLocation, offset: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1101843010) - serializeInt32(flags, buffer: buffer, boxed: false) - location.serialize(buffer, true) - serializeInt64(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getFile", parameters: [("flags", String(describing: flags)), ("location", String(describing: location)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.File? in - let reader = BufferReader(buffer) - var result: Api.upload.File? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.upload.File - } - return result - }) - } -} -public extension Api.functions.upload { - static func getFileHashes(location: Api.InputFileLocation, offset: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { - let buffer = Buffer() - buffer.appendInt32(-1856595926) - location.serialize(buffer, true) - serializeInt64(offset, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getFileHashes", parameters: [("location", String(describing: location)), ("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in - let reader = BufferReader(buffer) - var result: [Api.FileHash]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) - } - return result - }) - } -} -public extension Api.functions.upload { - static func getWebFile(location: Api.InputWebFileLocation, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(619086221) - location.serialize(buffer, true) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getWebFile", parameters: [("location", String(describing: location)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.WebFile? in - let reader = BufferReader(buffer) - var result: Api.upload.WebFile? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.upload.WebFile - } - return result - }) - } -} -public extension Api.functions.upload { - static func reuploadCdnFile(fileToken: Buffer, requestToken: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { - let buffer = Buffer() - buffer.appendInt32(-1691921240) - serializeBytes(fileToken, buffer: buffer, boxed: false) - serializeBytes(requestToken, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.reuploadCdnFile", parameters: [("fileToken", String(describing: fileToken)), ("requestToken", String(describing: requestToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in - let reader = BufferReader(buffer) - var result: [Api.FileHash]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) - } - return result - }) - } -} -public extension Api.functions.upload { - static func saveBigFilePart(fileId: Int64, filePart: Int32, fileTotalParts: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-562337987) - serializeInt64(fileId, buffer: buffer, boxed: false) - serializeInt32(filePart, buffer: buffer, boxed: false) - serializeInt32(fileTotalParts, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(seq, buffer: buffer, boxed: false) + serializeInt32(unreadCount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .state(let pts, let qts, let date, let seq, let unreadCount): + return ("state", [("pts", pts as Any), ("qts", qts as Any), ("date", date as Any), ("seq", seq as Any), ("unreadCount", unreadCount as Any)]) + } + } + + public static func parse_state(_ reader: BufferReader) -> State? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.updates.State.state(pts: _1!, qts: _2!, date: _3!, seq: _4!, unreadCount: _5!) + } + else { + return nil + } + } + + } +} +public extension Api.upload { + enum CdnFile: TypeConstructorDescription { + case cdnFile(bytes: Buffer) + case cdnFileReuploadNeeded(requestToken: Buffer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .cdnFile(let bytes): + if boxed { + buffer.appendInt32(-1449145777) + } serializeBytes(bytes, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.saveBigFilePart", parameters: [("fileId", String(describing: fileId)), ("filePart", String(describing: filePart)), ("fileTotalParts", String(describing: fileTotalParts)), ("bytes", String(describing: bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.upload { - static func saveFilePart(fileId: Int64, filePart: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1291540959) - serializeInt64(fileId, buffer: buffer, boxed: false) - serializeInt32(filePart, buffer: buffer, boxed: false) + break + case .cdnFileReuploadNeeded(let requestToken): + if boxed { + buffer.appendInt32(-290921362) + } + serializeBytes(requestToken, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .cdnFile(let bytes): + return ("cdnFile", [("bytes", bytes as Any)]) + case .cdnFileReuploadNeeded(let requestToken): + return ("cdnFileReuploadNeeded", [("requestToken", requestToken as Any)]) + } + } + + public static func parse_cdnFile(_ reader: BufferReader) -> CdnFile? { + var _1: Buffer? + _1 = parseBytes(reader) + let _c1 = _1 != nil + if _c1 { + return Api.upload.CdnFile.cdnFile(bytes: _1!) + } + else { + return nil + } + } + public static func parse_cdnFileReuploadNeeded(_ reader: BufferReader) -> CdnFile? { + var _1: Buffer? + _1 = parseBytes(reader) + let _c1 = _1 != nil + if _c1 { + return Api.upload.CdnFile.cdnFileReuploadNeeded(requestToken: _1!) + } + else { + return nil + } + } + + } +} +public extension Api.upload { + enum File: TypeConstructorDescription { + case file(type: Api.storage.FileType, mtime: Int32, bytes: Buffer) + case fileCdnRedirect(dcId: Int32, fileToken: Buffer, encryptionKey: Buffer, encryptionIv: Buffer, fileHashes: [Api.FileHash]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .file(let type, let mtime, let bytes): + if boxed { + buffer.appendInt32(157948117) + } + type.serialize(buffer, true) + serializeInt32(mtime, buffer: buffer, boxed: false) serializeBytes(bytes, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.saveFilePart", parameters: [("fileId", String(describing: fileId)), ("filePart", String(describing: filePart)), ("bytes", String(describing: bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.users { - static func getFullUser(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1240508136) - id.serialize(buffer, true) - return (FunctionDescription(name: "users.getFullUser", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.UserFull? in - let reader = BufferReader(buffer) - var result: Api.users.UserFull? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.users.UserFull - } - return result - }) - } -} -public extension Api.functions.users { - static func getIsPremiumRequiredToContact(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Bool]>) { - let buffer = Buffer() - buffer.appendInt32(-1507677680) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) + break + case .fileCdnRedirect(let dcId, let fileToken, let encryptionKey, let encryptionIv, let fileHashes): + if boxed { + buffer.appendInt32(-242427324) } - return (FunctionDescription(name: "users.getIsPremiumRequiredToContact", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Bool]? in - let reader = BufferReader(buffer) - var result: [Api.Bool]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Bool.self) - } - return result - }) - } -} -public extension Api.functions.users { - static func getUsers(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.User]>) { - let buffer = Buffer() - buffer.appendInt32(227648840) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeBytes(fileToken, buffer: buffer, boxed: false) + serializeBytes(encryptionKey, buffer: buffer, boxed: false) + serializeBytes(encryptionIv, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(fileHashes.count)) + for item in fileHashes { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .file(let type, let mtime, let bytes): + return ("file", [("type", type as Any), ("mtime", mtime as Any), ("bytes", bytes as Any)]) + case .fileCdnRedirect(let dcId, let fileToken, let encryptionKey, let encryptionIv, let fileHashes): + return ("fileCdnRedirect", [("dcId", dcId as Any), ("fileToken", fileToken as Any), ("encryptionKey", encryptionKey as Any), ("encryptionIv", encryptionIv as Any), ("fileHashes", fileHashes as Any)]) + } + } + + public static func parse_file(_ reader: BufferReader) -> File? { + var _1: Api.storage.FileType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.storage.FileType + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.upload.File.file(type: _1!, mtime: _2!, bytes: _3!) + } + else { + return nil + } + } + public static func parse_fileCdnRedirect(_ reader: BufferReader) -> File? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Buffer? + _3 = parseBytes(reader) + var _4: Buffer? + _4 = parseBytes(reader) + var _5: [Api.FileHash]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.upload.File.fileCdnRedirect(dcId: _1!, fileToken: _2!, encryptionKey: _3!, encryptionIv: _4!, fileHashes: _5!) + } + else { + return nil + } + } + + } +} +public extension Api.upload { + enum WebFile: TypeConstructorDescription { + case webFile(size: Int32, mimeType: String, fileType: Api.storage.FileType, mtime: Int32, bytes: Buffer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webFile(let size, let mimeType, let fileType, let mtime, let bytes): + if boxed { + buffer.appendInt32(568808380) + } + serializeInt32(size, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + fileType.serialize(buffer, true) + serializeInt32(mtime, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .webFile(let size, let mimeType, let fileType, let mtime, let bytes): + return ("webFile", [("size", size as Any), ("mimeType", mimeType as Any), ("fileType", fileType as Any), ("mtime", mtime as Any), ("bytes", bytes as Any)]) + } + } + + public static func parse_webFile(_ reader: BufferReader) -> WebFile? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.storage.FileType? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.storage.FileType + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.upload.WebFile.webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!) + } + else { + return nil + } + } + + } +} +public extension Api.users { + enum UserFull: TypeConstructorDescription { + case userFull(fullUser: Api.UserFull, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .userFull(let fullUser, let chats, let users): + if boxed { + buffer.appendInt32(997004590) + } + fullUser.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { item.serialize(buffer, true) } - return (FunctionDescription(name: "users.getUsers", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.User]? in - let reader = BufferReader(buffer) - var result: [Api.User]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - return result - }) - } -} -public extension Api.functions.users { - static func setSecureValueErrors(id: Api.InputUser, errors: [Api.SecureValueError]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1865902923) - id.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(errors.count)) - for item in errors { + buffer.appendInt32(Int32(users.count)) + for item in users { item.serialize(buffer, true) } - return (FunctionDescription(name: "users.setSecureValueErrors", parameters: [("id", String(describing: id)), ("errors", String(describing: errors))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .userFull(let fullUser, let chats, let users): + return ("userFull", [("fullUser", fullUser as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_userFull(_ reader: BufferReader) -> UserFull? { + var _1: Api.UserFull? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.UserFull + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.users.UserFull.userFull(fullUser: _1!, chats: _2!, users: _3!) + } + else { + return nil + } + } + + } } diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift new file mode 100644 index 00000000000..283bdcd1dc7 --- /dev/null +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -0,0 +1,10785 @@ +public extension Api.functions.account { + static func acceptAuthorization(botId: Int64, scope: String, publicKey: String, valueHashes: [Api.SecureValueHash], credentials: Api.SecureCredentialsEncrypted) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-202552205) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeString(scope, buffer: buffer, boxed: false) + serializeString(publicKey, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(valueHashes.count)) + for item in valueHashes { + item.serialize(buffer, true) + } + credentials.serialize(buffer, true) + return (FunctionDescription(name: "account.acceptAuthorization", parameters: [("botId", String(describing: botId)), ("scope", String(describing: scope)), ("publicKey", String(describing: publicKey)), ("valueHashes", String(describing: valueHashes)), ("credentials", String(describing: credentials))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func cancelPasswordEmail() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1043606090) + + return (FunctionDescription(name: "account.cancelPasswordEmail", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func changeAuthorizationSettings(flags: Int32, hash: Int64, encryptedRequestsDisabled: Api.Bool?, callRequestsDisabled: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1089766498) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {encryptedRequestsDisabled!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {callRequestsDisabled!.serialize(buffer, true)} + return (FunctionDescription(name: "account.changeAuthorizationSettings", parameters: [("flags", String(describing: flags)), ("hash", String(describing: hash)), ("encryptedRequestsDisabled", String(describing: encryptedRequestsDisabled)), ("callRequestsDisabled", String(describing: callRequestsDisabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func changePhone(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1891839707) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeString(phoneCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.changePhone", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("phoneCode", String(describing: phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.account { + static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(655677548) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.checkUsername", parameters: [("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func clearRecentEmojiStatuses() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(404757166) + + return (FunctionDescription(name: "account.clearRecentEmojiStatuses", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func confirmPasswordEmail(code: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1881204448) + serializeString(code, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.confirmPasswordEmail", parameters: [("code", String(describing: code))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func confirmPhone(phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1596029123) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeString(phoneCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.confirmPhone", parameters: [("phoneCodeHash", String(describing: phoneCodeHash)), ("phoneCode", String(describing: phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func createBusinessChatLink(link: Api.InputBusinessChatLink) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2007898482) + link.serialize(buffer, true) + return (FunctionDescription(name: "account.createBusinessChatLink", parameters: [("link", String(describing: link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BusinessChatLink? in + let reader = BufferReader(buffer) + var result: Api.BusinessChatLink? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.BusinessChatLink + } + return result + }) + } +} +public extension Api.functions.account { + static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1697530880) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(slug, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(settings!.count)) + for item in settings! { + item.serialize(buffer, true) + }} + return (FunctionDescription(name: "account.createTheme", parameters: [("flags", String(describing: flags)), ("slug", String(describing: slug)), ("title", String(describing: title)), ("document", String(describing: document)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in + let reader = BufferReader(buffer) + var result: Api.Theme? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Theme + } + return result + }) + } +} +public extension Api.functions.account { + static func declinePasswordReset() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1284770294) + + return (FunctionDescription(name: "account.declinePasswordReset", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func deleteAccount(flags: Int32, reason: String, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1564422284) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(reason, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {password!.serialize(buffer, true)} + return (FunctionDescription(name: "account.deleteAccount", parameters: [("flags", String(describing: flags)), ("reason", String(describing: reason)), ("password", String(describing: password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func deleteAutoSaveExceptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1404829728) + + return (FunctionDescription(name: "account.deleteAutoSaveExceptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func deleteBusinessChatLink(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1611085428) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.deleteBusinessChatLink", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func deleteSecureValue(types: [Api.SecureValueType]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1199522741) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(types.count)) + for item in types { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "account.deleteSecureValue", parameters: [("types", String(describing: types))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func disablePeerConnectedBot(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1581481689) + peer.serialize(buffer, true) + return (FunctionDescription(name: "account.disablePeerConnectedBot", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func editBusinessChatLink(slug: String, link: Api.InputBusinessChatLink) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1942744913) + serializeString(slug, buffer: buffer, boxed: false) + link.serialize(buffer, true) + return (FunctionDescription(name: "account.editBusinessChatLink", parameters: [("slug", String(describing: slug)), ("link", String(describing: link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BusinessChatLink? in + let reader = BufferReader(buffer) + var result: Api.BusinessChatLink? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.BusinessChatLink + } + return result + }) + } +} +public extension Api.functions.account { + static func finishTakeoutSession(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(489050862) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.finishTakeoutSession", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func getAccountTTL() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(150761757) + + return (FunctionDescription(name: "account.getAccountTTL", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AccountDaysTTL? in + let reader = BufferReader(buffer) + var result: Api.AccountDaysTTL? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.AccountDaysTTL + } + return result + }) + } +} +public extension Api.functions.account { + static func getAllSecureValues() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SecureValue]>) { + let buffer = Buffer() + buffer.appendInt32(-1299661699) + + return (FunctionDescription(name: "account.getAllSecureValues", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SecureValue]? in + let reader = BufferReader(buffer) + var result: [Api.SecureValue]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) + } + return result + }) + } +} +public extension Api.functions.account { + static func getAuthorizationForm(botId: Int64, scope: String, publicKey: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1456907910) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeString(scope, buffer: buffer, boxed: false) + serializeString(publicKey, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getAuthorizationForm", parameters: [("botId", String(describing: botId)), ("scope", String(describing: scope)), ("publicKey", String(describing: publicKey))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AuthorizationForm? in + let reader = BufferReader(buffer) + var result: Api.account.AuthorizationForm? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.AuthorizationForm + } + return result + }) + } +} +public extension Api.functions.account { + static func getAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-484392616) + + return (FunctionDescription(name: "account.getAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Authorizations? in + let reader = BufferReader(buffer) + var result: Api.account.Authorizations? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Authorizations + } + return result + }) + } +} +public extension Api.functions.account { + static func getAutoDownloadSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1457130303) + + return (FunctionDescription(name: "account.getAutoDownloadSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AutoDownloadSettings? in + let reader = BufferReader(buffer) + var result: Api.account.AutoDownloadSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.AutoDownloadSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getAutoSaveSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1379156774) + + return (FunctionDescription(name: "account.getAutoSaveSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AutoSaveSettings? in + let reader = BufferReader(buffer) + var result: Api.account.AutoSaveSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.AutoSaveSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getBotBusinessConnection(connectionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1990746736) + serializeString(connectionId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getBotBusinessConnection", parameters: [("connectionId", String(describing: connectionId))]), 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.account { + static func getBusinessChatLinks() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1869667809) + + return (FunctionDescription(name: "account.getBusinessChatLinks", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.BusinessChatLinks? in + let reader = BufferReader(buffer) + var result: Api.account.BusinessChatLinks? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.BusinessChatLinks + } + return result + }) + } +} +public extension Api.functions.account { + static func getChannelDefaultEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1999087573) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getChannelDefaultEmojiStatuses", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in + let reader = BufferReader(buffer) + var result: Api.account.EmojiStatuses? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses + } + return result + }) + } +} +public extension Api.functions.account { + static func getChannelRestrictedStatusEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(900325589) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getChannelRestrictedStatusEmojis", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in + let reader = BufferReader(buffer) + var result: Api.EmojiList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiList + } + return result + }) + } +} +public extension Api.functions.account { + static func getChatThemes(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-700916087) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in + let reader = BufferReader(buffer) + var result: Api.account.Themes? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Themes + } + return result + }) + } +} +public extension Api.functions.account { + static func getConnectedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1319421967) + + return (FunctionDescription(name: "account.getConnectedBots", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ConnectedBots? in + let reader = BufferReader(buffer) + var result: Api.account.ConnectedBots? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.ConnectedBots + } + return result + }) + } +} +public extension Api.functions.account { + static func getContactSignUpNotification() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1626880216) + + return (FunctionDescription(name: "account.getContactSignUpNotification", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func getContentSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1952756306) + + return (FunctionDescription(name: "account.getContentSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ContentSettings? in + let reader = BufferReader(buffer) + var result: Api.account.ContentSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.ContentSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getDefaultBackgroundEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1509246514) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getDefaultBackgroundEmojis", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in + let reader = BufferReader(buffer) + var result: Api.EmojiList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiList + } + return result + }) + } +} +public extension Api.functions.account { + static func getDefaultEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-696962170) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getDefaultEmojiStatuses", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in + let reader = BufferReader(buffer) + var result: Api.account.EmojiStatuses? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses + } + return result + }) + } +} +public extension Api.functions.account { + static func getDefaultGroupPhotoEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1856479058) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getDefaultGroupPhotoEmojis", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in + let reader = BufferReader(buffer) + var result: Api.EmojiList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiList + } + return result + }) + } +} +public extension Api.functions.account { + static func getDefaultProfilePhotoEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-495647960) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getDefaultProfilePhotoEmojis", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in + let reader = BufferReader(buffer) + var result: Api.EmojiList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiList + } + return result + }) + } +} +public extension Api.functions.account { + static func getGlobalPrivacySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-349483786) + + return (FunctionDescription(name: "account.getGlobalPrivacySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.GlobalPrivacySettings? in + let reader = BufferReader(buffer) + var result: Api.GlobalPrivacySettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.GlobalPrivacySettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getMultiWallPapers(wallpapers: [Api.InputWallPaper]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.WallPaper]>) { + let buffer = Buffer() + buffer.appendInt32(1705865692) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(wallpapers.count)) + for item in wallpapers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "account.getMultiWallPapers", parameters: [("wallpapers", String(describing: wallpapers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.WallPaper]? in + let reader = BufferReader(buffer) + var result: [Api.WallPaper]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.WallPaper.self) + } + return result + }) + } +} +public extension Api.functions.account { + static func getNotifyExceptions(flags: Int32, peer: Api.InputNotifyPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1398240377) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} + return (FunctionDescription(name: "account.getNotifyExceptions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), 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.account { + static func getNotifySettings(peer: Api.InputNotifyPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(313765169) + peer.serialize(buffer, true) + return (FunctionDescription(name: "account.getNotifySettings", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.PeerNotifySettings? in + let reader = BufferReader(buffer) + var result: Api.PeerNotifySettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getPassword() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1418342645) + + return (FunctionDescription(name: "account.getPassword", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Password? in + let reader = BufferReader(buffer) + var result: Api.account.Password? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Password + } + return result + }) + } +} +public extension Api.functions.account { + static func getPasswordSettings(password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1663767815) + password.serialize(buffer, true) + return (FunctionDescription(name: "account.getPasswordSettings", parameters: [("password", String(describing: password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PasswordSettings? in + let reader = BufferReader(buffer) + var result: Api.account.PasswordSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.PasswordSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getPrivacy(key: Api.InputPrivacyKey) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-623130288) + key.serialize(buffer, true) + return (FunctionDescription(name: "account.getPrivacy", parameters: [("key", String(describing: key))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PrivacyRules? in + let reader = BufferReader(buffer) + var result: Api.account.PrivacyRules? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.PrivacyRules + } + return result + }) + } +} +public extension Api.functions.account { + static func getReactionsNotifySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(115172684) + + return (FunctionDescription(name: "account.getReactionsNotifySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReactionsNotifySettings? in + let reader = BufferReader(buffer) + var result: Api.ReactionsNotifySettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ReactionsNotifySettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getRecentEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(257392901) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getRecentEmojiStatuses", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in + let reader = BufferReader(buffer) + var result: Api.account.EmojiStatuses? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses + } + return result + }) + } +} +public extension Api.functions.account { + static func getSavedRingtones(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-510647672) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getSavedRingtones", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SavedRingtones? in + let reader = BufferReader(buffer) + var result: Api.account.SavedRingtones? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.SavedRingtones + } + return result + }) + } +} +public extension Api.functions.account { + static func getSecureValue(types: [Api.SecureValueType]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SecureValue]>) { + let buffer = Buffer() + buffer.appendInt32(1936088002) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(types.count)) + for item in types { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "account.getSecureValue", parameters: [("types", String(describing: types))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SecureValue]? in + let reader = BufferReader(buffer) + var result: [Api.SecureValue]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) + } + return result + }) + } +} +public extension Api.functions.account { + static func getTheme(format: String, theme: Api.InputTheme) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(978872812) + serializeString(format, buffer: buffer, boxed: false) + theme.serialize(buffer, true) + return (FunctionDescription(name: "account.getTheme", parameters: [("format", String(describing: format)), ("theme", String(describing: theme))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in + let reader = BufferReader(buffer) + var result: Api.Theme? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Theme + } + return result + }) + } +} +public extension Api.functions.account { + static func getThemes(format: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1913054296) + serializeString(format, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getThemes", parameters: [("format", String(describing: format)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in + let reader = BufferReader(buffer) + var result: Api.account.Themes? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Themes + } + return result + }) + } +} +public extension Api.functions.account { + static func getTmpPassword(password: Api.InputCheckPasswordSRP, period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1151208273) + password.serialize(buffer, true) + serializeInt32(period, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getTmpPassword", parameters: [("password", String(describing: password)), ("period", String(describing: period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.TmpPassword? in + let reader = BufferReader(buffer) + var result: Api.account.TmpPassword? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.TmpPassword + } + return result + }) + } +} +public extension Api.functions.account { + static func getWallPaper(wallpaper: Api.InputWallPaper) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-57811990) + wallpaper.serialize(buffer, true) + return (FunctionDescription(name: "account.getWallPaper", parameters: [("wallpaper", String(describing: wallpaper))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WallPaper? in + let reader = BufferReader(buffer) + var result: Api.WallPaper? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WallPaper + } + return result + }) + } +} +public extension Api.functions.account { + static func getWallPapers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(127302966) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getWallPapers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WallPapers? in + let reader = BufferReader(buffer) + var result: Api.account.WallPapers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.WallPapers + } + return result + }) + } +} +public extension Api.functions.account { + static func getWebAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(405695855) + + return (FunctionDescription(name: "account.getWebAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WebAuthorizations? in + let reader = BufferReader(buffer) + var result: Api.account.WebAuthorizations? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.WebAuthorizations + } + return result + }) + } +} +public extension Api.functions.account { + static func initTakeoutSession(flags: Int32, fileMaxSize: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1896617296) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 5) != 0 {serializeInt64(fileMaxSize!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "account.initTakeoutSession", parameters: [("flags", String(describing: flags)), ("fileMaxSize", String(describing: fileMaxSize))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Takeout? in + let reader = BufferReader(buffer) + var result: Api.account.Takeout? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Takeout + } + return result + }) + } +} +public extension Api.functions.account { + static func installTheme(flags: Int32, theme: Api.InputTheme?, format: String?, baseTheme: Api.BaseTheme?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-953697477) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {theme!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(format!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {baseTheme!.serialize(buffer, true)} + return (FunctionDescription(name: "account.installTheme", parameters: [("flags", String(describing: flags)), ("theme", String(describing: theme)), ("format", String(describing: format)), ("baseTheme", String(describing: baseTheme))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func installWallPaper(wallpaper: Api.InputWallPaper, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-18000023) + wallpaper.serialize(buffer, true) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.installWallPaper", parameters: [("wallpaper", String(describing: wallpaper)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func invalidateSignInCodes(codes: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-896866118) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(codes.count)) + for item in codes { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.invalidateSignInCodes", parameters: [("codes", String(describing: codes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func registerDevice(flags: Int32, tokenType: Int32, token: String, appSandbox: Api.Bool, secret: Buffer, otherUids: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-326762118) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(tokenType, buffer: buffer, boxed: false) + serializeString(token, buffer: buffer, boxed: false) + appSandbox.serialize(buffer, true) + serializeBytes(secret, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(otherUids.count)) + for item in otherUids { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.registerDevice", parameters: [("flags", String(describing: flags)), ("tokenType", String(describing: tokenType)), ("token", String(describing: token)), ("appSandbox", String(describing: appSandbox)), ("secret", String(describing: secret)), ("otherUids", String(describing: otherUids))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func reorderUsernames(order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-279966037) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.reorderUsernames", parameters: [("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func reportPeer(peer: Api.InputPeer, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-977650298) + peer.serialize(buffer, true) + reason.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.reportPeer", parameters: [("peer", String(describing: peer)), ("reason", String(describing: reason)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func reportProfilePhoto(peer: Api.InputPeer, photoId: Api.InputPhoto, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-91437323) + peer.serialize(buffer, true) + photoId.serialize(buffer, true) + reason.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.reportProfilePhoto", parameters: [("peer", String(describing: peer)), ("photoId", String(describing: photoId)), ("reason", String(describing: reason)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resendPasswordEmail() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2055154197) + + return (FunctionDescription(name: "account.resendPasswordEmail", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resetAuthorization(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-545786948) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.resetAuthorization", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resetNotifySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-612493497) + + return (FunctionDescription(name: "account.resetNotifySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resetPassword() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1828139493) + + return (FunctionDescription(name: "account.resetPassword", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ResetPasswordResult? in + let reader = BufferReader(buffer) + var result: Api.account.ResetPasswordResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.ResetPasswordResult + } + return result + }) + } +} +public extension Api.functions.account { + static func resetWallPapers() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1153722364) + + return (FunctionDescription(name: "account.resetWallPapers", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resetWebAuthorization(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(755087855) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.resetWebAuthorization", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resetWebAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1747789204) + + return (FunctionDescription(name: "account.resetWebAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resolveBusinessChatLink(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1418913262) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.resolveBusinessChatLink", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ResolvedBusinessChatLinks? in + let reader = BufferReader(buffer) + var result: Api.account.ResolvedBusinessChatLinks? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.ResolvedBusinessChatLinks + } + return result + }) + } +} +public extension Api.functions.account { + static func saveAutoDownloadSettings(flags: Int32, settings: Api.AutoDownloadSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1995661875) + serializeInt32(flags, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.saveAutoDownloadSettings", parameters: [("flags", String(describing: flags)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func saveAutoSaveSettings(flags: Int32, peer: Api.InputPeer?, settings: Api.AutoSaveSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-694451359) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {peer!.serialize(buffer, true)} + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.saveAutoSaveSettings", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func saveRingtone(id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1038768899) + id.serialize(buffer, true) + unsave.serialize(buffer, true) + return (FunctionDescription(name: "account.saveRingtone", parameters: [("id", String(describing: id)), ("unsave", String(describing: unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SavedRingtone? in + let reader = BufferReader(buffer) + var result: Api.account.SavedRingtone? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.SavedRingtone + } + return result + }) + } +} +public extension Api.functions.account { + static func saveSecureValue(value: Api.InputSecureValue, secureSecretId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1986010339) + value.serialize(buffer, true) + serializeInt64(secureSecretId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.saveSecureValue", parameters: [("value", String(describing: value)), ("secureSecretId", String(describing: secureSecretId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SecureValue? in + let reader = BufferReader(buffer) + var result: Api.SecureValue? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.SecureValue + } + return result + }) + } +} +public extension Api.functions.account { + static func saveTheme(theme: Api.InputTheme, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-229175188) + theme.serialize(buffer, true) + unsave.serialize(buffer, true) + return (FunctionDescription(name: "account.saveTheme", parameters: [("theme", String(describing: theme)), ("unsave", String(describing: unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func saveWallPaper(wallpaper: Api.InputWallPaper, unsave: Api.Bool, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1817860919) + wallpaper.serialize(buffer, true) + unsave.serialize(buffer, true) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.saveWallPaper", parameters: [("wallpaper", String(describing: wallpaper)), ("unsave", String(describing: unsave)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func sendChangePhoneCode(phoneNumber: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2108208411) + serializeString(phoneNumber, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.sendChangePhoneCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.account { + static func sendConfirmPhoneCode(hash: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(457157256) + serializeString(hash, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.sendConfirmPhoneCode", parameters: [("hash", String(describing: hash)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.account { + static func sendVerifyEmailCode(purpose: Api.EmailVerifyPurpose, email: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1730136133) + purpose.serialize(buffer, true) + serializeString(email, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.sendVerifyEmailCode", parameters: [("purpose", String(describing: purpose)), ("email", String(describing: email))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SentEmailCode? in + let reader = BufferReader(buffer) + var result: Api.account.SentEmailCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.SentEmailCode + } + return result + }) + } +} +public extension Api.functions.account { + static func sendVerifyPhoneCode(phoneNumber: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1516022023) + serializeString(phoneNumber, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.sendVerifyPhoneCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.account { + static func setAccountTTL(ttl: Api.AccountDaysTTL) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(608323678) + ttl.serialize(buffer, true) + return (FunctionDescription(name: "account.setAccountTTL", parameters: [("ttl", String(describing: ttl))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func setAuthorizationTTL(authorizationTtlDays: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1081501024) + serializeInt32(authorizationTtlDays, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.setAuthorizationTTL", parameters: [("authorizationTtlDays", String(describing: authorizationTtlDays))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func setContactSignUpNotification(silent: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-806076575) + silent.serialize(buffer, true) + return (FunctionDescription(name: "account.setContactSignUpNotification", parameters: [("silent", String(describing: silent))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func setContentSettings(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1250643605) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.setContentSettings", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func setGlobalPrivacySettings(settings: Api.GlobalPrivacySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(517647042) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.setGlobalPrivacySettings", parameters: [("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.GlobalPrivacySettings? in + let reader = BufferReader(buffer) + var result: Api.GlobalPrivacySettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.GlobalPrivacySettings + } + return result + }) + } +} +public extension Api.functions.account { + static func setPrivacy(key: Api.InputPrivacyKey, rules: [Api.InputPrivacyRule]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-906486552) + key.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(rules.count)) + for item in rules { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "account.setPrivacy", parameters: [("key", String(describing: key)), ("rules", String(describing: rules))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PrivacyRules? in + let reader = BufferReader(buffer) + var result: Api.account.PrivacyRules? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.PrivacyRules + } + return result + }) + } +} +public extension Api.functions.account { + static func setReactionsNotifySettings(settings: Api.ReactionsNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(829220168) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.setReactionsNotifySettings", parameters: [("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReactionsNotifySettings? in + let reader = BufferReader(buffer) + var result: Api.ReactionsNotifySettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ReactionsNotifySettings + } + return result + }) + } +} +public extension Api.functions.account { + static func toggleConnectedBotPaused(peer: Api.InputPeer, paused: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1684934807) + peer.serialize(buffer, true) + paused.serialize(buffer, true) + return (FunctionDescription(name: "account.toggleConnectedBotPaused", parameters: [("peer", String(describing: peer)), ("paused", String(describing: paused))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func toggleSponsoredMessages(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1176919155) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "account.toggleSponsoredMessages", parameters: [("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func toggleUsername(username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1490465654) + serializeString(username, buffer: buffer, boxed: false) + active.serialize(buffer, true) + return (FunctionDescription(name: "account.toggleUsername", parameters: [("username", String(describing: username)), ("active", String(describing: active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func unregisterDevice(tokenType: Int32, token: String, otherUids: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1779249670) + serializeInt32(tokenType, buffer: buffer, boxed: false) + serializeString(token, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(otherUids.count)) + for item in otherUids { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.unregisterDevice", parameters: [("tokenType", String(describing: tokenType)), ("token", String(describing: token)), ("otherUids", String(describing: otherUids))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBirthday(flags: Int32, birthday: Api.Birthday?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-865203183) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {birthday!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateBirthday", parameters: [("flags", String(describing: flags)), ("birthday", String(describing: birthday))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessAwayMessage(flags: Int32, message: Api.InputBusinessAwayMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1570078811) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {message!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateBusinessAwayMessage", parameters: [("flags", String(describing: flags)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessGreetingMessage(flags: Int32, message: Api.InputBusinessGreetingMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1724755908) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {message!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateBusinessGreetingMessage", parameters: [("flags", String(describing: flags)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessIntro(flags: Int32, intro: Api.InputBusinessIntro?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1508585420) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {intro!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateBusinessIntro", parameters: [("flags", String(describing: flags)), ("intro", String(describing: intro))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessLocation(flags: Int32, geoPoint: Api.InputGeoPoint?, address: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1637149926) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {geoPoint!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {serializeString(address!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "account.updateBusinessLocation", parameters: [("flags", String(describing: flags)), ("geoPoint", String(describing: geoPoint)), ("address", String(describing: address))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessWorkHours(flags: Int32, businessWorkHours: Api.BusinessWorkHours?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1258348646) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {businessWorkHours!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateBusinessWorkHours", parameters: [("flags", String(describing: flags)), ("businessWorkHours", String(describing: businessWorkHours))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateColor(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2096079197) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "account.updateColor", parameters: [("flags", String(describing: flags)), ("color", String(describing: color)), ("backgroundEmojiId", String(describing: backgroundEmojiId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateConnectedBot(flags: Int32, bot: Api.InputUser, recipients: Api.InputBusinessBotRecipients) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1138250269) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + recipients.serialize(buffer, true) + return (FunctionDescription(name: "account.updateConnectedBot", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("recipients", String(describing: recipients))]), 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.account { + static func updateDeviceLocked(period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(954152242) + serializeInt32(period, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.updateDeviceLocked", parameters: [("period", String(describing: period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateEmojiStatus(emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-70001045) + emojiStatus.serialize(buffer, true) + return (FunctionDescription(name: "account.updateEmojiStatus", parameters: [("emojiStatus", String(describing: emojiStatus))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateNotifySettings(peer: Api.InputNotifyPeer, settings: Api.InputPeerNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2067899501) + peer.serialize(buffer, true) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.updateNotifySettings", parameters: [("peer", String(describing: peer)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updatePasswordSettings(password: Api.InputCheckPasswordSRP, newSettings: Api.account.PasswordInputSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1516564433) + password.serialize(buffer, true) + newSettings.serialize(buffer, true) + return (FunctionDescription(name: "account.updatePasswordSettings", parameters: [("password", String(describing: password)), ("newSettings", String(describing: newSettings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updatePersonalChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-649919008) + channel.serialize(buffer, true) + return (FunctionDescription(name: "account.updatePersonalChannel", parameters: [("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateProfile(flags: Int32, firstName: String?, lastName: String?, about: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2018596725) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(firstName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(lastName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(about!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "account.updateProfile", parameters: [("flags", String(describing: flags)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("about", String(describing: about))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.account { + static func updateStatus(offline: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1713919532) + offline.serialize(buffer, true) + return (FunctionDescription(name: "account.updateStatus", parameters: [("offline", String(describing: offline))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateTheme(flags: Int32, format: String, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(737414348) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(format, buffer: buffer, boxed: false) + theme.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(slug!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(settings!.count)) + for item in settings! { + item.serialize(buffer, true) + }} + return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", String(describing: flags)), ("format", String(describing: format)), ("theme", String(describing: theme)), ("slug", String(describing: slug)), ("title", String(describing: title)), ("document", String(describing: document)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in + let reader = BufferReader(buffer) + var result: Api.Theme? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Theme + } + return result + }) + } +} +public extension Api.functions.account { + static func updateUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1040964988) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.updateUsername", parameters: [("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.account { + static func uploadRingtone(file: Api.InputFile, fileName: String, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2095414366) + file.serialize(buffer, true) + serializeString(fileName, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.uploadRingtone", parameters: [("file", String(describing: file)), ("fileName", String(describing: fileName)), ("mimeType", String(describing: mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in + let reader = BufferReader(buffer) + var result: Api.Document? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Document + } + return result + }) + } +} +public extension Api.functions.account { + static func uploadTheme(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, fileName: String, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(473805619) + serializeInt32(flags, buffer: buffer, boxed: false) + file.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {thumb!.serialize(buffer, true)} + serializeString(fileName, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.uploadTheme", parameters: [("flags", String(describing: flags)), ("file", String(describing: file)), ("thumb", String(describing: thumb)), ("fileName", String(describing: fileName)), ("mimeType", String(describing: mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in + let reader = BufferReader(buffer) + var result: Api.Document? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Document + } + return result + }) + } +} +public extension Api.functions.account { + static func uploadWallPaper(flags: Int32, file: Api.InputFile, mimeType: String, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-476410109) + serializeInt32(flags, buffer: buffer, boxed: false) + file.serialize(buffer, true) + serializeString(mimeType, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.uploadWallPaper", parameters: [("flags", String(describing: flags)), ("file", String(describing: file)), ("mimeType", String(describing: mimeType)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WallPaper? in + let reader = BufferReader(buffer) + var result: Api.WallPaper? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WallPaper + } + return result + }) + } +} +public extension Api.functions.account { + static func verifyEmail(purpose: Api.EmailVerifyPurpose, verification: Api.EmailVerification) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(53322959) + purpose.serialize(buffer, true) + verification.serialize(buffer, true) + return (FunctionDescription(name: "account.verifyEmail", parameters: [("purpose", String(describing: purpose)), ("verification", String(describing: verification))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmailVerified? in + let reader = BufferReader(buffer) + var result: Api.account.EmailVerified? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.EmailVerified + } + return result + }) + } +} +public extension Api.functions.account { + static func verifyPhone(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1305716726) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeString(phoneCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.verifyPhone", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("phoneCode", String(describing: phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func acceptLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-392909491) + serializeBytes(token, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.acceptLoginToken", parameters: [("token", String(describing: token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Authorization? in + let reader = BufferReader(buffer) + var result: Api.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func bindTempAuthKey(permAuthKeyId: Int64, nonce: Int64, expiresAt: Int32, encryptedMessage: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-841733627) + serializeInt64(permAuthKeyId, buffer: buffer, boxed: false) + serializeInt64(nonce, buffer: buffer, boxed: false) + serializeInt32(expiresAt, buffer: buffer, boxed: false) + serializeBytes(encryptedMessage, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.bindTempAuthKey", parameters: [("permAuthKeyId", String(describing: permAuthKeyId)), ("nonce", String(describing: nonce)), ("expiresAt", String(describing: expiresAt)), ("encryptedMessage", String(describing: encryptedMessage))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func cancelCode(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(520357240) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.cancelCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func checkPassword(password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-779399914) + password.serialize(buffer, true) + return (FunctionDescription(name: "auth.checkPassword", parameters: [("password", String(describing: password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func checkRecoveryPassword(code: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(221691769) + serializeString(code, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.checkRecoveryPassword", parameters: [("code", String(describing: code))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func dropTempAuthKeys(exceptAuthKeys: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1907842680) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(exceptAuthKeys.count)) + for item in exceptAuthKeys { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "auth.dropTempAuthKeys", parameters: [("exceptAuthKeys", String(describing: exceptAuthKeys))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func exportAuthorization(dcId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-440401971) + serializeInt32(dcId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.exportAuthorization", parameters: [("dcId", String(describing: dcId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.ExportedAuthorization? in + let reader = BufferReader(buffer) + var result: Api.auth.ExportedAuthorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.ExportedAuthorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func exportLoginToken(apiId: Int32, apiHash: String, exceptIds: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1210022402) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(exceptIds.count)) + for item in exceptIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "auth.exportLoginToken", parameters: [("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("exceptIds", String(describing: exceptIds))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in + let reader = BufferReader(buffer) + var result: Api.auth.LoginToken? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken + } + return result + }) + } +} +public extension Api.functions.auth { + static func importAuthorization(id: Int64, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1518699091) + serializeInt64(id, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.importAuthorization", parameters: [("id", String(describing: id)), ("bytes", String(describing: bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func importBotAuthorization(flags: Int32, apiId: Int32, apiHash: String, botAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1738800940) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + serializeString(botAuthToken, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.importBotAuthorization", parameters: [("flags", String(describing: flags)), ("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("botAuthToken", String(describing: botAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func importLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1783866140) + serializeBytes(token, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.importLoginToken", parameters: [("token", String(describing: token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in + let reader = BufferReader(buffer) + var result: Api.auth.LoginToken? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken + } + return result + }) + } +} +public extension Api.functions.auth { + static func importWebTokenAuthorization(apiId: Int32, apiHash: String, webAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(767062953) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + serializeString(webAuthToken, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.importWebTokenAuthorization", parameters: [("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("webAuthToken", String(describing: webAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func logOut() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1047706137) + + return (FunctionDescription(name: "auth.logOut", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoggedOut? in + let reader = BufferReader(buffer) + var result: Api.auth.LoggedOut? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.LoggedOut + } + return result + }) + } +} +public extension Api.functions.auth { + static func recoverPassword(flags: Int32, code: String, newSettings: Api.account.PasswordInputSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(923364464) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(code, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {newSettings!.serialize(buffer, true)} + return (FunctionDescription(name: "auth.recoverPassword", parameters: [("flags", String(describing: flags)), ("code", String(describing: code)), ("newSettings", String(describing: newSettings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func reportMissingCode(phoneNumber: String, phoneCodeHash: String, mnc: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-878841866) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeString(mnc, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.reportMissingCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("mnc", String(describing: mnc))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func requestFirebaseSms(flags: Int32, phoneNumber: String, phoneCodeHash: String, safetyNetToken: String?, playIntegrityToken: String?, iosPushSecret: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1908857314) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(safetyNetToken!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(playIntegrityToken!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(iosPushSecret!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "auth.requestFirebaseSms", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("safetyNetToken", String(describing: safetyNetToken)), ("playIntegrityToken", String(describing: playIntegrityToken)), ("iosPushSecret", String(describing: iosPushSecret))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func requestPasswordRecovery() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-661144474) + + return (FunctionDescription(name: "auth.requestPasswordRecovery", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.PasswordRecovery? in + let reader = BufferReader(buffer) + var result: Api.auth.PasswordRecovery? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.PasswordRecovery + } + return result + }) + } +} +public extension Api.functions.auth { + static func resendCode(flags: Int32, phoneNumber: String, phoneCodeHash: String, reason: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-890997469) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(reason!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "auth.resendCode", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("reason", String(describing: reason))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.auth { + static func resetAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1616179942) + + return (FunctionDescription(name: "auth.resetAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func resetLoginEmail(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2123760019) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.resetLoginEmail", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.auth { + static func sendCode(phoneNumber: String, apiId: Int32, apiHash: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1502141361) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "auth.sendCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.auth { + static func signIn(flags: Int32, phoneNumber: String, phoneCodeHash: String, phoneCode: String?, emailVerification: Api.EmailVerification?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1923962543) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(phoneCode!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {emailVerification!.serialize(buffer, true)} + return (FunctionDescription(name: "auth.signIn", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("phoneCode", String(describing: phoneCode)), ("emailVerification", String(describing: emailVerification))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func signUp(flags: Int32, phoneNumber: String, phoneCodeHash: String, firstName: String, lastName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1429752041) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.signUp", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.bots { + static func allowSendMessage(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-248323089) + bot.serialize(buffer, true) + return (FunctionDescription(name: "bots.allowSendMessage", parameters: [("bot", String(describing: bot))]), 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.bots { + static func answerWebhookJSONQuery(queryId: Int64, data: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-434028723) + serializeInt64(queryId, buffer: buffer, boxed: false) + data.serialize(buffer, true) + return (FunctionDescription(name: "bots.answerWebhookJSONQuery", parameters: [("queryId", String(describing: queryId)), ("data", String(describing: data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func canSendMessage(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(324662502) + bot.serialize(buffer, true) + return (FunctionDescription(name: "bots.canSendMessage", parameters: [("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func getBotCommands(scope: Api.BotCommandScope, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.BotCommand]>) { + let buffer = Buffer() + buffer.appendInt32(-481554986) + scope.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.getBotCommands", parameters: [("scope", String(describing: scope)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.BotCommand]? in + let reader = BufferReader(buffer) + var result: [Api.BotCommand]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) + } + return result + }) + } +} +public extension Api.functions.bots { + static func getBotInfo(flags: Int32, bot: Api.InputUser?, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-589753091) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {bot!.serialize(buffer, true)} + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.getBotInfo", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.BotInfo? in + let reader = BufferReader(buffer) + var result: Api.bots.BotInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.bots.BotInfo + } + return result + }) + } +} +public extension Api.functions.bots { + static func getBotMenuButton(userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1671369944) + userId.serialize(buffer, true) + return (FunctionDescription(name: "bots.getBotMenuButton", parameters: [("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BotMenuButton? in + let reader = BufferReader(buffer) + var result: Api.BotMenuButton? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.BotMenuButton + } + return result + }) + } +} +public extension Api.functions.bots { + static func invokeWebViewCustomMethod(bot: Api.InputUser, customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(142591463) + bot.serialize(buffer, true) + serializeString(customMethod, buffer: buffer, boxed: false) + params.serialize(buffer, true) + return (FunctionDescription(name: "bots.invokeWebViewCustomMethod", parameters: [("bot", String(describing: bot)), ("customMethod", String(describing: customMethod)), ("params", String(describing: params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in + let reader = BufferReader(buffer) + var result: Api.DataJSON? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.DataJSON + } + return result + }) + } +} +public extension Api.functions.bots { + static func reorderUsernames(bot: Api.InputUser, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1760972350) + bot.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "bots.reorderUsernames", parameters: [("bot", String(describing: bot)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func resetBotCommands(scope: Api.BotCommandScope, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1032708345) + scope.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.resetBotCommands", parameters: [("scope", String(describing: scope)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func sendCustomRequest(customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1440257555) + serializeString(customMethod, buffer: buffer, boxed: false) + params.serialize(buffer, true) + return (FunctionDescription(name: "bots.sendCustomRequest", parameters: [("customMethod", String(describing: customMethod)), ("params", String(describing: params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in + let reader = BufferReader(buffer) + var result: Api.DataJSON? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.DataJSON + } + return result + }) + } +} +public extension Api.functions.bots { + static func setBotBroadcastDefaultAdminRights(adminRights: Api.ChatAdminRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2021942497) + adminRights.serialize(buffer, true) + return (FunctionDescription(name: "bots.setBotBroadcastDefaultAdminRights", parameters: [("adminRights", String(describing: adminRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func setBotCommands(scope: Api.BotCommandScope, langCode: String, commands: [Api.BotCommand]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(85399130) + scope.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(commands.count)) + for item in commands { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "bots.setBotCommands", parameters: [("scope", String(describing: scope)), ("langCode", String(describing: langCode)), ("commands", String(describing: commands))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func setBotGroupDefaultAdminRights(adminRights: Api.ChatAdminRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1839281686) + adminRights.serialize(buffer, true) + return (FunctionDescription(name: "bots.setBotGroupDefaultAdminRights", parameters: [("adminRights", String(describing: adminRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func setBotInfo(flags: Int32, bot: Api.InputUser?, langCode: String, name: String?, about: String?, description: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(282013987) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {bot!.serialize(buffer, true)} + serializeString(langCode, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {serializeString(name!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeString(about!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(description!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "bots.setBotInfo", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("langCode", String(describing: langCode)), ("name", String(describing: name)), ("about", String(describing: about)), ("description", String(describing: description))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func setBotMenuButton(userId: Api.InputUser, button: Api.BotMenuButton) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1157944655) + userId.serialize(buffer, true) + button.serialize(buffer, true) + return (FunctionDescription(name: "bots.setBotMenuButton", parameters: [("userId", String(describing: userId)), ("button", String(describing: button))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func toggleUsername(bot: Api.InputUser, username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(87861619) + bot.serialize(buffer, true) + serializeString(username, buffer: buffer, boxed: false) + active.serialize(buffer, true) + return (FunctionDescription(name: "bots.toggleUsername", parameters: [("bot", String(describing: bot)), ("username", String(describing: username)), ("active", String(describing: active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func checkUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(283557164) + channel.serialize(buffer, true) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.checkUsername", parameters: [("channel", String(describing: channel)), ("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func clickSponsoredMessage(channel: Api.InputChannel, randomId: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(414170259) + channel.serialize(buffer, true) + serializeBytes(randomId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.clickSponsoredMessage", parameters: [("channel", String(describing: channel)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func convertToGigagroup(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(187239529) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.convertToGigagroup", parameters: [("channel", String(describing: channel))]), 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.channels { + static func createChannel(flags: Int32, title: String, about: String, geoPoint: Api.InputGeoPoint?, address: String?, ttlPeriod: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1862244601) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(about, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {geoPoint!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(address!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "channels.createChannel", parameters: [("flags", String(describing: flags)), ("title", String(describing: title)), ("about", String(describing: about)), ("geoPoint", String(describing: geoPoint)), ("address", String(describing: address)), ("ttlPeriod", String(describing: ttlPeriod))]), 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.channels { + static func createForumTopic(flags: Int32, channel: Api.InputChannel, title: String, iconColor: Int32?, iconEmojiId: Int64?, randomId: Int64, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-200539612) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(iconColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {sendAs!.serialize(buffer, true)} + return (FunctionDescription(name: "channels.createForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId)), ("randomId", String(describing: randomId)), ("sendAs", String(describing: sendAs))]), 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.channels { + static func deactivateAllUsernames(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(170155475) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.deactivateAllUsernames", parameters: [("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func deleteChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1072619549) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.deleteChannel", parameters: [("channel", String(describing: channel))]), 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.channels { + static func deleteHistory(flags: Int32, channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1683319225) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.deleteHistory", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("maxId", String(describing: maxId))]), 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.channels { + static func deleteMessages(channel: Api.InputChannel, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2067661490) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.deleteMessages", parameters: [("channel", String(describing: channel)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages + } + return result + }) + } +} +public extension Api.functions.channels { + static func deleteParticipantHistory(channel: Api.InputChannel, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(913655003) + channel.serialize(buffer, true) + participant.serialize(buffer, true) + return (FunctionDescription(name: "channels.deleteParticipantHistory", parameters: [("channel", String(describing: channel)), ("participant", String(describing: participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.channels { + static func deleteTopicHistory(channel: Api.InputChannel, topMsgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(876830509) + channel.serialize(buffer, true) + serializeInt32(topMsgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.deleteTopicHistory", parameters: [("channel", String(describing: channel)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.channels { + static func editAdmin(channel: Api.InputChannel, userId: Api.InputUser, adminRights: Api.ChatAdminRights, rank: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-751007486) + channel.serialize(buffer, true) + userId.serialize(buffer, true) + adminRights.serialize(buffer, true) + serializeString(rank, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.editAdmin", parameters: [("channel", String(describing: channel)), ("userId", String(describing: userId)), ("adminRights", String(describing: adminRights)), ("rank", String(describing: rank))]), 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.channels { + static func editBanned(channel: Api.InputChannel, participant: Api.InputPeer, bannedRights: Api.ChatBannedRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1763259007) + channel.serialize(buffer, true) + participant.serialize(buffer, true) + bannedRights.serialize(buffer, true) + return (FunctionDescription(name: "channels.editBanned", parameters: [("channel", String(describing: channel)), ("participant", String(describing: participant)), ("bannedRights", String(describing: bannedRights))]), 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.channels { + static func editCreator(channel: Api.InputChannel, userId: Api.InputUser, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1892102881) + channel.serialize(buffer, true) + userId.serialize(buffer, true) + password.serialize(buffer, true) + return (FunctionDescription(name: "channels.editCreator", parameters: [("channel", String(describing: channel)), ("userId", String(describing: userId)), ("password", String(describing: password))]), 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.channels { + static func editForumTopic(flags: Int32, channel: Api.InputChannel, topicId: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-186670715) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt32(topicId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {hidden!.serialize(buffer, true)} + return (FunctionDescription(name: "channels.editForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed)), ("hidden", String(describing: hidden))]), 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.channels { + static func editLocation(channel: Api.InputChannel, geoPoint: Api.InputGeoPoint, address: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1491484525) + channel.serialize(buffer, true) + geoPoint.serialize(buffer, true) + serializeString(address, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.editLocation", parameters: [("channel", String(describing: channel)), ("geoPoint", String(describing: geoPoint)), ("address", String(describing: address))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func editPhoto(channel: Api.InputChannel, photo: Api.InputChatPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-248621111) + channel.serialize(buffer, true) + photo.serialize(buffer, true) + return (FunctionDescription(name: "channels.editPhoto", parameters: [("channel", String(describing: channel)), ("photo", String(describing: photo))]), 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.channels { + static func editTitle(channel: Api.InputChannel, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1450044624) + channel.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.editTitle", parameters: [("channel", String(describing: channel)), ("title", String(describing: title))]), 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.channels { + static func exportMessageLink(flags: Int32, channel: Api.InputChannel, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-432034325) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.exportMessageLink", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedMessageLink? in + let reader = BufferReader(buffer) + var result: Api.ExportedMessageLink? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedMessageLink + } + return result + }) + } +} +public extension Api.functions.channels { + static func getAdminLog(flags: Int32, channel: Api.InputChannel, q: String, eventsFilter: Api.ChannelAdminLogEventsFilter?, admins: [Api.InputUser]?, maxId: Int64, minId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(870184064) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeString(q, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {eventsFilter!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(admins!.count)) + for item in admins! { + item.serialize(buffer, true) + }} + serializeInt64(maxId, buffer: buffer, boxed: false) + serializeInt64(minId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getAdminLog", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("q", String(describing: q)), ("eventsFilter", String(describing: eventsFilter)), ("admins", String(describing: admins)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.AdminLogResults? in + let reader = BufferReader(buffer) + var result: Api.channels.AdminLogResults? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.channels.AdminLogResults + } + return result + }) + } +} +public extension Api.functions.channels { + static func getAdminedPublicChannels(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-122669393) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getAdminedPublicChannels", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getChannelRecommendations(flags: Int32, channel: Api.InputChannel?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(631707458) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {channel!.serialize(buffer, true)} + return (FunctionDescription(name: "channels.getChannelRecommendations", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getChannels(id: [Api.InputChannel]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(176122811) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "channels.getChannels", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getForumTopics(flags: Int32, channel: Api.InputChannel, q: String?, offsetDate: Int32, offsetId: Int32, offsetTopic: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(233136337) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(q!, buffer: buffer, boxed: false)} + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetTopic, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getForumTopics", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("q", String(describing: q)), ("offsetDate", String(describing: offsetDate)), ("offsetId", String(describing: offsetId)), ("offsetTopic", String(describing: offsetTopic)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ForumTopics? in + let reader = BufferReader(buffer) + var result: Api.messages.ForumTopics? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ForumTopics + } + return result + }) + } +} +public extension Api.functions.channels { + static func getForumTopicsByID(channel: Api.InputChannel, topics: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1333584199) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(topics.count)) + for item in topics { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.getForumTopicsByID", parameters: [("channel", String(describing: channel)), ("topics", String(describing: topics))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ForumTopics? in + let reader = BufferReader(buffer) + var result: Api.messages.ForumTopics? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ForumTopics + } + return result + }) + } +} +public extension Api.functions.channels { + static func getFullChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(141781513) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.getFullChannel", parameters: [("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatFull? in + let reader = BufferReader(buffer) + var result: Api.messages.ChatFull? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ChatFull + } + return result + }) + } +} +public extension Api.functions.channels { + static func getGroupsForDiscussion() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-170208392) + + return (FunctionDescription(name: "channels.getGroupsForDiscussion", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getInactiveChannels() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(300429806) + + return (FunctionDescription(name: "channels.getInactiveChannels", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InactiveChats? in + let reader = BufferReader(buffer) + var result: Api.messages.InactiveChats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.InactiveChats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getLeftChannels(offset: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2092831552) + serializeInt32(offset, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getLeftChannels", parameters: [("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getMessages(channel: Api.InputChannel, id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1383294429) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "channels.getMessages", parameters: [("channel", String(describing: channel)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.channels { + static func getParticipant(channel: Api.InputChannel, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1599378234) + channel.serialize(buffer, true) + participant.serialize(buffer, true) + return (FunctionDescription(name: "channels.getParticipant", parameters: [("channel", String(describing: channel)), ("participant", String(describing: participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.ChannelParticipant? in + let reader = BufferReader(buffer) + var result: Api.channels.ChannelParticipant? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.channels.ChannelParticipant + } + return result + }) + } +} +public extension Api.functions.channels { + static func getParticipants(channel: Api.InputChannel, filter: Api.ChannelParticipantsFilter, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2010044880) + channel.serialize(buffer, true) + filter.serialize(buffer, true) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getParticipants", parameters: [("channel", String(describing: channel)), ("filter", String(describing: filter)), ("offset", String(describing: offset)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.ChannelParticipants? in + let reader = BufferReader(buffer) + var result: Api.channels.ChannelParticipants? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.channels.ChannelParticipants + } + return result + }) + } +} +public extension Api.functions.channels { + static func getSendAs(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(231174382) + peer.serialize(buffer, true) + return (FunctionDescription(name: "channels.getSendAs", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.SendAsPeers? in + let reader = BufferReader(buffer) + var result: Api.channels.SendAsPeers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.channels.SendAsPeers + } + return result + }) + } +} +public extension Api.functions.channels { + static func getSponsoredMessages(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-333377601) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.getSponsoredMessages", parameters: [("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SponsoredMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.SponsoredMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SponsoredMessages + } + return result + }) + } +} +public extension Api.functions.channels { + static func inviteToChannel(channel: Api.InputChannel, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-907854508) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "channels.inviteToChannel", parameters: [("channel", String(describing: channel)), ("users", String(describing: users))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in + let reader = BufferReader(buffer) + var result: Api.messages.InvitedUsers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers + } + return result + }) + } +} +public extension Api.functions.channels { + static func joinChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(615851205) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.joinChannel", parameters: [("channel", String(describing: channel))]), 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.channels { + static func leaveChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-130635115) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.leaveChannel", parameters: [("channel", String(describing: channel))]), 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.channels { + static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-871347913) + channel.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.readHistory", parameters: [("channel", String(describing: channel)), ("maxId", String(describing: maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func readMessageContents(channel: Api.InputChannel, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-357180360) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.readMessageContents", parameters: [("channel", String(describing: channel)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func reorderPinnedForumTopics(flags: Int32, channel: Api.InputChannel, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(693150095) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.reorderPinnedForumTopics", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("order", String(describing: order))]), 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.channels { + static func reorderUsernames(channel: Api.InputChannel, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1268978403) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.reorderUsernames", parameters: [("channel", String(describing: channel)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func reportAntiSpamFalsePositive(channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1471109485) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.reportAntiSpamFalsePositive", parameters: [("channel", String(describing: channel)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func reportSpam(channel: Api.InputChannel, participant: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-196443371) + channel.serialize(buffer, true) + participant.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.reportSpam", parameters: [("channel", String(describing: channel)), ("participant", String(describing: participant)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func reportSponsoredMessage(channel: Api.InputChannel, randomId: Buffer, option: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1349519687) + channel.serialize(buffer, true) + serializeBytes(randomId, buffer: buffer, boxed: false) + serializeBytes(option, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.reportSponsoredMessage", parameters: [("channel", String(describing: channel)), ("randomId", String(describing: randomId)), ("option", String(describing: option))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.SponsoredMessageReportResult? in + let reader = BufferReader(buffer) + var result: Api.channels.SponsoredMessageReportResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.channels.SponsoredMessageReportResult + } + return result + }) + } +} +public extension Api.functions.channels { + static func restrictSponsoredMessages(channel: Api.InputChannel, restricted: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1696000743) + channel.serialize(buffer, true) + restricted.serialize(buffer, true) + return (FunctionDescription(name: "channels.restrictSponsoredMessages", parameters: [("channel", String(describing: channel)), ("restricted", String(describing: restricted))]), 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.channels { + static func searchPosts(hashtag: String, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-778069893) + serializeString(hashtag, buffer: buffer, boxed: false) + serializeInt32(offsetRate, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.searchPosts", parameters: [("hashtag", String(describing: hashtag)), ("offsetRate", String(describing: offsetRate)), ("offsetPeer", String(describing: offsetPeer)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.channels { + static func setBoostsToUnblockRestrictions(channel: Api.InputChannel, boosts: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1388733202) + channel.serialize(buffer, true) + serializeInt32(boosts, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.setBoostsToUnblockRestrictions", parameters: [("channel", String(describing: channel)), ("boosts", String(describing: boosts))]), 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.channels { + static func setDiscussionGroup(broadcast: Api.InputChannel, group: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1079520178) + broadcast.serialize(buffer, true) + group.serialize(buffer, true) + return (FunctionDescription(name: "channels.setDiscussionGroup", parameters: [("broadcast", String(describing: broadcast)), ("group", String(describing: group))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func setEmojiStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1020866743) + channel.serialize(buffer, true) + stickerset.serialize(buffer, true) + return (FunctionDescription(name: "channels.setEmojiStickers", parameters: [("channel", String(describing: channel)), ("stickerset", String(describing: stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func setStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-359881479) + channel.serialize(buffer, true) + stickerset.serialize(buffer, true) + return (FunctionDescription(name: "channels.setStickers", parameters: [("channel", String(describing: channel)), ("stickerset", String(describing: stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleAntiSpam(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1760814315) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleAntiSpam", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { + static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1540781271) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleForum", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { + static func toggleJoinRequest(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1277789622) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleJoinRequest", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { + static func toggleJoinToSend(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-456419968) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleJoinToSend", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { + static func toggleParticipantsHidden(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1785624660) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleParticipantsHidden", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { + static func togglePreHistoryHidden(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-356796084) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.togglePreHistoryHidden", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { + static func toggleSignatures(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(527021574) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleSignatures", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { + static func toggleSlowMode(channel: Api.InputChannel, seconds: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-304832784) + channel.serialize(buffer, true) + serializeInt32(seconds, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.toggleSlowMode", parameters: [("channel", String(describing: channel)), ("seconds", String(describing: seconds))]), 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.channels { + static func toggleUsername(channel: Api.InputChannel, username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1358053637) + channel.serialize(buffer, true) + serializeString(username, buffer: buffer, boxed: false) + active.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleUsername", parameters: [("channel", String(describing: channel)), ("username", String(describing: username)), ("active", String(describing: active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleViewForumAsMessages(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1757889771) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleViewForumAsMessages", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), 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.channels { + static func updateColor(flags: Int32, channel: Api.InputChannel, color: Int32?, backgroundEmojiId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-659933583) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "channels.updateColor", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("color", String(describing: color)), ("backgroundEmojiId", String(describing: backgroundEmojiId))]), 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.channels { + static func updateEmojiStatus(channel: Api.InputChannel, emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-254548312) + channel.serialize(buffer, true) + emojiStatus.serialize(buffer, true) + return (FunctionDescription(name: "channels.updateEmojiStatus", parameters: [("channel", String(describing: channel)), ("emojiStatus", String(describing: emojiStatus))]), 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.channels { + static func updatePinnedForumTopic(channel: Api.InputChannel, topicId: Int32, pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1814925350) + channel.serialize(buffer, true) + serializeInt32(topicId, buffer: buffer, boxed: false) + pinned.serialize(buffer, true) + return (FunctionDescription(name: "channels.updatePinnedForumTopic", parameters: [("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("pinned", String(describing: pinned))]), 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.channels { + static func updateUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(890549214) + channel.serialize(buffer, true) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.updateUsername", parameters: [("channel", String(describing: channel)), ("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func viewSponsoredMessage(channel: Api.InputChannel, randomId: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1095836780) + channel.serialize(buffer, true) + serializeBytes(randomId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.viewSponsoredMessage", parameters: [("channel", String(describing: channel)), ("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func checkChatlistInvite(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1103171583) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "chatlists.checkChatlistInvite", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ChatlistInvite? in + let reader = BufferReader(buffer) + var result: Api.chatlists.ChatlistInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.chatlists.ChatlistInvite + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func deleteExportedInvite(chatlist: Api.InputChatlist, slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1906072670) + chatlist.serialize(buffer, true) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "chatlists.deleteExportedInvite", parameters: [("chatlist", String(describing: chatlist)), ("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func editExportedInvite(flags: Int32, chatlist: Api.InputChatlist, slug: String, title: String?, peers: [Api.InputPeer]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1698543165) + serializeInt32(flags, buffer: buffer, boxed: false) + chatlist.serialize(buffer, true) + serializeString(slug, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers!.count)) + for item in peers! { + item.serialize(buffer, true) + }} + return (FunctionDescription(name: "chatlists.editExportedInvite", parameters: [("flags", String(describing: flags)), ("chatlist", String(describing: chatlist)), ("slug", String(describing: slug)), ("title", String(describing: title)), ("peers", String(describing: peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedChatlistInvite? in + let reader = BufferReader(buffer) + var result: Api.ExportedChatlistInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedChatlistInvite + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func exportChatlistInvite(chatlist: Api.InputChatlist, title: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2072885362) + chatlist.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "chatlists.exportChatlistInvite", parameters: [("chatlist", String(describing: chatlist)), ("title", String(describing: title)), ("peers", String(describing: peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ExportedChatlistInvite? in + let reader = BufferReader(buffer) + var result: Api.chatlists.ExportedChatlistInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.chatlists.ExportedChatlistInvite + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func getChatlistUpdates(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1992190687) + chatlist.serialize(buffer, true) + return (FunctionDescription(name: "chatlists.getChatlistUpdates", parameters: [("chatlist", String(describing: chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ChatlistUpdates? in + let reader = BufferReader(buffer) + var result: Api.chatlists.ChatlistUpdates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.chatlists.ChatlistUpdates + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func getExportedInvites(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-838608253) + chatlist.serialize(buffer, true) + return (FunctionDescription(name: "chatlists.getExportedInvites", parameters: [("chatlist", String(describing: chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ExportedInvites? in + let reader = BufferReader(buffer) + var result: Api.chatlists.ExportedInvites? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.chatlists.ExportedInvites + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func getLeaveChatlistSuggestions(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Peer]>) { + let buffer = Buffer() + buffer.appendInt32(-37955820) + chatlist.serialize(buffer, true) + return (FunctionDescription(name: "chatlists.getLeaveChatlistSuggestions", parameters: [("chatlist", String(describing: chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Peer]? in + let reader = BufferReader(buffer) + var result: [Api.Peer]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func hideChatlistUpdates(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1726252795) + chatlist.serialize(buffer, true) + return (FunctionDescription(name: "chatlists.hideChatlistUpdates", parameters: [("chatlist", String(describing: chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func joinChatlistInvite(slug: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1498291302) + serializeString(slug, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "chatlists.joinChatlistInvite", parameters: [("slug", String(describing: slug)), ("peers", String(describing: peers))]), 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.chatlists { + static func joinChatlistUpdates(chatlist: Api.InputChatlist, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-527828747) + chatlist.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "chatlists.joinChatlistUpdates", parameters: [("chatlist", String(describing: chatlist)), ("peers", String(describing: peers))]), 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.chatlists { + static func leaveChatlist(chatlist: Api.InputChatlist, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1962598714) + chatlist.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "chatlists.leaveChatlist", parameters: [("chatlist", String(describing: chatlist)), ("peers", String(describing: peers))]), 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.contacts { + static func acceptContact(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-130964977) + id.serialize(buffer, true) + return (FunctionDescription(name: "contacts.acceptContact", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.contacts { + static func addContact(flags: Int32, id: Api.InputUser, firstName: String, lastName: String, phone: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-386636848) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeString(phone, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.addContact", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("phone", String(describing: phone))]), 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.contacts { + static func block(flags: Int32, id: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(774801204) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + return (FunctionDescription(name: "contacts.block", parameters: [("flags", String(describing: flags)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func blockFromReplies(flags: Int32, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(698914348) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.blockFromReplies", parameters: [("flags", String(describing: flags)), ("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.contacts { + static func deleteByPhones(phones: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(269745566) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(phones.count)) + for item in phones { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "contacts.deleteByPhones", parameters: [("phones", String(describing: phones))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func deleteContacts(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(157945344) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "contacts.deleteContacts", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.contacts { + static func editCloseFriends(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1167653392) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "contacts.editCloseFriends", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func exportContactToken() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-127582169) + + return (FunctionDescription(name: "contacts.exportContactToken", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedContactToken? in + let reader = BufferReader(buffer) + var result: Api.ExportedContactToken? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedContactToken + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getBirthdays() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-621959068) + + return (FunctionDescription(name: "contacts.getBirthdays", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ContactBirthdays? in + let reader = BufferReader(buffer) + var result: Api.contacts.ContactBirthdays? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.ContactBirthdays + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getBlocked(flags: Int32, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1702457472) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.getBlocked", parameters: [("flags", String(describing: flags)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Blocked? in + let reader = BufferReader(buffer) + var result: Api.contacts.Blocked? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.Blocked + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getContactIDs(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(2061264541) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.getContactIDs", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getContacts(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1574346258) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.getContacts", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Contacts? in + let reader = BufferReader(buffer) + var result: Api.contacts.Contacts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.Contacts + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getLocated(flags: Int32, geoPoint: Api.InputGeoPoint, selfExpires: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-750207932) + serializeInt32(flags, buffer: buffer, boxed: false) + geoPoint.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(selfExpires!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "contacts.getLocated", parameters: [("flags", String(describing: flags)), ("geoPoint", String(describing: geoPoint)), ("selfExpires", String(describing: selfExpires))]), 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.contacts { + static func getSaved() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SavedContact]>) { + let buffer = Buffer() + buffer.appendInt32(-2098076769) + + return (FunctionDescription(name: "contacts.getSaved", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SavedContact]? in + let reader = BufferReader(buffer) + var result: [Api.SavedContact]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedContact.self) + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getStatuses() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ContactStatus]>) { + let buffer = Buffer() + buffer.appendInt32(-995929106) + + return (FunctionDescription(name: "contacts.getStatuses", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ContactStatus]? in + let reader = BufferReader(buffer) + var result: [Api.ContactStatus]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ContactStatus.self) + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getTopPeers(flags: Int32, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1758168906) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.getTopPeers", parameters: [("flags", String(describing: flags)), ("offset", String(describing: offset)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.TopPeers? in + let reader = BufferReader(buffer) + var result: Api.contacts.TopPeers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.TopPeers + } + return result + }) + } +} +public extension Api.functions.contacts { + static func importContactToken(token: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(318789512) + serializeString(token, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.importContactToken", parameters: [("token", String(describing: token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.contacts { + static func importContacts(contacts: [Api.InputContact]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(746589157) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(contacts.count)) + for item in contacts { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "contacts.importContacts", parameters: [("contacts", String(describing: contacts))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ImportedContacts? in + let reader = BufferReader(buffer) + var result: Api.contacts.ImportedContacts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.ImportedContacts + } + return result + }) + } +} +public extension Api.functions.contacts { + static func resetSaved() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2020263951) + + return (FunctionDescription(name: "contacts.resetSaved", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func resetTopPeerRating(category: Api.TopPeerCategory, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(451113900) + category.serialize(buffer, true) + peer.serialize(buffer, true) + return (FunctionDescription(name: "contacts.resetTopPeerRating", parameters: [("category", String(describing: category)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func resolvePhone(phone: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1963375804) + serializeString(phone, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.resolvePhone", parameters: [("phone", String(describing: phone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ResolvedPeer? in + let reader = BufferReader(buffer) + var result: Api.contacts.ResolvedPeer? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.ResolvedPeer + } + return result + }) + } +} +public extension Api.functions.contacts { + static func resolveUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-113456221) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.resolveUsername", parameters: [("username", String(describing: username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ResolvedPeer? in + let reader = BufferReader(buffer) + var result: Api.contacts.ResolvedPeer? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.ResolvedPeer + } + return result + }) + } +} +public extension Api.functions.contacts { + static func search(q: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(301470424) + serializeString(q, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.search", parameters: [("q", String(describing: q)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Found? in + let reader = BufferReader(buffer) + var result: Api.contacts.Found? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.Found + } + return result + }) + } +} +public extension Api.functions.contacts { + static func setBlocked(flags: Int32, id: [Api.InputPeer], limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1798939530) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.setBlocked", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func toggleTopPeers(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2062238246) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "contacts.toggleTopPeers", parameters: [("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func unblock(flags: Int32, id: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1252994264) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + return (FunctionDescription(name: "contacts.unblock", parameters: [("flags", String(describing: flags)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.folders { + static func editPeerFolders(folderPeers: [Api.InputFolderPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1749536939) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(folderPeers.count)) + for item in folderPeers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "folders.editPeerFolders", parameters: [("folderPeers", String(describing: folderPeers))]), 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.fragment { + static func getCollectibleInfo(collectible: Api.InputCollectible) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1105295942) + collectible.serialize(buffer, true) + return (FunctionDescription(name: "fragment.getCollectibleInfo", parameters: [("collectible", String(describing: collectible))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.fragment.CollectibleInfo? in + let reader = BufferReader(buffer) + var result: Api.fragment.CollectibleInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.fragment.CollectibleInfo + } + return result + }) + } +} +public extension Api.functions.help { + static func acceptTermsOfService(id: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-294455398) + id.serialize(buffer, true) + return (FunctionDescription(name: "help.acceptTermsOfService", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.help { + static func dismissSuggestion(peer: Api.InputPeer, suggestion: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-183649631) + peer.serialize(buffer, true) + serializeString(suggestion, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.dismissSuggestion", parameters: [("peer", String(describing: peer)), ("suggestion", String(describing: suggestion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.help { + static func editUserInfo(userId: Api.InputUser, message: String, entities: [Api.MessageEntity]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1723407216) + userId.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities.count)) + for item in entities { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "help.editUserInfo", parameters: [("userId", String(describing: userId)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.UserInfo? in + let reader = BufferReader(buffer) + var result: Api.help.UserInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.UserInfo + } + return result + }) + } +} +public extension Api.functions.help { + static func getAppConfig(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1642330196) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getAppConfig", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.AppConfig? in + let reader = BufferReader(buffer) + var result: Api.help.AppConfig? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.AppConfig + } + return result + }) + } +} +public extension Api.functions.help { + static func getAppUpdate(source: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1378703997) + serializeString(source, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getAppUpdate", parameters: [("source", String(describing: source))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.AppUpdate? in + let reader = BufferReader(buffer) + var result: Api.help.AppUpdate? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.AppUpdate + } + return result + }) + } +} +public extension Api.functions.help { + static func getCdnConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1375900482) + + return (FunctionDescription(name: "help.getCdnConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.CdnConfig? in + let reader = BufferReader(buffer) + var result: Api.CdnConfig? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.CdnConfig + } + return result + }) + } +} +public extension Api.functions.help { + static func getConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-990308245) + + return (FunctionDescription(name: "help.getConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Config? in + let reader = BufferReader(buffer) + var result: Api.Config? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Config + } + return result + }) + } +} +public extension Api.functions.help { + static func getCountriesList(langCode: String, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1935116200) + serializeString(langCode, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getCountriesList", parameters: [("langCode", String(describing: langCode)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.CountriesList? in + let reader = BufferReader(buffer) + var result: Api.help.CountriesList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.CountriesList + } + return result + }) + } +} +public extension Api.functions.help { + static func getDeepLinkInfo(path: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1072547679) + serializeString(path, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getDeepLinkInfo", parameters: [("path", String(describing: path))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.DeepLinkInfo? in + let reader = BufferReader(buffer) + var result: Api.help.DeepLinkInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.DeepLinkInfo + } + return result + }) + } +} +public extension Api.functions.help { + static func getInviteText() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1295590211) + + return (FunctionDescription(name: "help.getInviteText", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.InviteText? in + let reader = BufferReader(buffer) + var result: Api.help.InviteText? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.InviteText + } + return result + }) + } +} +public extension Api.functions.help { + static func getNearestDc() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(531836966) + + return (FunctionDescription(name: "help.getNearestDc", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.NearestDc? in + let reader = BufferReader(buffer) + var result: Api.NearestDc? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.NearestDc + } + return result + }) + } +} +public extension Api.functions.help { + static func getPassportConfig(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-966677240) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getPassportConfig", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PassportConfig? in + let reader = BufferReader(buffer) + var result: Api.help.PassportConfig? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PassportConfig + } + return result + }) + } +} +public extension Api.functions.help { + static func getPeerColors(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-629083089) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getPeerColors", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PeerColors? in + let reader = BufferReader(buffer) + var result: Api.help.PeerColors? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PeerColors + } + return result + }) + } +} +public extension Api.functions.help { + static func getPeerProfileColors(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1412453891) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getPeerProfileColors", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PeerColors? in + let reader = BufferReader(buffer) + var result: Api.help.PeerColors? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PeerColors + } + return result + }) + } +} +public extension Api.functions.help { + static func getPremiumPromo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1206152236) + + return (FunctionDescription(name: "help.getPremiumPromo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PremiumPromo? in + let reader = BufferReader(buffer) + var result: Api.help.PremiumPromo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PremiumPromo + } + return result + }) + } +} +public extension Api.functions.help { + static func getPromoData() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1063816159) + + return (FunctionDescription(name: "help.getPromoData", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PromoData? in + let reader = BufferReader(buffer) + var result: Api.help.PromoData? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PromoData + } + return result + }) + } +} +public extension Api.functions.help { + static func getRecentMeUrls(referer: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1036054804) + serializeString(referer, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getRecentMeUrls", parameters: [("referer", String(describing: referer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.RecentMeUrls? in + let reader = BufferReader(buffer) + var result: Api.help.RecentMeUrls? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.RecentMeUrls + } + return result + }) + } +} +public extension Api.functions.help { + static func getSupport() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1663104819) + + return (FunctionDescription(name: "help.getSupport", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.Support? in + let reader = BufferReader(buffer) + var result: Api.help.Support? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.Support + } + return result + }) + } +} +public extension Api.functions.help { + static func getSupportName() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-748624084) + + return (FunctionDescription(name: "help.getSupportName", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.SupportName? in + let reader = BufferReader(buffer) + var result: Api.help.SupportName? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.SupportName + } + return result + }) + } +} +public extension Api.functions.help { + static func getTermsOfServiceUpdate() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(749019089) + + return (FunctionDescription(name: "help.getTermsOfServiceUpdate", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.TermsOfServiceUpdate? in + let reader = BufferReader(buffer) + var result: Api.help.TermsOfServiceUpdate? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.TermsOfServiceUpdate + } + return result + }) + } +} +public extension Api.functions.help { + static func getTimezonesList(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1236468288) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getTimezonesList", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.TimezonesList? in + let reader = BufferReader(buffer) + var result: Api.help.TimezonesList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.TimezonesList + } + return result + }) + } +} +public extension Api.functions.help { + static func getUserInfo(userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(59377875) + userId.serialize(buffer, true) + return (FunctionDescription(name: "help.getUserInfo", parameters: [("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.UserInfo? in + let reader = BufferReader(buffer) + var result: Api.help.UserInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.UserInfo + } + return result + }) + } +} +public extension Api.functions.help { + static func hidePromoData(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(505748629) + peer.serialize(buffer, true) + return (FunctionDescription(name: "help.hidePromoData", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.help { + static func saveAppLog(events: [Api.InputAppEvent]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1862465352) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(events.count)) + for item in events { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "help.saveAppLog", parameters: [("events", String(describing: events))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.help { + static func setBotUpdatesStatus(pendingUpdatesCount: Int32, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-333262899) + serializeInt32(pendingUpdatesCount, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.setBotUpdatesStatus", parameters: [("pendingUpdatesCount", String(describing: pendingUpdatesCount)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.help { + static func test() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1058929929) + + return (FunctionDescription(name: "help.test", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.langpack { + static func getDifference(langPack: String, langCode: String, fromVersion: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-845657435) + serializeString(langPack, buffer: buffer, boxed: false) + serializeString(langCode, buffer: buffer, boxed: false) + serializeInt32(fromVersion, buffer: buffer, boxed: false) + return (FunctionDescription(name: "langpack.getDifference", parameters: [("langPack", String(describing: langPack)), ("langCode", String(describing: langCode)), ("fromVersion", String(describing: fromVersion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackDifference? in + let reader = BufferReader(buffer) + var result: Api.LangPackDifference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.LangPackDifference + } + return result + }) + } +} +public extension Api.functions.langpack { + static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-219008246) + serializeString(langPack, buffer: buffer, boxed: false) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "langpack.getLangPack", parameters: [("langPack", String(describing: langPack)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackDifference? in + let reader = BufferReader(buffer) + var result: Api.LangPackDifference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.LangPackDifference + } + return result + }) + } +} +public extension Api.functions.langpack { + static func getLanguage(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1784243458) + serializeString(langPack, buffer: buffer, boxed: false) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "langpack.getLanguage", parameters: [("langPack", String(describing: langPack)), ("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackLanguage? in + let reader = BufferReader(buffer) + var result: Api.LangPackLanguage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.LangPackLanguage + } + return result + }) + } +} +public extension Api.functions.langpack { + static func getLanguages(langPack: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.LangPackLanguage]>) { + let buffer = Buffer() + buffer.appendInt32(1120311183) + serializeString(langPack, buffer: buffer, boxed: false) + return (FunctionDescription(name: "langpack.getLanguages", parameters: [("langPack", String(describing: langPack))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.LangPackLanguage]? in + let reader = BufferReader(buffer) + var result: [Api.LangPackLanguage]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackLanguage.self) + } + return result + }) + } +} +public extension Api.functions.langpack { + static func getStrings(langPack: String, langCode: String, keys: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.LangPackString]>) { + let buffer = Buffer() + buffer.appendInt32(-269862909) + serializeString(langPack, buffer: buffer, boxed: false) + serializeString(langCode, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(keys.count)) + for item in keys { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "langpack.getStrings", parameters: [("langPack", String(describing: langPack)), ("langCode", String(describing: langCode)), ("keys", String(describing: keys))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.LangPackString]? in + let reader = BufferReader(buffer) + var result: [Api.LangPackString]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func acceptEncryption(peer: Api.InputEncryptedChat, gB: Buffer, keyFingerprint: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1035731989) + peer.serialize(buffer, true) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.acceptEncryption", parameters: [("peer", String(describing: peer)), ("gB", String(describing: gB)), ("keyFingerprint", String(describing: keyFingerprint))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedChat? in + let reader = BufferReader(buffer) + var result: Api.EncryptedChat? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EncryptedChat + } + return result + }) + } +} +public extension Api.functions.messages { + static func acceptUrlAuth(flags: Int32, peer: Api.InputPeer?, msgId: Int32?, buttonId: Int32?, url: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1322487515) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {peer!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(msgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(buttonId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.acceptUrlAuth", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("buttonId", String(describing: buttonId)), ("url", String(describing: url))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.UrlAuthResult? in + let reader = BufferReader(buffer) + var result: Api.UrlAuthResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.UrlAuthResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func addChatUser(chatId: Int64, userId: Api.InputUser, fwdLimit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-876162809) + serializeInt64(chatId, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + serializeInt32(fwdLimit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.addChatUser", parameters: [("chatId", String(describing: chatId)), ("userId", String(describing: userId)), ("fwdLimit", String(describing: fwdLimit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in + let reader = BufferReader(buffer) + var result: Api.messages.InvitedUsers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers + } + return result + }) + } +} +public extension Api.functions.messages { + static func checkChatInvite(hash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1051570619) + serializeString(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.checkChatInvite", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ChatInvite? in + let reader = BufferReader(buffer) + var result: Api.ChatInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ChatInvite + } + return result + }) + } +} +public extension Api.functions.messages { + static func checkHistoryImport(importHead: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1140726259) + serializeString(importHead, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.checkHistoryImport", parameters: [("importHead", String(describing: importHead))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImportParsed? in + let reader = BufferReader(buffer) + var result: Api.messages.HistoryImportParsed? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImportParsed + } + return result + }) + } +} +public extension Api.functions.messages { + static func checkHistoryImportPeer(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1573261059) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.checkHistoryImportPeer", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.CheckedHistoryImportPeer? in + let reader = BufferReader(buffer) + var result: Api.messages.CheckedHistoryImportPeer? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.CheckedHistoryImportPeer + } + return result + }) + } +} +public extension Api.functions.messages { + static func checkQuickReplyShortcut(shortcut: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-237962285) + serializeString(shortcut, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.checkQuickReplyShortcut", parameters: [("shortcut", String(describing: shortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func clearAllDrafts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2119757468) + + return (FunctionDescription(name: "messages.clearAllDrafts", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func clearRecentReactions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1644236876) + + return (FunctionDescription(name: "messages.clearRecentReactions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func clearRecentStickers(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1986437075) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.clearRecentStickers", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func createChat(flags: Int32, users: [Api.InputUser], title: String, ttlPeriod: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1831936556) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.createChat", parameters: [("flags", String(describing: flags)), ("users", String(describing: users)), ("title", String(describing: title)), ("ttlPeriod", String(describing: ttlPeriod))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in + let reader = BufferReader(buffer) + var result: Api.messages.InvitedUsers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1540419152) + serializeInt64(chatId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deleteChat", parameters: [("chatId", String(describing: chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteChatUser(flags: Int32, chatId: Int64, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1575461717) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(chatId, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + return (FunctionDescription(name: "messages.deleteChatUser", parameters: [("flags", String(describing: flags)), ("chatId", String(describing: chatId)), ("userId", String(describing: userId))]), 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 deleteExportedChatInvite(peer: Api.InputPeer, link: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-731601877) + peer.serialize(buffer, true) + serializeString(link, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deleteExportedChatInvite", parameters: [("peer", String(describing: peer)), ("link", String(describing: link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +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() + buffer.appendInt32(-1332768214) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(minDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(maxDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.deleteHistory", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("maxId", String(describing: maxId)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteMessages(flags: Int32, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-443640366) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.deleteMessages", parameters: [("flags", String(describing: flags)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages + } + return result + }) + } +} +public extension Api.functions.messages { + static func deletePhoneCallHistory(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-104078327) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deletePhoneCallHistory", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedFoundMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedFoundMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedFoundMessages + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteQuickReplyMessages(shortcutId: Int32, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-519706352) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.deleteQuickReplyMessages", parameters: [("shortcutId", String(describing: shortcutId)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteQuickReplyShortcut(shortcutId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1019234112) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deleteQuickReplyShortcut", parameters: [("shortcutId", String(describing: shortcutId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteRevokedExportedChatInvites(peer: Api.InputPeer, adminId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1452833749) + peer.serialize(buffer, true) + adminId.serialize(buffer, true) + return (FunctionDescription(name: "messages.deleteRevokedExportedChatInvites", parameters: [("peer", String(describing: peer)), ("adminId", String(describing: adminId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteSavedHistory(flags: Int32, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1855459371) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(minDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(maxDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.deleteSavedHistory", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("maxId", String(describing: maxId)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1504586518) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.deleteScheduledMessages", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func discardEncryption(flags: Int32, chatId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-208425312) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.discardEncryption", parameters: [("flags", String(describing: flags)), ("chatId", String(describing: chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func editChatAbout(peer: Api.InputPeer, about: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-554301545) + peer.serialize(buffer, true) + serializeString(about, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.editChatAbout", parameters: [("peer", String(describing: peer)), ("about", String(describing: about))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func editChatAdmin(chatId: Int64, userId: Api.InputUser, isAdmin: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1470377534) + serializeInt64(chatId, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + isAdmin.serialize(buffer, true) + return (FunctionDescription(name: "messages.editChatAdmin", parameters: [("chatId", String(describing: chatId)), ("userId", String(describing: userId)), ("isAdmin", String(describing: isAdmin))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func editChatDefaultBannedRights(peer: Api.InputPeer, bannedRights: Api.ChatBannedRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1517917375) + peer.serialize(buffer, true) + bannedRights.serialize(buffer, true) + return (FunctionDescription(name: "messages.editChatDefaultBannedRights", parameters: [("peer", String(describing: peer)), ("bannedRights", String(describing: bannedRights))]), 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 editChatPhoto(chatId: Int64, photo: Api.InputChatPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(903730804) + serializeInt64(chatId, buffer: buffer, boxed: false) + photo.serialize(buffer, true) + return (FunctionDescription(name: "messages.editChatPhoto", parameters: [("chatId", String(describing: chatId)), ("photo", String(describing: photo))]), 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 editChatTitle(chatId: Int64, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1937260541) + serializeInt64(chatId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.editChatTitle", parameters: [("chatId", String(describing: chatId)), ("title", String(describing: title))]), 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 editExportedChatInvite(flags: Int32, peer: Api.InputPeer, link: String, expireDate: Int32?, usageLimit: Int32?, requestNeeded: Api.Bool?, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1110823051) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(link, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(expireDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(usageLimit!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {requestNeeded!.serialize(buffer, true)} + if Int(flags) & Int(1 << 4) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.editExportedChatInvite", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("link", String(describing: link)), ("expireDate", String(describing: expireDate)), ("usageLimit", String(describing: usageLimit)), ("requestNeeded", String(describing: requestNeeded)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvite? in + let reader = BufferReader(buffer) + var result: Api.messages.ExportedChatInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvite + } + return result + }) + } +} +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() + buffer.appendInt32(-2091549254) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + return (FunctionDescription(name: "messages.editInlineBotMessage", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("message", String(describing: message)), ("media", String(describing: media)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, quickReplyShortcutId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-539934715) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 17) != 0 {serializeInt32(quickReplyShortcutId!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("message", String(describing: message)), ("media", String(describing: media)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("quickReplyShortcutId", String(describing: quickReplyShortcutId))]), 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 editQuickReplyShortcut(shortcutId: Int32, shortcut: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1543519471) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + serializeString(shortcut, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.editQuickReplyShortcut", parameters: [("shortcutId", String(describing: shortcutId)), ("shortcut", String(describing: shortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func exportChatInvite(flags: Int32, peer: Api.InputPeer, expireDate: Int32?, usageLimit: Int32?, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1607670315) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(expireDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(usageLimit!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.exportChatInvite", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("expireDate", String(describing: expireDate)), ("usageLimit", String(describing: usageLimit)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedChatInvite? in + let reader = BufferReader(buffer) + var result: Api.ExportedChatInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } + return result + }) + } +} +public extension Api.functions.messages { + static func faveSticker(id: Api.InputDocument, unfave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1174420133) + id.serialize(buffer, true) + unfave.serialize(buffer, true) + return (FunctionDescription(name: "messages.faveSticker", parameters: [("id", String(describing: id)), ("unfave", String(describing: unfave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-721186296) + serializeInt32(flags, buffer: buffer, boxed: false) + fromPeer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomId.count)) + for item in randomId { + serializeInt64(item, buffer: buffer, boxed: false) + } + toPeer.serialize(buffer, true) + if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} + if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", String(describing: flags)), ("fromPeer", String(describing: fromPeer)), ("id", String(describing: id)), ("randomId", String(describing: randomId)), ("toPeer", String(describing: toPeer)), ("topMsgId", String(describing: topMsgId)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), 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 getAdminsWithInvites(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(958457583) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.getAdminsWithInvites", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatAdminsWithInvites? in + let reader = BufferReader(buffer) + var result: Api.messages.ChatAdminsWithInvites? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ChatAdminsWithInvites + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAllDrafts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1782549861) + + return (FunctionDescription(name: "messages.getAllDrafts", parameters: []), 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 getAllStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1197432408) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getAllStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.AllStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getArchivedStickers(flags: Int32, offsetId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1475442322) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getArchivedStickers", parameters: [("flags", String(describing: flags)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ArchivedStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.ArchivedStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ArchivedStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAttachMenuBot(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1998676370) + bot.serialize(buffer, true) + return (FunctionDescription(name: "messages.getAttachMenuBot", parameters: [("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AttachMenuBotsBot? in + let reader = BufferReader(buffer) + var result: Api.AttachMenuBotsBot? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.AttachMenuBotsBot + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAttachMenuBots(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(385663691) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getAttachMenuBots", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AttachMenuBots? in + let reader = BufferReader(buffer) + var result: Api.AttachMenuBots? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.AttachMenuBots + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAttachedStickers(media: Api.InputStickeredMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StickerSetCovered]>) { + let buffer = Buffer() + buffer.appendInt32(-866424884) + media.serialize(buffer, true) + return (FunctionDescription(name: "messages.getAttachedStickers", parameters: [("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StickerSetCovered]? in + let reader = BufferReader(buffer) + var result: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAvailableEffects(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-559805895) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getAvailableEffects", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AvailableEffects? in + let reader = BufferReader(buffer) + var result: Api.messages.AvailableEffects? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AvailableEffects + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAvailableReactions(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(417243308) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getAvailableReactions", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AvailableReactions? in + let reader = BufferReader(buffer) + var result: Api.messages.AvailableReactions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AvailableReactions + } + return result + }) + } +} +public extension Api.functions.messages { + static func getBotApp(app: Api.InputBotApp, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(889046467) + app.serialize(buffer, true) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getBotApp", parameters: [("app", String(describing: app)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotApp? in + let reader = BufferReader(buffer) + var result: Api.messages.BotApp? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.BotApp + } + return result + }) + } +} +public extension Api.functions.messages { + static func getBotCallbackAnswer(flags: Int32, peer: Api.InputPeer, msgId: Int32, data: Buffer?, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1824339449) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {password!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.getBotCallbackAnswer", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("data", String(describing: data)), ("password", String(describing: password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotCallbackAnswer? in + let reader = BufferReader(buffer) + var result: Api.messages.BotCallbackAnswer? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.BotCallbackAnswer + } + return result + }) + } +} +public extension Api.functions.messages { + static func getChatInviteImporters(flags: Int32, peer: Api.InputPeer, link: String?, q: String?, offsetDate: Int32, offsetUser: Api.InputUser, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-553329330) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {serializeString(link!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(q!, buffer: buffer, boxed: false)} + serializeInt32(offsetDate, buffer: buffer, boxed: false) + offsetUser.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getChatInviteImporters", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("link", String(describing: link)), ("q", String(describing: q)), ("offsetDate", String(describing: offsetDate)), ("offsetUser", String(describing: offsetUser)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteImporters? in + let reader = BufferReader(buffer) + var result: Api.messages.ChatInviteImporters? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ChatInviteImporters + } + return result + }) + } +} +public extension Api.functions.messages { + static func getChats(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1240027791) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getChats", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.messages { + static func getCommonChats(userId: Api.InputUser, maxId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-468934396) + userId.serialize(buffer, true) + serializeInt64(maxId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getCommonChats", parameters: [("userId", String(describing: userId)), ("maxId", String(describing: maxId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.messages { + static func getCustomEmojiDocuments(documentId: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Document]>) { + let buffer = Buffer() + buffer.appendInt32(-643100844) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(documentId.count)) + for item in documentId { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getCustomEmojiDocuments", parameters: [("documentId", String(describing: documentId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Document]? in + let reader = BufferReader(buffer) + var result: [Api.Document]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDefaultHistoryTTL() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1703637384) + + return (FunctionDescription(name: "messages.getDefaultHistoryTTL", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DefaultHistoryTTL? in + let reader = BufferReader(buffer) + var result: Api.DefaultHistoryTTL? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.DefaultHistoryTTL + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDefaultTagReactions(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1107741656) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDefaultTagReactions", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in + let reader = BufferReader(buffer) + var result: Api.messages.Reactions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Reactions + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDhConfig(version: Int32, randomLength: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(651135312) + serializeInt32(version, buffer: buffer, boxed: false) + serializeInt32(randomLength, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDhConfig", parameters: [("version", String(describing: version)), ("randomLength", String(describing: randomLength))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DhConfig? in + let reader = BufferReader(buffer) + var result: Api.messages.DhConfig? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.DhConfig + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-271283063) + + return (FunctionDescription(name: "messages.getDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DialogFilters? in + let reader = BufferReader(buffer) + var result: Api.messages.DialogFilters? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.DialogFilters + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDialogUnreadMarks() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogPeer]>) { + let buffer = Buffer() + buffer.appendInt32(585256482) + + return (FunctionDescription(name: "messages.getDialogUnreadMarks", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogPeer]? in + let reader = BufferReader(buffer) + var result: [Api.DialogPeer]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDialogs(flags: Int32, folderId: Int32?, offsetDate: Int32, offsetId: Int32, offsetPeer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1594569905) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDialogs", parameters: [("flags", String(describing: flags)), ("folderId", String(describing: folderId)), ("offsetDate", String(describing: offsetDate)), ("offsetId", String(describing: offsetId)), ("offsetPeer", String(describing: offsetPeer)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Dialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.Dialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Dialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1147761405) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDiscussionMessage", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DiscussionMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.DiscussionMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.DiscussionMessage + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDocumentByHash(sha256: Buffer, size: Int64, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1309538785) + serializeBytes(sha256, buffer: buffer, boxed: false) + serializeInt64(size, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDocumentByHash", parameters: [("sha256", String(describing: sha256)), ("size", String(describing: size)), ("mimeType", String(describing: mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in + let reader = BufferReader(buffer) + var result: Api.Document? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Document + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1955122779) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiGroups", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in + let reader = BufferReader(buffer) + var result: Api.messages.EmojiGroups? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiKeywords(langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(899735650) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiKeywords", parameters: [("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiKeywordsDifference? in + let reader = BufferReader(buffer) + var result: Api.EmojiKeywordsDifference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiKeywordsDifference + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiKeywordsDifference(langCode: String, fromVersion: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(352892591) + serializeString(langCode, buffer: buffer, boxed: false) + serializeInt32(fromVersion, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiKeywordsDifference", parameters: [("langCode", String(describing: langCode)), ("fromVersion", String(describing: fromVersion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiKeywordsDifference? in + let reader = BufferReader(buffer) + var result: Api.EmojiKeywordsDifference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiKeywordsDifference + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiKeywordsLanguages(langCodes: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.EmojiLanguage]>) { + let buffer = Buffer() + buffer.appendInt32(1318675378) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(langCodes.count)) + for item in langCodes { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getEmojiKeywordsLanguages", parameters: [("langCodes", String(describing: langCodes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.EmojiLanguage]? in + let reader = BufferReader(buffer) + var result: [Api.EmojiLanguage]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiLanguage.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiProfilePhotoGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(564480243) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiProfilePhotoGroups", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in + let reader = BufferReader(buffer) + var result: Api.messages.EmojiGroups? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiStatusGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(785209037) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiStatusGroups", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in + let reader = BufferReader(buffer) + var result: Api.messages.EmojiGroups? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiStickerGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(500711669) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiStickerGroups", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in + let reader = BufferReader(buffer) + var result: Api.messages.EmojiGroups? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-67329649) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.AllStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiURL(langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-709817306) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiURL", parameters: [("langCode", String(describing: langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiURL? in + let reader = BufferReader(buffer) + var result: Api.EmojiURL? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiURL + } + return result + }) + } +} +public extension Api.functions.messages { + static func getExportedChatInvite(peer: Api.InputPeer, link: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1937010524) + peer.serialize(buffer, true) + serializeString(link, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getExportedChatInvite", parameters: [("peer", String(describing: peer)), ("link", String(describing: link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvite? in + let reader = BufferReader(buffer) + var result: Api.messages.ExportedChatInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvite + } + return result + }) + } +} +public extension Api.functions.messages { + static func getExportedChatInvites(flags: Int32, peer: Api.InputPeer, adminId: Api.InputUser, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1565154314) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + adminId.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(offsetLink!, buffer: buffer, boxed: false)} + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getExportedChatInvites", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("adminId", String(describing: adminId)), ("offsetDate", String(describing: offsetDate)), ("offsetLink", String(describing: offsetLink)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvites? in + let reader = BufferReader(buffer) + var result: Api.messages.ExportedChatInvites? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvites + } + return result + }) + } +} +public extension Api.functions.messages { + static func getExtendedMedia(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2064119788) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getExtendedMedia", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func 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() + buffer.appendInt32(82946729) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getFavedStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FavedStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.FavedStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FavedStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getFeaturedEmojiStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(248473398) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getFeaturedEmojiStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.FeaturedStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getFeaturedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1685588756) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getFeaturedStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.FeaturedStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getFullChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1364194508) + serializeInt64(chatId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getFullChat", parameters: [("chatId", String(describing: chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatFull? in + let reader = BufferReader(buffer) + var result: Api.messages.ChatFull? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ChatFull + } + return result + }) + } +} +public extension Api.functions.messages { + static func getGameHighScores(peer: Api.InputPeer, id: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-400399203) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + return (FunctionDescription(name: "messages.getGameHighScores", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HighScores? in + let reader = BufferReader(buffer) + var result: Api.messages.HighScores? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.HighScores + } + return result + }) + } +} +public extension Api.functions.messages { + static func getHistory(peer: Api.InputPeer, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1143203525) + peer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getHistory", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("offsetDate", String(describing: offsetDate)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getInlineBotResults(flags: Int32, bot: Api.InputUser, peer: Api.InputPeer, geoPoint: Api.InputGeoPoint?, query: String, offset: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1364105629) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {geoPoint!.serialize(buffer, true)} + serializeString(query, buffer: buffer, boxed: false) + serializeString(offset, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getInlineBotResults", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("peer", String(describing: peer)), ("geoPoint", String(describing: geoPoint)), ("query", String(describing: query)), ("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotResults? in + let reader = BufferReader(buffer) + var result: Api.messages.BotResults? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.BotResults + } + return result + }) + } +} +public extension Api.functions.messages { + static func getInlineGameHighScores(id: Api.InputBotInlineMessageID, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(258170395) + id.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "messages.getInlineGameHighScores", parameters: [("id", String(describing: id)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HighScores? in + let reader = BufferReader(buffer) + var result: Api.messages.HighScores? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.HighScores + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMaskStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1678738104) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMaskStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.AllStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessageEditData(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-39416522) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMessageEditData", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageEditData? in + let reader = BufferReader(buffer) + var result: Api.messages.MessageEditData? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MessageEditData + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessageReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: Api.Reaction?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1176190792) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {reaction!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(offset!, buffer: buffer, boxed: false)} + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMessageReactionsList", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("reaction", String(describing: reaction)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageReactionsList? in + let reader = BufferReader(buffer) + var result: Api.messages.MessageReactionsList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MessageReactionsList + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessageReadParticipants(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ReadParticipantDate]>) { + let buffer = Buffer() + buffer.appendInt32(834782287) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMessageReadParticipants", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ReadParticipantDate]? in + let reader = BufferReader(buffer) + var result: [Api.ReadParticipantDate]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReadParticipantDate.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessages(id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1673946374) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.getMessages", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessagesReactions(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1950707482) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getMessagesReactions", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1468322785) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + increment.serialize(buffer, true) + return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("increment", String(describing: increment))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageViews? in + let reader = BufferReader(buffer) + var result: Api.messages.MessageViews? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MessageViews + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMyStickers(offsetId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-793386500) + serializeInt64(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMyStickers", parameters: [("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MyStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.MyStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MyStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getOldFeaturedStickers(offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2127598753) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getOldFeaturedStickers", parameters: [("offset", String(describing: offset)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.FeaturedStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getOnlines(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1848369232) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.getOnlines", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ChatOnlines? in + let reader = BufferReader(buffer) + var result: Api.ChatOnlines? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ChatOnlines + } + return result + }) + } +} +public extension Api.functions.messages { + static func getOutboxReadDate(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1941176739) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getOutboxReadDate", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.OutboxReadDate? in + let reader = BufferReader(buffer) + var result: Api.OutboxReadDate? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.OutboxReadDate + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPeerDialogs(peers: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-462373635) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.getPeerDialogs", parameters: [("peers", String(describing: peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerDialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.PeerDialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.PeerDialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPeerSettings(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-270948702) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.getPeerSettings", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerSettings? in + let reader = BufferReader(buffer) + var result: Api.messages.PeerSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.PeerSettings + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPinnedDialogs(folderId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-692498958) + serializeInt32(folderId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getPinnedDialogs", parameters: [("folderId", String(describing: folderId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerDialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.PeerDialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.PeerDialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPinnedSavedDialogs() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-700607264) + + return (FunctionDescription(name: "messages.getPinnedSavedDialogs", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedDialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.SavedDialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SavedDialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPollResults(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1941660731) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getPollResults", 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 getPollVotes(flags: Int32, peer: Api.InputPeer, id: Int32, option: Buffer?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1200736242) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeBytes(option!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(offset!, buffer: buffer, boxed: false)} + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getPollVotes", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("option", String(describing: option)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.VotesList? in + let reader = BufferReader(buffer) + var result: Api.messages.VotesList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.VotesList + } + return result + }) + } +} +public extension Api.functions.messages { + static func getQuickReplies(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-729550168) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getQuickReplies", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.QuickReplies? in + let reader = BufferReader(buffer) + var result: Api.messages.QuickReplies? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.QuickReplies + } + return result + }) + } +} +public extension Api.functions.messages { + static func getQuickReplyMessages(flags: Int32, shortcutId: Int32, id: [Int32]?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1801153085) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id!.count)) + for item in id! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getQuickReplyMessages", parameters: [("flags", String(describing: flags)), ("shortcutId", String(describing: shortcutId)), ("id", String(describing: id)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getRecentLocations(peer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1881817312) + peer.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getRecentLocations", parameters: [("peer", String(describing: peer)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getRecentReactions(limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(960896434) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getRecentReactions", parameters: [("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in + let reader = BufferReader(buffer) + var result: Api.messages.Reactions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Reactions + } + return result + }) + } +} +public extension Api.functions.messages { + static func getRecentStickers(flags: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1649852357) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getRecentStickers", parameters: [("flags", String(describing: flags)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.RecentStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.RecentStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.RecentStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getReplies(peer: Api.InputPeer, msgId: Int32, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(584962828) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getReplies", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("offsetId", String(describing: offsetId)), ("offsetDate", String(describing: offsetDate)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSavedDialogs(flags: Int32, offsetDate: Int32, offsetId: Int32, offsetPeer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1401016858) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSavedDialogs", parameters: [("flags", String(describing: flags)), ("offsetDate", String(describing: offsetDate)), ("offsetId", String(describing: offsetId)), ("offsetPeer", String(describing: offsetPeer)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedDialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.SavedDialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SavedDialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSavedGifs(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1559270965) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSavedGifs", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedGifs? in + let reader = BufferReader(buffer) + var result: Api.messages.SavedGifs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SavedGifs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSavedHistory(peer: Api.InputPeer, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1033519437) + peer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSavedHistory", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("offsetDate", String(describing: offsetDate)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSavedReactionTags(flags: Int32, peer: Api.InputPeer?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(909631579) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSavedReactionTags", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedReactionTags? in + let reader = BufferReader(buffer) + var result: Api.messages.SavedReactionTags? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SavedReactionTags + } + return result + }) + } +} +public extension Api.functions.messages { + static func getScheduledHistory(peer: Api.InputPeer, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-183077365) + peer.serialize(buffer, true) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getScheduledHistory", parameters: [("peer", String(describing: peer)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1111817116) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getScheduledMessages", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSearchCounters(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, topMsgId: Int32?, filters: [Api.MessagesFilter]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.messages.SearchCounter]>) { + let buffer = Buffer() + buffer.appendInt32(465367808) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 {savedPeerId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(filters.count)) + for item in filters { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.getSearchCounters", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("savedPeerId", String(describing: savedPeerId)), ("topMsgId", String(describing: topMsgId)), ("filters", String(describing: filters))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.messages.SearchCounter]? in + let reader = BufferReader(buffer) + var result: [Api.messages.SearchCounter]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.messages.SearchCounter.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSearchResultsCalendar(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, filter: Api.MessagesFilter, offsetId: Int32, offsetDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1789130429) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 {savedPeerId!.serialize(buffer, true)} + filter.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSearchResultsCalendar", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("savedPeerId", String(describing: savedPeerId)), ("filter", String(describing: filter)), ("offsetId", String(describing: offsetId)), ("offsetDate", String(describing: offsetDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsCalendar? in + let reader = BufferReader(buffer) + var result: Api.messages.SearchResultsCalendar? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsCalendar + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSearchResultsPositions(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, filter: Api.MessagesFilter, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1669386480) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 {savedPeerId!.serialize(buffer, true)} + filter.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSearchResultsPositions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("savedPeerId", String(describing: savedPeerId)), ("filter", String(describing: filter)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsPositions? in + let reader = BufferReader(buffer) + var result: Api.messages.SearchResultsPositions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsPositions + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSplitRanges() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.MessageRange]>) { + let buffer = Buffer() + buffer.appendInt32(486505992) + + return (FunctionDescription(name: "messages.getSplitRanges", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.MessageRange]? in + let reader = BufferReader(buffer) + var result: [Api.MessageRange]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageRange.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getStickerSet(stickerset: Api.InputStickerSet, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-928977804) + stickerset.serialize(buffer, true) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getStickerSet", parameters: [("stickerset", String(describing: stickerset)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.messages { + static func getStickers(emoticon: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-710552671) + serializeString(emoticon, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getStickers", parameters: [("emoticon", String(describing: emoticon)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Stickers? in + let reader = BufferReader(buffer) + var result: Api.messages.Stickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Stickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSuggestedDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogFilterSuggested]>) { + let buffer = Buffer() + buffer.appendInt32(-1566780372) + + return (FunctionDescription(name: "messages.getSuggestedDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogFilterSuggested]? in + let reader = BufferReader(buffer) + var result: [Api.DialogFilterSuggested]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilterSuggested.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getTopReactions(limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1149164102) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getTopReactions", parameters: [("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in + let reader = BufferReader(buffer) + var result: Api.messages.Reactions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Reactions + } + return result + }) + } +} +public extension Api.functions.messages { + static func getUnreadMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-251140208) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getUnreadMentions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getUnreadReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(841173339) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getUnreadReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getWebPage(url: String, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1919511901) + serializeString(url, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getWebPage", parameters: [("url", String(describing: url)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.WebPage? in + let reader = BufferReader(buffer) + var result: Api.messages.WebPage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.WebPage + } + return result + }) + } +} +public extension Api.functions.messages { + static func getWebPagePreview(flags: Int32, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1956073268) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + return (FunctionDescription(name: "messages.getWebPagePreview", parameters: [("flags", String(describing: flags)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in + let reader = BufferReader(buffer) + var result: Api.MessageMedia? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + return result + }) + } +} +public extension Api.functions.messages { + static func hideAllChatJoinRequests(flags: Int32, peer: Api.InputPeer, link: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-528091926) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {serializeString(link!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.hideAllChatJoinRequests", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("link", String(describing: link))]), 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 hideChatJoinRequest(flags: Int32, peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2145904661) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "messages.hideChatJoinRequest", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("userId", String(describing: userId))]), 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 hidePeerSettingsBar(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1336717624) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.hidePeerSettingsBar", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func importChatInvite(hash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1817183516) + serializeString(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.importChatInvite", parameters: [("hash", String(describing: hash))]), 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 initHistoryImport(peer: Api.InputPeer, file: Api.InputFile, mediaCount: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(873008187) + peer.serialize(buffer, true) + file.serialize(buffer, true) + serializeInt32(mediaCount, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.initHistoryImport", parameters: [("peer", String(describing: peer)), ("file", String(describing: file)), ("mediaCount", String(describing: mediaCount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImport? in + let reader = BufferReader(buffer) + var result: Api.messages.HistoryImport? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImport + } + return result + }) + } +} +public extension Api.functions.messages { + static func installStickerSet(stickerset: Api.InputStickerSet, archived: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-946871200) + stickerset.serialize(buffer, true) + archived.serialize(buffer, true) + return (FunctionDescription(name: "messages.installStickerSet", parameters: [("stickerset", String(describing: stickerset)), ("archived", String(describing: archived))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSetInstallResult? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSetInstallResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSetInstallResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func markDialogUnread(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1031349873) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.markDialogUnread", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func migrateChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1568189671) + serializeInt64(chatId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.migrateChat", parameters: [("chatId", String(describing: chatId))]), 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 prolongWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, queryId: Int64, replyTo: Api.InputReplyTo?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1328014717) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + bot.serialize(buffer, true) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.prolongWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("queryId", String(describing: queryId)), ("replyTo", String(describing: replyTo)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func rateTranscribedAudio(peer: Api.InputPeer, msgId: Int32, transcriptionId: Int64, good: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2132608815) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt64(transcriptionId, buffer: buffer, boxed: false) + good.serialize(buffer, true) + return (FunctionDescription(name: "messages.rateTranscribedAudio", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("transcriptionId", String(describing: transcriptionId)), ("good", String(describing: good))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func readDiscussion(peer: Api.InputPeer, msgId: Int32, readMaxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-147740172) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.readDiscussion", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("readMaxId", String(describing: readMaxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func readEncryptedHistory(peer: Api.InputEncryptedChat, maxDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2135648522) + peer.serialize(buffer, true) + serializeInt32(maxDate, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.readEncryptedHistory", parameters: [("peer", String(describing: peer)), ("maxDate", String(describing: maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func readFeaturedStickers(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1527873830) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.readFeaturedStickers", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func readHistory(peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(238054714) + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.readHistory", parameters: [("peer", String(describing: peer)), ("maxId", String(describing: maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages + } + return result + }) + } +} +public extension Api.functions.messages { + static func readMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(921026381) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.readMentions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func readMessageContents(id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(916930423) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.readMessageContents", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages + } + return result + }) + } +} +public extension Api.functions.messages { + static func readReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1420459918) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.readReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func receivedMessages(maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ReceivedNotifyMessage]>) { + let buffer = Buffer() + buffer.appendInt32(94983360) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.receivedMessages", parameters: [("maxId", String(describing: maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ReceivedNotifyMessage]? in + let reader = BufferReader(buffer) + var result: [Api.ReceivedNotifyMessage]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReceivedNotifyMessage.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func receivedQueue(maxQts: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int64]>) { + let buffer = Buffer() + buffer.appendInt32(1436924774) + serializeInt32(maxQts, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.receivedQueue", parameters: [("maxQts", String(describing: maxQts))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int64]? in + let reader = BufferReader(buffer) + var result: [Int64]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func reorderPinnedDialogs(flags: Int32, folderId: Int32, order: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(991616823) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(folderId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.reorderPinnedDialogs", parameters: [("flags", String(describing: flags)), ("folderId", String(describing: folderId)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reorderPinnedSavedDialogs(flags: Int32, order: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1955502713) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.reorderPinnedSavedDialogs", parameters: [("flags", String(describing: flags)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reorderQuickReplies(order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1613961479) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.reorderQuickReplies", parameters: [("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reorderStickerSets(flags: Int32, order: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2016638777) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.reorderStickerSets", parameters: [("flags", String(describing: flags)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func report(peer: Api.InputPeer, id: [Int32], reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1991005362) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + reason.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.report", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("reason", String(describing: reason)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reportEncryptedSpam(peer: Api.InputEncryptedChat) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1259113487) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.reportEncryptedSpam", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reportReaction(peer: Api.InputPeer, id: Int32, reactionPeer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1063567478) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + reactionPeer.serialize(buffer, true) + return (FunctionDescription(name: "messages.reportReaction", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("reactionPeer", String(describing: reactionPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reportSpam(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-820669733) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.reportSpam", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestAppWebView(flags: Int32, peer: Api.InputPeer, app: Api.InputBotApp, startParam: String?, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1940243652) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + app.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {themeParams!.serialize(buffer, true)} + serializeString(platform, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.requestAppWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("app", String(describing: app)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AppWebViewResult? in + let reader = BufferReader(buffer) + var result: Api.AppWebViewResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.AppWebViewResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestEncryption(userId: Api.InputUser, randomId: Int32, gA: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-162681021) + userId.serialize(buffer, true) + serializeInt32(randomId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.requestEncryption", parameters: [("userId", String(describing: userId)), ("randomId", String(describing: randomId)), ("gA", String(describing: gA))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedChat? in + let reader = BufferReader(buffer) + var result: Api.EncryptedChat? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EncryptedChat + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestSimpleWebView(flags: Int32, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(440815626) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + if Int(flags) & Int(1 << 3) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {themeParams!.serialize(buffer, true)} + serializeString(platform, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.requestSimpleWebView", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SimpleWebViewResult? in + let reader = BufferReader(buffer) + var result: Api.SimpleWebViewResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.SimpleWebViewResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestUrlAuth(flags: Int32, peer: Api.InputPeer?, msgId: Int32?, buttonId: Int32?, url: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(428848198) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {peer!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(msgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(buttonId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.requestUrlAuth", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("buttonId", String(describing: buttonId)), ("url", String(describing: url))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.UrlAuthResult? in + let reader = BufferReader(buffer) + var result: Api.UrlAuthResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.UrlAuthResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String, replyTo: Api.InputReplyTo?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(647873217) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + bot.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {themeParams!.serialize(buffer, true)} + serializeString(platform, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform)), ("replyTo", String(describing: replyTo)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in + let reader = BufferReader(buffer) + var result: Api.WebViewResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WebViewResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func saveDefaultSendAs(peer: Api.InputPeer, sendAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-855777386) + peer.serialize(buffer, true) + sendAs.serialize(buffer, true) + return (FunctionDescription(name: "messages.saveDefaultSendAs", parameters: [("peer", String(describing: peer)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func saveDraft(flags: Int32, replyTo: Api.InputReplyTo?, peer: Api.InputPeer, message: String, entities: [Api.MessageEntity]?, media: Api.InputMedia?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2146678790) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 {replyTo!.serialize(buffer, true)} + peer.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 5) != 0 {media!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.saveDraft", parameters: [("flags", String(describing: flags)), ("replyTo", String(describing: replyTo)), ("peer", String(describing: peer)), ("message", String(describing: message)), ("entities", String(describing: entities)), ("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func saveGif(id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(846868683) + id.serialize(buffer, true) + unsave.serialize(buffer, true) + return (FunctionDescription(name: "messages.saveGif", parameters: [("id", String(describing: id)), ("unsave", String(describing: unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func saveRecentSticker(flags: Int32, id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(958863608) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + unsave.serialize(buffer, true) + return (FunctionDescription(name: "messages.saveRecentSticker", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("unsave", String(describing: unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputPeer?, savedPeerId: Api.InputPeer?, savedReaction: [Api.Reaction]?, topMsgId: Int32?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(703497338) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(q, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {savedPeerId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(savedReaction!.count)) + for item in savedReaction! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + filter.serialize(buffer, true) + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(maxDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.search", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("q", String(describing: q)), ("fromId", String(describing: fromId)), ("savedPeerId", String(describing: savedPeerId)), ("savedReaction", String(describing: savedReaction)), ("topMsgId", String(describing: topMsgId)), ("filter", String(describing: filter)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchCustomEmoji(emoticon: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(739360983) + serializeString(emoticon, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchCustomEmoji", parameters: [("emoticon", String(describing: emoticon)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in + let reader = BufferReader(buffer) + var result: Api.EmojiList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiList + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchEmojiStickerSets(flags: Int32, q: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1833678516) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(q, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchEmojiStickerSets", parameters: [("flags", String(describing: flags)), ("q", String(describing: q)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickerSets? in + let reader = BufferReader(buffer) + var result: Api.messages.FoundStickerSets? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickerSets + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchGlobal(flags: Int32, folderId: Int32?, q: String, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1271290010) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} + serializeString(q, buffer: buffer, boxed: false) + filter.serialize(buffer, true) + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(maxDate, buffer: buffer, boxed: false) + serializeInt32(offsetRate, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", String(describing: flags)), ("folderId", String(describing: folderId)), ("q", String(describing: q)), ("filter", String(describing: filter)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate)), ("offsetRate", String(describing: offsetRate)), ("offsetPeer", String(describing: offsetPeer)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchSentMedia(q: String, filter: Api.MessagesFilter, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(276705696) + serializeString(q, buffer: buffer, boxed: false) + filter.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchSentMedia", parameters: [("q", String(describing: q)), ("filter", String(describing: filter)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchStickerSets(flags: Int32, q: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(896555914) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(q, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchStickerSets", parameters: [("flags", String(describing: flags)), ("q", String(describing: q)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickerSets? in + let reader = BufferReader(buffer) + var result: Api.messages.FoundStickerSets? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickerSets + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendBotRequestedPeer(peer: Api.InputPeer, msgId: Int32, buttonId: Int32, requestedPeers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1850552224) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(buttonId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(requestedPeers.count)) + for item in requestedPeers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.sendBotRequestedPeer", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("buttonId", String(describing: buttonId)), ("requestedPeers", String(describing: requestedPeers))]), 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 sendEncrypted(flags: Int32, peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1157265941) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeBytes(data, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.sendEncrypted", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("randomId", String(describing: randomId)), ("data", String(describing: data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.SentEncryptedMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendEncryptedFile(flags: Int32, peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1431914525) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeBytes(data, buffer: buffer, boxed: false) + file.serialize(buffer, true) + return (FunctionDescription(name: "messages.sendEncryptedFile", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("randomId", String(describing: randomId)), ("data", String(describing: data)), ("file", String(describing: file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.SentEncryptedMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendEncryptedService(peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(852769188) + peer.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeBytes(data, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.sendEncryptedService", parameters: [("peer", String(describing: peer)), ("randomId", String(describing: randomId)), ("data", String(describing: data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.SentEncryptedMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1052698730) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + serializeString(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} + if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("randomId", String(describing: randomId)), ("queryId", String(describing: queryId)), ("id", String(describing: id)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), 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 sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2018673486) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} + media.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} + if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} + if Int(flags) & Int(1 << 18) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("media", String(describing: media)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect))]), 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 sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1740662971) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} + serializeString(message, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} + if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} + if Int(flags) & Int(1 << 18) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect))]), 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 sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(934757205) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(multiMedia.count)) + for item in multiMedia { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} + if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} + if Int(flags) & Int(1 << 18) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("multiMedia", String(describing: multiMedia)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect))]), 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 sendQuickReplyMessages(peer: Api.InputPeer, shortcutId: Int32, id: [Int32], randomId: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1819610593) + peer.serialize(buffer, true) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomId.count)) + for item in randomId { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendQuickReplyMessages", parameters: [("peer", String(describing: peer)), ("shortcutId", String(describing: shortcutId)), ("id", String(describing: id)), ("randomId", String(describing: randomId))]), 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 sendReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, reaction: [Api.Reaction]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-754091820) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(reaction!.count)) + for item in reaction! { + item.serialize(buffer, true) + }} + return (FunctionDescription(name: "messages.sendReaction", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("reaction", String(describing: reaction))]), 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 sendScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1120369398) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendScheduledMessages", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendScreenshotNotification(peer: Api.InputPeer, replyTo: Api.InputReplyTo, randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1589618665) + peer.serialize(buffer, true) + replyTo.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.sendScreenshotNotification", parameters: [("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("randomId", String(describing: randomId))]), 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 sendVote(peer: Api.InputPeer, msgId: Int32, options: [Buffer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(283795844) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(options.count)) + for item in options { + serializeBytes(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendVote", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("options", String(describing: options))]), 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 sendWebViewData(bot: Api.InputUser, randomId: Int64, buttonText: String, data: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-603831608) + bot.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeString(buttonText, buffer: buffer, boxed: false) + serializeString(data, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.sendWebViewData", parameters: [("bot", String(describing: bot)), ("randomId", String(describing: randomId)), ("buttonText", String(describing: buttonText)), ("data", String(describing: data))]), 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 sendWebViewResultMessage(botQueryId: String, result: Api.InputBotInlineResult) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(172168437) + serializeString(botQueryId, buffer: buffer, boxed: false) + result.serialize(buffer, true) + return (FunctionDescription(name: "messages.sendWebViewResultMessage", parameters: [("botQueryId", String(describing: botQueryId)), ("result", String(describing: result))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewMessageSent? in + let reader = BufferReader(buffer) + var result: Api.WebViewMessageSent? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WebViewMessageSent + } + return result + }) + } +} +public extension Api.functions.messages { + static func setBotCallbackAnswer(flags: Int32, queryId: Int64, message: String?, url: String?, cacheTime: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-712043766) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(message!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + serializeInt32(cacheTime, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setBotCallbackAnswer", parameters: [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("message", String(describing: message)), ("url", String(describing: url)), ("cacheTime", String(describing: cacheTime))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setBotPrecheckoutResults(flags: Int32, queryId: Int64, error: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(163765653) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(error!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.setBotPrecheckoutResults", parameters: [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("error", String(describing: error))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setBotShippingResults(flags: Int32, queryId: Int64, error: String?, shippingOptions: [Api.ShippingOption]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-436833542) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(error!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(shippingOptions!.count)) + for item in shippingOptions! { + item.serialize(buffer, true) + }} + return (FunctionDescription(name: "messages.setBotShippingResults", parameters: [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("error", String(describing: error)), ("shippingOptions", String(describing: shippingOptions))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setChatAvailableReactions(flags: Int32, peer: Api.InputPeer, availableReactions: Api.ChatReactions, reactionsLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1511328724) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + availableReactions.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(reactionsLimit!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.setChatAvailableReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("availableReactions", String(describing: availableReactions)), ("reactionsLimit", String(describing: reactionsLimit))]), 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 setChatTheme(peer: Api.InputPeer, emoticon: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-432283329) + peer.serialize(buffer, true) + serializeString(emoticon, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setChatTheme", parameters: [("peer", String(describing: peer)), ("emoticon", String(describing: emoticon))]), 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 setChatWallPaper(flags: Int32, peer: Api.InputPeer, wallpaper: Api.InputWallPaper?, settings: Api.WallPaperSettings?, id: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1879389471) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {wallpaper!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {settings!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(id!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.setChatWallPaper", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("wallpaper", String(describing: wallpaper)), ("settings", String(describing: settings)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func setDefaultHistoryTTL(period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1632299963) + serializeInt32(period, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setDefaultHistoryTTL", parameters: [("period", String(describing: period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setDefaultReaction(reaction: Api.Reaction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1330094102) + reaction.serialize(buffer, true) + return (FunctionDescription(name: "messages.setDefaultReaction", parameters: [("reaction", String(describing: reaction))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setEncryptedTyping(peer: Api.InputEncryptedChat, typing: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2031374829) + peer.serialize(buffer, true) + typing.serialize(buffer, true) + return (FunctionDescription(name: "messages.setEncryptedTyping", parameters: [("peer", String(describing: peer)), ("typing", String(describing: typing))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setGameScore(flags: Int32, peer: Api.InputPeer, id: Int32, userId: Api.InputUser, score: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1896289088) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + serializeInt32(score, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setGameScore", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("userId", String(describing: userId)), ("score", String(describing: score))]), 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 setHistoryTTL(peer: Api.InputPeer, period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1207017500) + peer.serialize(buffer, true) + serializeInt32(period, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setHistoryTTL", parameters: [("peer", String(describing: peer)), ("period", String(describing: period))]), 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 setInlineBotResults(flags: Int32, queryId: Int64, results: [Api.InputBotInlineResult], cacheTime: Int32, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, switchWebview: Api.InlineBotWebView?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1156406247) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(results.count)) + for item in results { + item.serialize(buffer, true) + } + serializeInt32(cacheTime, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {switchPm!.serialize(buffer, true)} + if Int(flags) & Int(1 << 4) != 0 {switchWebview!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.setInlineBotResults", parameters: [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("results", String(describing: results)), ("cacheTime", String(describing: cacheTime)), ("nextOffset", String(describing: nextOffset)), ("switchPm", String(describing: switchPm)), ("switchWebview", String(describing: switchWebview))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setInlineGameScore(flags: Int32, id: Api.InputBotInlineMessageID, userId: Api.InputUser, score: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(363700068) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + userId.serialize(buffer, true) + serializeInt32(score, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setInlineGameScore", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("userId", String(describing: userId)), ("score", String(describing: score))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setTyping(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, action: Api.SendMessageAction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1486110434) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + action.serialize(buffer, true) + return (FunctionDescription(name: "messages.setTyping", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("action", String(describing: action))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func startBot(bot: Api.InputUser, peer: Api.InputPeer, randomId: Int64, startParam: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-421563528) + bot.serialize(buffer, true) + peer.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeString(startParam, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.startBot", parameters: [("bot", String(describing: bot)), ("peer", String(describing: peer)), ("randomId", String(describing: randomId)), ("startParam", String(describing: startParam))]), 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 startHistoryImport(peer: Api.InputPeer, importId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1271008444) + peer.serialize(buffer, true) + serializeInt64(importId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.startHistoryImport", parameters: [("peer", String(describing: peer)), ("importId", String(describing: importId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleBotInAttachMenu(flags: Int32, bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1777704297) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "messages.toggleBotInAttachMenu", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleDialogFilterTags(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-47326647) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "messages.toggleDialogFilterTags", parameters: [("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1489903017) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.toggleDialogPin", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleNoForwards(peer: Api.InputPeer, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1323389022) + peer.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "messages.toggleNoForwards", parameters: [("peer", String(describing: peer)), ("enabled", String(describing: enabled))]), 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 togglePeerTranslations(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-461589127) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.togglePeerTranslations", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleSavedDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1400783906) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.toggleSavedDialogPin", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleStickerSets(flags: Int32, stickersets: [Api.InputStickerSet]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1257951254) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickersets.count)) + for item in stickersets { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.toggleStickerSets", parameters: [("flags", String(describing: flags)), ("stickersets", String(describing: stickersets))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func transcribeAudio(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(647928393) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.transcribeAudio", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.TranscribedAudio? in + let reader = BufferReader(buffer) + var result: Api.messages.TranscribedAudio? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.TranscribedAudio + } + return result + }) + } +} +public extension Api.functions.messages { + static func translateText(flags: Int32, peer: Api.InputPeer?, id: [Int32]?, text: [Api.TextWithEntities]?, toLang: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1662529584) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id!.count)) + for item in id! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(text!.count)) + for item in text! { + item.serialize(buffer, true) + }} + serializeString(toLang, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.translateText", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("text", String(describing: text)), ("toLang", String(describing: toLang))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.TranslatedText? in + let reader = BufferReader(buffer) + var result: Api.messages.TranslatedText? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.TranslatedText + } + return result + }) + } +} +public extension Api.functions.messages { + static func uninstallStickerSet(stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-110209570) + stickerset.serialize(buffer, true) + return (FunctionDescription(name: "messages.uninstallStickerSet", parameters: [("stickerset", String(describing: stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func unpinAllMessages(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-299714136) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.unpinAllMessages", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(450142282) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {filter!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.updateDialogFilter", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("filter", String(describing: filter))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func updateDialogFiltersOrder(order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-983318044) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.updateDialogFiltersOrder", parameters: [("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func updatePinnedMessage(flags: Int32, peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-760547348) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.updatePinnedMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func updateSavedReactionTag(flags: Int32, reaction: Api.Reaction, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1613331948) + serializeInt32(flags, buffer: buffer, boxed: false) + reaction.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.updateSavedReactionTag", parameters: [("flags", String(describing: flags)), ("reaction", String(describing: reaction)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func uploadEncryptedFile(peer: Api.InputEncryptedChat, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1347929239) + peer.serialize(buffer, true) + file.serialize(buffer, true) + return (FunctionDescription(name: "messages.uploadEncryptedFile", parameters: [("peer", String(describing: peer)), ("file", String(describing: file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedFile? in + let reader = BufferReader(buffer) + var result: Api.EncryptedFile? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EncryptedFile + } + return result + }) + } +} +public extension Api.functions.messages { + static func uploadImportedMedia(peer: Api.InputPeer, importId: Int64, fileName: String, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(713433234) + peer.serialize(buffer, true) + serializeInt64(importId, buffer: buffer, boxed: false) + serializeString(fileName, buffer: buffer, boxed: false) + media.serialize(buffer, true) + return (FunctionDescription(name: "messages.uploadImportedMedia", parameters: [("peer", String(describing: peer)), ("importId", String(describing: importId)), ("fileName", String(describing: fileName)), ("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in + let reader = BufferReader(buffer) + var result: Api.MessageMedia? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + return result + }) + } +} +public extension Api.functions.messages { + static func uploadMedia(flags: Int32, businessConnectionId: String?, peer: Api.InputPeer, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(345405816) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(businessConnectionId!, buffer: buffer, boxed: false)} + peer.serialize(buffer, true) + media.serialize(buffer, true) + return (FunctionDescription(name: "messages.uploadMedia", parameters: [("flags", String(describing: flags)), ("businessConnectionId", String(describing: businessConnectionId)), ("peer", String(describing: peer)), ("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in + let reader = BufferReader(buffer) + var result: Api.MessageMedia? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + return result + }) + } +} +public extension Api.functions.payments { + static func applyGiftCode(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-152934316) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.applyGiftCode", parameters: [("slug", String(describing: slug))]), 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.payments { + static func assignAppStoreTransaction(receipt: Buffer, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2131921795) + serializeBytes(receipt, buffer: buffer, boxed: false) + purpose.serialize(buffer, true) + return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("receipt", String(describing: receipt)), ("purpose", String(describing: purpose))]), 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.payments { + static func assignPlayMarketTransaction(receipt: Api.DataJSON, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-537046829) + receipt.serialize(buffer, true) + purpose.serialize(buffer, true) + return (FunctionDescription(name: "payments.assignPlayMarketTransaction", parameters: [("receipt", String(describing: receipt)), ("purpose", String(describing: purpose))]), 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.payments { + static func canPurchasePremium(purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1614700874) + purpose.serialize(buffer, true) + return (FunctionDescription(name: "payments.canPurchasePremium", parameters: [("purpose", String(describing: purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func checkGiftCode(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1907247935) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.checkGiftCode", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.CheckedGiftCode? in + let reader = BufferReader(buffer) + var result: Api.payments.CheckedGiftCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.CheckedGiftCode + } + return result + }) + } +} +public extension Api.functions.payments { + static func clearSavedInfo(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-667062079) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.clearSavedInfo", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func exportInvoice(invoiceMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(261206117) + invoiceMedia.serialize(buffer, true) + return (FunctionDescription(name: "payments.exportInvoice", parameters: [("invoiceMedia", String(describing: invoiceMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ExportedInvoice? in + let reader = BufferReader(buffer) + var result: Api.payments.ExportedInvoice? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.ExportedInvoice + } + return result + }) + } +} +public extension Api.functions.payments { + static func getBankCardData(number: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(779736953) + serializeString(number, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getBankCardData", parameters: [("number", String(describing: number))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.BankCardData? in + let reader = BufferReader(buffer) + var result: Api.payments.BankCardData? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.BankCardData + } + return result + }) + } +} +public extension Api.functions.payments { + static func getGiveawayInfo(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-198994907) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getGiveawayInfo", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.GiveawayInfo? in + let reader = BufferReader(buffer) + var result: Api.payments.GiveawayInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.GiveawayInfo + } + return result + }) + } +} +public extension Api.functions.payments { + static func getPaymentForm(flags: Int32, invoice: Api.InputInvoice, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(924093883) + serializeInt32(flags, buffer: buffer, boxed: false) + invoice.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {themeParams!.serialize(buffer, true)} + return (FunctionDescription(name: "payments.getPaymentForm", parameters: [("flags", String(describing: flags)), ("invoice", String(describing: invoice)), ("themeParams", String(describing: themeParams))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentForm? in + let reader = BufferReader(buffer) + var result: Api.payments.PaymentForm? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.PaymentForm + } + return result + }) + } +} +public extension Api.functions.payments { + static func getPaymentReceipt(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(611897804) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getPaymentReceipt", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentReceipt? in + let reader = BufferReader(buffer) + var result: Api.payments.PaymentReceipt? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.PaymentReceipt + } + return result + }) + } +} +public extension Api.functions.payments { + static func getPremiumGiftCodeOptions(flags: Int32, boostPeer: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.PremiumGiftCodeOption]>) { + let buffer = Buffer() + buffer.appendInt32(660060756) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {boostPeer!.serialize(buffer, true)} + return (FunctionDescription(name: "payments.getPremiumGiftCodeOptions", parameters: [("flags", String(describing: flags)), ("boostPeer", String(describing: boostPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.PremiumGiftCodeOption]? in + let reader = BufferReader(buffer) + var result: [Api.PremiumGiftCodeOption]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumGiftCodeOption.self) + } + return result + }) + } +} +public extension Api.functions.payments { + static func getSavedInfo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(578650699) + + return (FunctionDescription(name: "payments.getSavedInfo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SavedInfo? in + let reader = BufferReader(buffer) + var result: Api.payments.SavedInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.SavedInfo + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsStatus(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(273665959) + peer.serialize(buffer, true) + return (FunctionDescription(name: "payments.getStarsStatus", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in + let reader = BufferReader(buffer) + var result: Api.payments.StarsStatus? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarsStatus + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsTopupOptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StarsTopupOption]>) { + let buffer = Buffer() + buffer.appendInt32(-1072773165) + + return (FunctionDescription(name: "payments.getStarsTopupOptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StarsTopupOption]? in + let reader = BufferReader(buffer) + var result: [Api.StarsTopupOption]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTopupOption.self) + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsTransactions(flags: Int32, peer: Api.InputPeer, offset: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1731904249) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(offset, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarsTransactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in + let reader = BufferReader(buffer) + var result: Api.payments.StarsStatus? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarsStatus + } + return result + }) + } +} +public extension Api.functions.payments { + static func launchPrepaidGiveaway(peer: Api.InputPeer, giveawayId: Int64, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1609928480) + peer.serialize(buffer, true) + serializeInt64(giveawayId, buffer: buffer, boxed: false) + purpose.serialize(buffer, true) + return (FunctionDescription(name: "payments.launchPrepaidGiveaway", parameters: [("peer", String(describing: peer)), ("giveawayId", String(describing: giveawayId)), ("purpose", String(describing: purpose))]), 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.payments { + static func refundStarsCharge(userId: Api.InputUser, msgId: Int32, chargeId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-258950164) + userId.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeString(chargeId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.refundStarsCharge", parameters: [("userId", String(describing: userId)), ("msgId", String(describing: msgId)), ("chargeId", String(describing: chargeId))]), 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.payments { + static func sendPaymentForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(755192367) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(formId, buffer: buffer, boxed: false) + invoice.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(requestedInfoId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(shippingOptionId!, buffer: buffer, boxed: false)} + credentials.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "payments.sendPaymentForm", parameters: [("flags", String(describing: flags)), ("formId", String(describing: formId)), ("invoice", String(describing: invoice)), ("requestedInfoId", String(describing: requestedInfoId)), ("shippingOptionId", String(describing: shippingOptionId)), ("credentials", String(describing: credentials)), ("tipAmount", String(describing: tipAmount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in + let reader = BufferReader(buffer) + var result: Api.payments.PaymentResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.PaymentResult + } + return result + }) + } +} +public extension Api.functions.payments { + static func sendStarsForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(45839133) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(formId, buffer: buffer, boxed: false) + invoice.serialize(buffer, true) + return (FunctionDescription(name: "payments.sendStarsForm", parameters: [("flags", String(describing: flags)), ("formId", String(describing: formId)), ("invoice", String(describing: invoice))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in + let reader = BufferReader(buffer) + var result: Api.payments.PaymentResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.PaymentResult + } + return result + }) + } +} +public extension Api.functions.payments { + static func validateRequestedInfo(flags: Int32, invoice: Api.InputInvoice, info: Api.PaymentRequestedInfo) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1228345045) + serializeInt32(flags, buffer: buffer, boxed: false) + invoice.serialize(buffer, true) + info.serialize(buffer, true) + return (FunctionDescription(name: "payments.validateRequestedInfo", parameters: [("flags", String(describing: flags)), ("invoice", String(describing: invoice)), ("info", String(describing: info))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ValidatedRequestedInfo? in + let reader = BufferReader(buffer) + var result: Api.payments.ValidatedRequestedInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.ValidatedRequestedInfo + } + return result + }) + } +} +public extension Api.functions.phone { + static func acceptCall(peer: Api.InputPhoneCall, gB: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1003664544) + peer.serialize(buffer, true) + serializeBytes(gB, buffer: buffer, boxed: false) + `protocol`.serialize(buffer, true) + return (FunctionDescription(name: "phone.acceptCall", parameters: [("peer", String(describing: peer)), ("gB", String(describing: gB)), ("`protocol`", String(describing: `protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in + let reader = BufferReader(buffer) + var result: Api.phone.PhoneCall? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall + } + return result + }) + } +} +public extension Api.functions.phone { + static func checkGroupCall(call: Api.InputGroupCall, sources: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(-1248003721) + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sources.count)) + for item in sources { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "phone.checkGroupCall", parameters: [("call", String(describing: call)), ("sources", String(describing: sources))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.phone { + static func confirmCall(peer: Api.InputPhoneCall, gA: Buffer, keyFingerprint: Int64, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(788404002) + peer.serialize(buffer, true) + serializeBytes(gA, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + `protocol`.serialize(buffer, true) + return (FunctionDescription(name: "phone.confirmCall", parameters: [("peer", String(describing: peer)), ("gA", String(describing: gA)), ("keyFingerprint", String(describing: keyFingerprint)), ("`protocol`", String(describing: `protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in + let reader = BufferReader(buffer) + var result: Api.phone.PhoneCall? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall + } + return result + }) + } +} +public extension Api.functions.phone { + static func createGroupCall(flags: Int32, peer: Api.InputPeer, randomId: Int32, title: String?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1221445336) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "phone.createGroupCall", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("randomId", String(describing: randomId)), ("title", String(describing: title)), ("scheduleDate", String(describing: scheduleDate))]), 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.phone { + static func discardCall(flags: Int32, peer: Api.InputPhoneCall, duration: Int32, reason: Api.PhoneCallDiscardReason, connectionId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1295269440) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(duration, buffer: buffer, boxed: false) + reason.serialize(buffer, true) + serializeInt64(connectionId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.discardCall", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("duration", String(describing: duration)), ("reason", String(describing: reason)), ("connectionId", String(describing: connectionId))]), 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.phone { + static func discardGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2054648117) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.discardGroupCall", parameters: [("call", String(describing: call))]), 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.phone { + static func editGroupCallParticipant(flags: Int32, call: Api.InputGroupCall, participant: Api.InputPeer, muted: Api.Bool?, volume: Int32?, raiseHand: Api.Bool?, videoStopped: Api.Bool?, videoPaused: Api.Bool?, presentationPaused: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1524155713) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + participant.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {muted!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(volume!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {raiseHand!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {videoStopped!.serialize(buffer, true)} + if Int(flags) & Int(1 << 4) != 0 {videoPaused!.serialize(buffer, true)} + if Int(flags) & Int(1 << 5) != 0 {presentationPaused!.serialize(buffer, true)} + return (FunctionDescription(name: "phone.editGroupCallParticipant", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("participant", String(describing: participant)), ("muted", String(describing: muted)), ("volume", String(describing: volume)), ("raiseHand", String(describing: raiseHand)), ("videoStopped", String(describing: videoStopped)), ("videoPaused", String(describing: videoPaused)), ("presentationPaused", String(describing: presentationPaused))]), 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.phone { + static func editGroupCallTitle(call: Api.InputGroupCall, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(480685066) + call.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.editGroupCallTitle", parameters: [("call", String(describing: call)), ("title", String(describing: title))]), 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.phone { + static func exportGroupCallInvite(flags: Int32, call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-425040769) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.exportGroupCallInvite", parameters: [("flags", String(describing: flags)), ("call", String(describing: call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.ExportedGroupCallInvite? in + let reader = BufferReader(buffer) + var result: Api.phone.ExportedGroupCallInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.ExportedGroupCallInvite + } + return result + }) + } +} +public extension Api.functions.phone { + static func getCallConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1430593449) + + return (FunctionDescription(name: "phone.getCallConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in + let reader = BufferReader(buffer) + var result: Api.DataJSON? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.DataJSON + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupCall(call: Api.InputGroupCall, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(68699611) + call.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.getGroupCall", parameters: [("call", String(describing: call)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCall? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupCall? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupCall + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupCallJoinAs(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-277077702) + peer.serialize(buffer, true) + return (FunctionDescription(name: "phone.getGroupCallJoinAs", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.JoinAsPeers? in + let reader = BufferReader(buffer) + var result: Api.phone.JoinAsPeers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.JoinAsPeers + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupCallStreamChannels(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(447879488) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.getGroupCallStreamChannels", parameters: [("call", String(describing: call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamChannels? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupCallStreamChannels? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStreamChannels + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupCallStreamRtmpUrl(peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-558650433) + peer.serialize(buffer, true) + revoke.serialize(buffer, true) + return (FunctionDescription(name: "phone.getGroupCallStreamRtmpUrl", parameters: [("peer", String(describing: peer)), ("revoke", String(describing: revoke))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamRtmpUrl? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupCallStreamRtmpUrl? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStreamRtmpUrl + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupParticipants(call: Api.InputGroupCall, ids: [Api.InputPeer], sources: [Int32], offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-984033109) + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(ids.count)) + for item in ids { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sources.count)) + for item in sources { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.getGroupParticipants", parameters: [("call", String(describing: call)), ("ids", String(describing: ids)), ("sources", String(describing: sources)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupParticipants? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupParticipants? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupParticipants + } + return result + }) + } +} +public extension Api.functions.phone { + static func inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2067345760) + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "phone.inviteToGroupCall", parameters: [("call", String(describing: call)), ("users", String(describing: users))]), 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.phone { + static func joinGroupCall(flags: Int32, call: Api.InputGroupCall, joinAs: Api.InputPeer, inviteHash: String?, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1322057861) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + joinAs.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {serializeString(inviteHash!, buffer: buffer, boxed: false)} + params.serialize(buffer, true) + return (FunctionDescription(name: "phone.joinGroupCall", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("joinAs", String(describing: joinAs)), ("inviteHash", String(describing: inviteHash)), ("params", String(describing: params))]), 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.phone { + static func joinGroupCallPresentation(call: Api.InputGroupCall, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-873829436) + call.serialize(buffer, true) + params.serialize(buffer, true) + return (FunctionDescription(name: "phone.joinGroupCallPresentation", parameters: [("call", String(describing: call)), ("params", String(describing: params))]), 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.phone { + static func leaveGroupCall(call: Api.InputGroupCall, source: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1342404601) + call.serialize(buffer, true) + serializeInt32(source, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.leaveGroupCall", parameters: [("call", String(describing: call)), ("source", String(describing: source))]), 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.phone { + static func leaveGroupCallPresentation(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(475058500) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.leaveGroupCallPresentation", parameters: [("call", String(describing: call))]), 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.phone { + static func receivedCall(peer: Api.InputPhoneCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(399855457) + peer.serialize(buffer, true) + return (FunctionDescription(name: "phone.receivedCall", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func requestCall(flags: Int32, userId: Api.InputUser, randomId: Int32, gAHash: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1124046573) + serializeInt32(flags, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + serializeInt32(randomId, buffer: buffer, boxed: false) + serializeBytes(gAHash, buffer: buffer, boxed: false) + `protocol`.serialize(buffer, true) + return (FunctionDescription(name: "phone.requestCall", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("randomId", String(describing: randomId)), ("gAHash", String(describing: gAHash)), ("`protocol`", String(describing: `protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in + let reader = BufferReader(buffer) + var result: Api.phone.PhoneCall? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall + } + return result + }) + } +} +public extension Api.functions.phone { + static func saveCallDebug(peer: Api.InputPhoneCall, debug: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(662363518) + peer.serialize(buffer, true) + debug.serialize(buffer, true) + return (FunctionDescription(name: "phone.saveCallDebug", parameters: [("peer", String(describing: peer)), ("debug", String(describing: debug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func saveCallLog(peer: Api.InputPhoneCall, file: Api.InputFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1092913030) + peer.serialize(buffer, true) + file.serialize(buffer, true) + return (FunctionDescription(name: "phone.saveCallLog", parameters: [("peer", String(describing: peer)), ("file", String(describing: file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func saveDefaultGroupCallJoinAs(peer: Api.InputPeer, joinAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1465786252) + peer.serialize(buffer, true) + joinAs.serialize(buffer, true) + return (FunctionDescription(name: "phone.saveDefaultGroupCallJoinAs", parameters: [("peer", String(describing: peer)), ("joinAs", String(describing: joinAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func sendSignalingData(peer: Api.InputPhoneCall, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-8744061) + peer.serialize(buffer, true) + serializeBytes(data, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.sendSignalingData", parameters: [("peer", String(describing: peer)), ("data", String(describing: data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func setCallRating(flags: Int32, peer: Api.InputPhoneCall, rating: Int32, comment: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1508562471) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(rating, buffer: buffer, boxed: false) + serializeString(comment, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.setCallRating", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("rating", String(describing: rating)), ("comment", String(describing: comment))]), 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.phone { + static func startScheduledGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1451287362) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.startScheduledGroupCall", parameters: [("call", String(describing: call))]), 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.phone { + static func toggleGroupCallRecord(flags: Int32, call: Api.InputGroupCall, title: String?, videoPortrait: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-248985848) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {videoPortrait!.serialize(buffer, true)} + return (FunctionDescription(name: "phone.toggleGroupCallRecord", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("title", String(describing: title)), ("videoPortrait", String(describing: videoPortrait))]), 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.phone { + static func toggleGroupCallSettings(flags: Int32, call: Api.InputGroupCall, joinMuted: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1958458429) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {joinMuted!.serialize(buffer, true)} + return (FunctionDescription(name: "phone.toggleGroupCallSettings", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("joinMuted", String(describing: joinMuted))]), 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.phone { + static func toggleGroupCallStartSubscription(call: Api.InputGroupCall, subscribed: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(563885286) + call.serialize(buffer, true) + subscribed.serialize(buffer, true) + return (FunctionDescription(name: "phone.toggleGroupCallStartSubscription", parameters: [("call", String(describing: call)), ("subscribed", String(describing: subscribed))]), 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.photos { + static func deletePhotos(id: [Api.InputPhoto]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int64]>) { + let buffer = Buffer() + buffer.appendInt32(-2016444625) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "photos.deletePhotos", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int64]? in + let reader = BufferReader(buffer) + var result: [Int64]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + return result + }) + } +} +public extension Api.functions.photos { + static func getUserPhotos(userId: Api.InputUser, offset: Int32, maxId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1848823128) + userId.serialize(buffer, true) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt64(maxId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "photos.getUserPhotos", parameters: [("userId", String(describing: userId)), ("offset", String(describing: offset)), ("maxId", String(describing: maxId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photos? in + let reader = BufferReader(buffer) + var result: Api.photos.Photos? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.photos.Photos + } + return result + }) + } +} +public extension Api.functions.photos { + static func updateProfilePhoto(flags: Int32, bot: Api.InputUser?, id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(166207545) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {bot!.serialize(buffer, true)} + id.serialize(buffer, true) + return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in + let reader = BufferReader(buffer) + var result: Api.photos.Photo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.photos.Photo + } + return result + }) + } +} +public extension Api.functions.photos { + static func uploadContactProfilePhoto(flags: Int32, userId: Api.InputUser, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-515093903) + serializeInt32(flags, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {videoEmojiMarkup!.serialize(buffer, true)} + return (FunctionDescription(name: "photos.uploadContactProfilePhoto", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("file", String(describing: file)), ("video", String(describing: video)), ("videoStartTs", String(describing: videoStartTs)), ("videoEmojiMarkup", String(describing: videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in + let reader = BufferReader(buffer) + var result: Api.photos.Photo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.photos.Photo + } + return result + }) + } +} +public extension Api.functions.photos { + static func uploadProfilePhoto(flags: Int32, bot: Api.InputUser?, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(59286453) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 5) != 0 {bot!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {videoEmojiMarkup!.serialize(buffer, true)} + return (FunctionDescription(name: "photos.uploadProfilePhoto", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("file", String(describing: file)), ("video", String(describing: video)), ("videoStartTs", String(describing: videoStartTs)), ("videoEmojiMarkup", String(describing: videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in + let reader = BufferReader(buffer) + var result: Api.photos.Photo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.photos.Photo + } + return result + }) + } +} +public extension Api.functions.premium { + static func applyBoost(flags: Int32, slots: [Int32]?, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1803396934) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(slots!.count)) + for item in slots! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + peer.serialize(buffer, true) + return (FunctionDescription(name: "premium.applyBoost", parameters: [("flags", String(describing: flags)), ("slots", String(describing: slots)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.MyBoosts? in + let reader = BufferReader(buffer) + var result: Api.premium.MyBoosts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.premium.MyBoosts + } + return result + }) + } +} +public extension Api.functions.premium { + static func getBoostsList(flags: Int32, peer: Api.InputPeer, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1626764896) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "premium.getBoostsList", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsList? in + let reader = BufferReader(buffer) + var result: Api.premium.BoostsList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.premium.BoostsList + } + return result + }) + } +} +public extension Api.functions.premium { + static func getBoostsStatus(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(70197089) + peer.serialize(buffer, true) + return (FunctionDescription(name: "premium.getBoostsStatus", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsStatus? in + let reader = BufferReader(buffer) + var result: Api.premium.BoostsStatus? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.premium.BoostsStatus + } + return result + }) + } +} +public extension Api.functions.premium { + static func getMyBoosts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(199719754) + + return (FunctionDescription(name: "premium.getMyBoosts", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.MyBoosts? in + let reader = BufferReader(buffer) + var result: Api.premium.MyBoosts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.premium.MyBoosts + } + return result + }) + } +} +public extension Api.functions.premium { + static func getUserBoosts(peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(965037343) + peer.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "premium.getUserBoosts", parameters: [("peer", String(describing: peer)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsList? in + let reader = BufferReader(buffer) + var result: Api.premium.BoostsList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.premium.BoostsList + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func finishJob(flags: Int32, jobId: String, error: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1327415076) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(jobId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(error!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "smsjobs.finishJob", parameters: [("flags", String(describing: flags)), ("jobId", String(describing: jobId)), ("error", String(describing: error))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func getSmsJob(jobId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2005766191) + serializeString(jobId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "smsjobs.getSmsJob", parameters: [("jobId", String(describing: jobId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SmsJob? in + let reader = BufferReader(buffer) + var result: Api.SmsJob? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.SmsJob + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func getStatus() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(279353576) + + return (FunctionDescription(name: "smsjobs.getStatus", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.smsjobs.Status? in + let reader = BufferReader(buffer) + var result: Api.smsjobs.Status? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.smsjobs.Status + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func isEligibleToJoin() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(249313744) + + return (FunctionDescription(name: "smsjobs.isEligibleToJoin", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.smsjobs.EligibilityToJoin? in + let reader = BufferReader(buffer) + var result: Api.smsjobs.EligibilityToJoin? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.smsjobs.EligibilityToJoin + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func join() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1488007635) + + return (FunctionDescription(name: "smsjobs.join", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func leave() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1734824589) + + return (FunctionDescription(name: "smsjobs.leave", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func updateSettings(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(155164863) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "smsjobs.updateSettings", parameters: [("flags", String(describing: flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stats { + static func getBroadcastRevenueStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1977595505) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + return (FunctionDescription(name: "stats.getBroadcastRevenueStats", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastRevenueStats? in + let reader = BufferReader(buffer) + var result: Api.stats.BroadcastRevenueStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastRevenueStats + } + return result + }) + } +} +public extension Api.functions.stats { + static func getBroadcastRevenueTransactions(channel: Api.InputChannel, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(6891535) + channel.serialize(buffer, true) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getBroadcastRevenueTransactions", parameters: [("channel", String(describing: channel)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastRevenueTransactions? in + let reader = BufferReader(buffer) + var result: Api.stats.BroadcastRevenueTransactions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastRevenueTransactions + } + return result + }) + } +} +public extension Api.functions.stats { + static func getBroadcastRevenueWithdrawalUrl(channel: Api.InputChannel, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(711323507) + channel.serialize(buffer, true) + password.serialize(buffer, true) + return (FunctionDescription(name: "stats.getBroadcastRevenueWithdrawalUrl", parameters: [("channel", String(describing: channel)), ("password", String(describing: password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastRevenueWithdrawalUrl? in + let reader = BufferReader(buffer) + var result: Api.stats.BroadcastRevenueWithdrawalUrl? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastRevenueWithdrawalUrl + } + return result + }) + } +} +public extension Api.functions.stats { + static func getBroadcastStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1421720550) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + return (FunctionDescription(name: "stats.getBroadcastStats", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastStats? in + let reader = BufferReader(buffer) + var result: Api.stats.BroadcastStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastStats + } + return result + }) + } +} +public extension Api.functions.stats { + static func getMegagroupStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-589330937) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + return (FunctionDescription(name: "stats.getMegagroupStats", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MegagroupStats? in + let reader = BufferReader(buffer) + var result: Api.stats.MegagroupStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.MegagroupStats + } + return result + }) + } +} +public extension Api.functions.stats { + static func getMessagePublicForwards(channel: Api.InputChannel, msgId: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1595212100) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getMessagePublicForwards", parameters: [("channel", String(describing: channel)), ("msgId", String(describing: msgId)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.PublicForwards? in + let reader = BufferReader(buffer) + var result: Api.stats.PublicForwards? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.PublicForwards + } + return result + }) + } +} +public extension Api.functions.stats { + static func getMessageStats(flags: Int32, channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1226791947) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getMessageStats", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MessageStats? in + let reader = BufferReader(buffer) + var result: Api.stats.MessageStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.MessageStats + } + return result + }) + } +} +public extension Api.functions.stats { + static func getStoryPublicForwards(peer: Api.InputPeer, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1505526026) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getStoryPublicForwards", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.PublicForwards? in + let reader = BufferReader(buffer) + var result: Api.stats.PublicForwards? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.PublicForwards + } + return result + }) + } +} +public extension Api.functions.stats { + static func getStoryStats(flags: Int32, peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(927985472) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getStoryStats", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.StoryStats? in + let reader = BufferReader(buffer) + var result: Api.stats.StoryStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.StoryStats + } + return result + }) + } +} +public extension Api.functions.stats { + static func loadAsyncGraph(flags: Int32, token: String, x: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1646092192) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(token, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(x!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "stats.loadAsyncGraph", parameters: [("flags", String(describing: flags)), ("token", String(describing: token)), ("x", String(describing: x))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StatsGraph? in + let reader = BufferReader(buffer) + var result: Api.StatsGraph? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + return result + }) + } +} +public extension Api.functions.stickers { + static func addStickerToSet(stickerset: Api.InputStickerSet, sticker: Api.InputStickerSetItem) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2041315650) + stickerset.serialize(buffer, true) + sticker.serialize(buffer, true) + return (FunctionDescription(name: "stickers.addStickerToSet", parameters: [("stickerset", String(describing: stickerset)), ("sticker", String(describing: sticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func changeSticker(flags: Int32, sticker: Api.InputDocument, emoji: String?, maskCoords: Api.MaskCoords?, keywords: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-179077444) + serializeInt32(flags, buffer: buffer, boxed: false) + sticker.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(emoji!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {maskCoords!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(keywords!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "stickers.changeSticker", parameters: [("flags", String(describing: flags)), ("sticker", String(describing: sticker)), ("emoji", String(describing: emoji)), ("maskCoords", String(describing: maskCoords)), ("keywords", String(describing: keywords))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func changeStickerPosition(sticker: Api.InputDocument, position: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-4795190) + sticker.serialize(buffer, true) + serializeInt32(position, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stickers.changeStickerPosition", parameters: [("sticker", String(describing: sticker)), ("position", String(describing: position))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func checkShortName(shortName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(676017721) + serializeString(shortName, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stickers.checkShortName", parameters: [("shortName", String(describing: shortName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stickers { + static func createStickerSet(flags: Int32, userId: Api.InputUser, title: String, shortName: String, thumb: Api.InputDocument?, stickers: [Api.InputStickerSetItem], software: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1876841625) + serializeInt32(flags, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + serializeString(shortName, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {thumb!.serialize(buffer, true)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers.count)) + for item in stickers { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 {serializeString(software!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "stickers.createStickerSet", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("title", String(describing: title)), ("shortName", String(describing: shortName)), ("thumb", String(describing: thumb)), ("stickers", String(describing: stickers)), ("software", String(describing: software))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func deleteStickerSet(stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2022685804) + stickerset.serialize(buffer, true) + return (FunctionDescription(name: "stickers.deleteStickerSet", parameters: [("stickerset", String(describing: stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stickers { + static func removeStickerFromSet(sticker: Api.InputDocument) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-143257775) + sticker.serialize(buffer, true) + return (FunctionDescription(name: "stickers.removeStickerFromSet", parameters: [("sticker", String(describing: sticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func renameStickerSet(stickerset: Api.InputStickerSet, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(306912256) + stickerset.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stickers.renameStickerSet", parameters: [("stickerset", String(describing: stickerset)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func replaceSticker(sticker: Api.InputDocument, newSticker: Api.InputStickerSetItem) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1184253338) + sticker.serialize(buffer, true) + newSticker.serialize(buffer, true) + return (FunctionDescription(name: "stickers.replaceSticker", parameters: [("sticker", String(describing: sticker)), ("newSticker", String(describing: newSticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func setStickerSetThumb(flags: Int32, stickerset: Api.InputStickerSet, thumb: Api.InputDocument?, thumbDocumentId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1486204014) + serializeInt32(flags, buffer: buffer, boxed: false) + stickerset.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {thumb!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt64(thumbDocumentId!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "stickers.setStickerSetThumb", parameters: [("flags", String(describing: flags)), ("stickerset", String(describing: stickerset)), ("thumb", String(describing: thumb)), ("thumbDocumentId", String(describing: thumbDocumentId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func suggestShortName(title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1303364867) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stickers.suggestShortName", parameters: [("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stickers.SuggestedShortName? in + let reader = BufferReader(buffer) + var result: Api.stickers.SuggestedShortName? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stickers.SuggestedShortName + } + return result + }) + } +} +public extension Api.functions.stories { + static func activateStealthMode(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1471926630) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.activateStealthMode", parameters: [("flags", String(describing: flags))]), 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.stories { + static func canSendStory(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-941629475) + peer.serialize(buffer, true) + return (FunctionDescription(name: "stories.canSendStory", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func deleteStories(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(-1369842849) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.deleteStories", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.stories { + static func editStory(flags: Int32, peer: Api.InputPeer, id: Int32, media: Api.InputMedia?, mediaAreas: [Api.MediaArea]?, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1249658298) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {media!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(mediaAreas!.count)) + for item in mediaAreas! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 1) != 0 {serializeString(caption!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(privacyRules!.count)) + for item in privacyRules! { + item.serialize(buffer, true) + }} + return (FunctionDescription(name: "stories.editStory", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("media", String(describing: media)), ("mediaAreas", String(describing: mediaAreas)), ("caption", String(describing: caption)), ("entities", String(describing: entities)), ("privacyRules", String(describing: privacyRules))]), 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.stories { + static func exportStoryLink(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2072899360) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.exportStoryLink", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedStoryLink? in + let reader = BufferReader(buffer) + var result: Api.ExportedStoryLink? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedStoryLink + } + return result + }) + } +} +public extension Api.functions.stories { + static func getAllReadPeerStories() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1688541191) + + return (FunctionDescription(name: "stories.getAllReadPeerStories", parameters: []), 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.stories { + static func getAllStories(flags: Int32, state: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-290400731) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(state!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "stories.getAllStories", parameters: [("flags", String(describing: flags)), ("state", String(describing: state))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.AllStories? in + let reader = BufferReader(buffer) + var result: Api.stories.AllStories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.AllStories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getChatsToSend() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1519744160) + + return (FunctionDescription(name: "stories.getChatsToSend", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.stories { + static func getPeerMaxIDs(id: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(1398375363) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "stories.getPeerMaxIDs", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.stories { + static func getPeerStories(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(743103056) + peer.serialize(buffer, true) + return (FunctionDescription(name: "stories.getPeerStories", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.PeerStories? in + let reader = BufferReader(buffer) + var result: Api.stories.PeerStories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.PeerStories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getPinnedStories(peer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1478600156) + peer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.getPinnedStories", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in + let reader = BufferReader(buffer) + var result: Api.stories.Stories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.Stories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getStoriesArchive(peer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1271586794) + peer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.getStoriesArchive", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in + let reader = BufferReader(buffer) + var result: Api.stories.Stories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.Stories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getStoriesByID(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1467271796) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.getStoriesByID", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in + let reader = BufferReader(buffer) + var result: Api.stories.Stories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.Stories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getStoriesViews(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(685862088) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.getStoriesViews", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViews? in + let reader = BufferReader(buffer) + var result: Api.stories.StoryViews? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.StoryViews + } + return result + }) + } +} +public extension Api.functions.stories { + static func getStoryReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: Api.Reaction?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1179482081) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {reaction!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(offset!, buffer: buffer, boxed: false)} + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.getStoryReactionsList", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("reaction", String(describing: reaction)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryReactionsList? in + let reader = BufferReader(buffer) + var result: Api.stories.StoryReactionsList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.StoryReactionsList + } + return result + }) + } +} +public extension Api.functions.stories { + static func getStoryViewsList(flags: Int32, peer: Api.InputPeer, q: String?, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2127707223) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {serializeString(q!, buffer: buffer, boxed: false)} + serializeInt32(id, buffer: buffer, boxed: false) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.getStoryViewsList", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("q", String(describing: q)), ("id", String(describing: id)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViewsList? in + let reader = BufferReader(buffer) + var result: Api.stories.StoryViewsList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.StoryViewsList + } + return result + }) + } +} +public extension Api.functions.stories { + static func incrementStoryViews(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1308456197) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.incrementStoryViews", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func readStories(peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(-1521034552) + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.readStories", parameters: [("peer", String(describing: peer)), ("maxId", String(describing: maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.stories { + static func report(peer: Api.InputPeer, id: [Int32], reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(421788300) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + reason.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.report", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("reason", String(describing: reason)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func sendReaction(flags: Int32, peer: Api.InputPeer, storyId: Int32, reaction: Api.Reaction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2144810674) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(storyId, buffer: buffer, boxed: false) + reaction.serialize(buffer, true) + return (FunctionDescription(name: "stories.sendReaction", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("storyId", String(describing: storyId)), ("reaction", String(describing: reaction))]), 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.stories { + static func sendStory(flags: Int32, peer: Api.InputPeer, media: Api.InputMedia, mediaAreas: [Api.MediaArea]?, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule], randomId: Int64, period: Int32?, fwdFromId: Api.InputPeer?, fwdFromStory: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-454661813) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + media.serialize(buffer, true) + if Int(flags) & Int(1 << 5) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(mediaAreas!.count)) + for item in mediaAreas! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 0) != 0 {serializeString(caption!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(privacyRules.count)) + for item in privacyRules { + item.serialize(buffer, true) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(period!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {fwdFromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 6) != 0 {serializeInt32(fwdFromStory!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "stories.sendStory", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("media", String(describing: media)), ("mediaAreas", String(describing: mediaAreas)), ("caption", String(describing: caption)), ("entities", String(describing: entities)), ("privacyRules", String(describing: privacyRules)), ("randomId", String(describing: randomId)), ("period", String(describing: period)), ("fwdFromId", String(describing: fwdFromId)), ("fwdFromStory", String(describing: fwdFromStory))]), 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.stories { + static func toggleAllStoriesHidden(hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2082822084) + hidden.serialize(buffer, true) + return (FunctionDescription(name: "stories.toggleAllStoriesHidden", parameters: [("hidden", String(describing: hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func togglePeerStoriesHidden(peer: Api.InputPeer, hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1123805756) + peer.serialize(buffer, true) + hidden.serialize(buffer, true) + return (FunctionDescription(name: "stories.togglePeerStoriesHidden", parameters: [("peer", String(describing: peer)), ("hidden", String(describing: hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func togglePinned(peer: Api.InputPeer, id: [Int32], pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(-1703566865) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + pinned.serialize(buffer, true) + return (FunctionDescription(name: "stories.togglePinned", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("pinned", String(describing: pinned))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.stories { + static func togglePinnedToTop(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(187268763) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.togglePinnedToTop", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.updates { + static func getChannelDifference(flags: Int32, channel: Api.InputChannel, filter: Api.ChannelMessagesFilter, pts: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(51854712) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + filter.serialize(buffer, true) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "updates.getChannelDifference", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("filter", String(describing: filter)), ("pts", String(describing: pts)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.ChannelDifference? in + let reader = BufferReader(buffer) + var result: Api.updates.ChannelDifference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.updates.ChannelDifference + } + return result + }) + } +} +public extension Api.functions.updates { + static func getDifference(flags: Int32, pts: Int32, ptsLimit: Int32?, ptsTotalLimit: Int32?, date: Int32, qts: Int32, qtsLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(432207715) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(ptsLimit!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ptsTotalLimit!, buffer: buffer, boxed: false)} + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(qts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(qtsLimit!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "updates.getDifference", parameters: [("flags", String(describing: flags)), ("pts", String(describing: pts)), ("ptsLimit", String(describing: ptsLimit)), ("ptsTotalLimit", String(describing: ptsTotalLimit)), ("date", String(describing: date)), ("qts", String(describing: qts)), ("qtsLimit", String(describing: qtsLimit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.Difference? in + let reader = BufferReader(buffer) + var result: Api.updates.Difference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.updates.Difference + } + return result + }) + } +} +public extension Api.functions.updates { + static func getState() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-304838614) + + return (FunctionDescription(name: "updates.getState", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.State? in + let reader = BufferReader(buffer) + var result: Api.updates.State? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.updates.State + } + return result + }) + } +} +public extension Api.functions.upload { + static func getCdnFile(fileToken: Buffer, offset: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(962554330) + serializeBytes(fileToken, buffer: buffer, boxed: false) + serializeInt64(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getCdnFile", parameters: [("fileToken", String(describing: fileToken)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.CdnFile? in + let reader = BufferReader(buffer) + var result: Api.upload.CdnFile? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.upload.CdnFile + } + return result + }) + } +} +public extension Api.functions.upload { + static func getCdnFileHashes(fileToken: Buffer, offset: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { + let buffer = Buffer() + buffer.appendInt32(-1847836879) + serializeBytes(fileToken, buffer: buffer, boxed: false) + serializeInt64(offset, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getCdnFileHashes", parameters: [("fileToken", String(describing: fileToken)), ("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in + let reader = BufferReader(buffer) + var result: [Api.FileHash]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) + } + return result + }) + } +} +public extension Api.functions.upload { + static func getFile(flags: Int32, location: Api.InputFileLocation, offset: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1101843010) + serializeInt32(flags, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt64(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getFile", parameters: [("flags", String(describing: flags)), ("location", String(describing: location)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.File? in + let reader = BufferReader(buffer) + var result: Api.upload.File? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.upload.File + } + return result + }) + } +} +public extension Api.functions.upload { + static func getFileHashes(location: Api.InputFileLocation, offset: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { + let buffer = Buffer() + buffer.appendInt32(-1856595926) + location.serialize(buffer, true) + serializeInt64(offset, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getFileHashes", parameters: [("location", String(describing: location)), ("offset", String(describing: offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in + let reader = BufferReader(buffer) + var result: [Api.FileHash]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) + } + return result + }) + } +} +public extension Api.functions.upload { + static func getWebFile(location: Api.InputWebFileLocation, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(619086221) + location.serialize(buffer, true) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getWebFile", parameters: [("location", String(describing: location)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.WebFile? in + let reader = BufferReader(buffer) + var result: Api.upload.WebFile? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.upload.WebFile + } + return result + }) + } +} +public extension Api.functions.upload { + static func reuploadCdnFile(fileToken: Buffer, requestToken: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { + let buffer = Buffer() + buffer.appendInt32(-1691921240) + serializeBytes(fileToken, buffer: buffer, boxed: false) + serializeBytes(requestToken, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.reuploadCdnFile", parameters: [("fileToken", String(describing: fileToken)), ("requestToken", String(describing: requestToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in + let reader = BufferReader(buffer) + var result: [Api.FileHash]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) + } + return result + }) + } +} +public extension Api.functions.upload { + static func saveBigFilePart(fileId: Int64, filePart: Int32, fileTotalParts: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-562337987) + serializeInt64(fileId, buffer: buffer, boxed: false) + serializeInt32(filePart, buffer: buffer, boxed: false) + serializeInt32(fileTotalParts, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.saveBigFilePart", parameters: [("fileId", String(describing: fileId)), ("filePart", String(describing: filePart)), ("fileTotalParts", String(describing: fileTotalParts)), ("bytes", String(describing: bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.upload { + static func saveFilePart(fileId: Int64, filePart: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1291540959) + serializeInt64(fileId, buffer: buffer, boxed: false) + serializeInt32(filePart, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.saveFilePart", parameters: [("fileId", String(describing: fileId)), ("filePart", String(describing: filePart)), ("bytes", String(describing: bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.users { + static func getFullUser(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1240508136) + id.serialize(buffer, true) + return (FunctionDescription(name: "users.getFullUser", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.UserFull? in + let reader = BufferReader(buffer) + var result: Api.users.UserFull? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.users.UserFull + } + return result + }) + } +} +public extension Api.functions.users { + static func getIsPremiumRequiredToContact(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Bool]>) { + let buffer = Buffer() + buffer.appendInt32(-1507677680) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "users.getIsPremiumRequiredToContact", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Bool]? in + let reader = BufferReader(buffer) + var result: [Api.Bool]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Bool.self) + } + return result + }) + } +} +public extension Api.functions.users { + static func getUsers(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.User]>) { + let buffer = Buffer() + buffer.appendInt32(227648840) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "users.getUsers", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.User]? in + let reader = BufferReader(buffer) + var result: [Api.User]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + return result + }) + } +} +public extension Api.functions.users { + static func setSecureValueErrors(id: Api.InputUser, errors: [Api.SecureValueError]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1865902923) + id.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(errors.count)) + for item in errors { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "users.setSecureValueErrors", parameters: [("id", String(describing: id)), ("errors", String(describing: errors))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index 1b56cb7fbb5..af25d1b0351 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/TelegramApi/Sources/Api9.swift b/submodules/TelegramApi/Sources/Api9.swift index 551e5d648a5..3395b6c52d9 100644 --- a/submodules/TelegramApi/Sources/Api9.swift +++ b/submodules/TelegramApi/Sources/Api9.swift @@ -911,6 +911,7 @@ public extension Api { case inputInvoiceMessage(peer: Api.InputPeer, msgId: Int32) case inputInvoicePremiumGiftCode(purpose: Api.InputStorePaymentPurpose, option: Api.PremiumGiftCodeOption) case inputInvoiceSlug(slug: String) + case inputInvoiceStars(option: Api.StarsTopupOption) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -934,6 +935,12 @@ public extension Api { } serializeString(slug, buffer: buffer, boxed: false) break + case .inputInvoiceStars(let option): + if boxed { + buffer.appendInt32(497236696) + } + option.serialize(buffer, true) + break } } @@ -945,6 +952,8 @@ public extension Api { return ("inputInvoicePremiumGiftCode", [("purpose", purpose as Any), ("option", option as Any)]) case .inputInvoiceSlug(let slug): return ("inputInvoiceSlug", [("slug", slug as Any)]) + case .inputInvoiceStars(let option): + return ("inputInvoiceStars", [("option", option as Any)]) } } @@ -993,589 +1002,14 @@ public extension Api { return nil } } - - } -} -public extension Api { - indirect enum InputMedia: TypeConstructorDescription { - case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String) - case inputMediaDice(emoticon: String) - case inputMediaDocument(flags: Int32, id: Api.InputDocument, ttlSeconds: Int32?, query: String?) - case inputMediaDocumentExternal(flags: Int32, url: String, ttlSeconds: Int32?) - case inputMediaEmpty - case inputMediaGame(id: Api.InputGame) - case inputMediaGeoLive(flags: Int32, geoPoint: Api.InputGeoPoint, heading: Int32?, period: Int32?, proximityNotificationRadius: Int32?) - case inputMediaGeoPoint(geoPoint: Api.InputGeoPoint) - case inputMediaInvoice(flags: Int32, title: String, description: String, photo: Api.InputWebDocument?, invoice: Api.Invoice, payload: Buffer, provider: String, providerData: Api.DataJSON, startParam: String?, extendedMedia: Api.InputMedia?) - case inputMediaPhoto(flags: Int32, id: Api.InputPhoto, ttlSeconds: Int32?) - case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?) - case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?) - case inputMediaStory(peer: Api.InputPeer, id: Int32) - case inputMediaUploadedDocument(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, mimeType: String, attributes: [Api.DocumentAttribute], stickers: [Api.InputDocument]?, ttlSeconds: Int32?) - case inputMediaUploadedPhoto(flags: Int32, file: Api.InputFile, stickers: [Api.InputDocument]?, ttlSeconds: Int32?) - case inputMediaVenue(geoPoint: Api.InputGeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) - case inputMediaWebPage(flags: Int32, url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputMediaContact(let phoneNumber, let firstName, let lastName, let vcard): - if boxed { - buffer.appendInt32(-122978821) - } - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - serializeString(vcard, buffer: buffer, boxed: false) - break - case .inputMediaDice(let emoticon): - if boxed { - buffer.appendInt32(-428884101) - } - serializeString(emoticon, buffer: buffer, boxed: false) - break - case .inputMediaDocument(let flags, let id, let ttlSeconds, let query): - if boxed { - buffer.appendInt32(860303448) - } - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(query!, buffer: buffer, boxed: false)} - break - case .inputMediaDocumentExternal(let flags, let url, let ttlSeconds): - if boxed { - buffer.appendInt32(-78455655) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} - break - case .inputMediaEmpty: - if boxed { - buffer.appendInt32(-1771768449) - } - - break - case .inputMediaGame(let id): - if boxed { - buffer.appendInt32(-750828557) - } - id.serialize(buffer, true) - break - case .inputMediaGeoLive(let flags, let geoPoint, let heading, let period, let proximityNotificationRadius): - if boxed { - buffer.appendInt32(-1759532989) - } - serializeInt32(flags, buffer: buffer, boxed: false) - geoPoint.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(heading!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(period!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(proximityNotificationRadius!, buffer: buffer, boxed: false)} - break - case .inputMediaGeoPoint(let geoPoint): - if boxed { - buffer.appendInt32(-104578748) - } - geoPoint.serialize(buffer, true) - break - case .inputMediaInvoice(let flags, let title, let description, let photo, let invoice, let payload, let provider, let providerData, let startParam, let extendedMedia): - if boxed { - buffer.appendInt32(-1900697899) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {photo!.serialize(buffer, true)} - invoice.serialize(buffer, true) - serializeBytes(payload, buffer: buffer, boxed: false) - serializeString(provider, buffer: buffer, boxed: false) - providerData.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {extendedMedia!.serialize(buffer, true)} - break - case .inputMediaPhoto(let flags, let id, let ttlSeconds): - if boxed { - buffer.appendInt32(-1279654347) - } - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} - break - case .inputMediaPhotoExternal(let flags, let url, let ttlSeconds): - if boxed { - buffer.appendInt32(-440664550) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} - break - case .inputMediaPoll(let flags, let poll, let correctAnswers, let solution, let solutionEntities): - if boxed { - buffer.appendInt32(261416433) - } - serializeInt32(flags, buffer: buffer, boxed: false) - poll.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(correctAnswers!.count)) - for item in correctAnswers! { - serializeBytes(item, buffer: buffer, boxed: false) - }} - if Int(flags) & Int(1 << 1) != 0 {serializeString(solution!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(solutionEntities!.count)) - for item in solutionEntities! { - item.serialize(buffer, true) - }} - break - case .inputMediaStory(let peer, let id): - if boxed { - buffer.appendInt32(-1979852936) - } - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - break - case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let ttlSeconds): - if boxed { - buffer.appendInt32(1530447553) - } - serializeInt32(flags, buffer: buffer, boxed: false) - file.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {thumb!.serialize(buffer, true)} - serializeString(mimeType, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers!.count)) - for item in stickers! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} - break - case .inputMediaUploadedPhoto(let flags, let file, let stickers, let ttlSeconds): - if boxed { - buffer.appendInt32(505969924) - } - serializeInt32(flags, buffer: buffer, boxed: false) - file.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers!.count)) - for item in stickers! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} - break - case .inputMediaVenue(let geoPoint, let title, let address, let provider, let venueId, let venueType): - if boxed { - buffer.appendInt32(-1052959727) - } - geoPoint.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - serializeString(address, buffer: buffer, boxed: false) - serializeString(provider, buffer: buffer, boxed: false) - serializeString(venueId, buffer: buffer, boxed: false) - serializeString(venueType, buffer: buffer, boxed: false) - break - case .inputMediaWebPage(let flags, let url): - if boxed { - buffer.appendInt32(-1038383031) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputMediaContact(let phoneNumber, let firstName, let lastName, let vcard): - return ("inputMediaContact", [("phoneNumber", phoneNumber as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("vcard", vcard as Any)]) - case .inputMediaDice(let emoticon): - return ("inputMediaDice", [("emoticon", emoticon as Any)]) - case .inputMediaDocument(let flags, let id, let ttlSeconds, let query): - return ("inputMediaDocument", [("flags", flags as Any), ("id", id as Any), ("ttlSeconds", ttlSeconds as Any), ("query", query as Any)]) - case .inputMediaDocumentExternal(let flags, let url, let ttlSeconds): - return ("inputMediaDocumentExternal", [("flags", flags as Any), ("url", url as Any), ("ttlSeconds", ttlSeconds as Any)]) - case .inputMediaEmpty: - return ("inputMediaEmpty", []) - case .inputMediaGame(let id): - return ("inputMediaGame", [("id", id as Any)]) - case .inputMediaGeoLive(let flags, let geoPoint, let heading, let period, let proximityNotificationRadius): - return ("inputMediaGeoLive", [("flags", flags as Any), ("geoPoint", geoPoint as Any), ("heading", heading as Any), ("period", period as Any), ("proximityNotificationRadius", proximityNotificationRadius as Any)]) - case .inputMediaGeoPoint(let geoPoint): - return ("inputMediaGeoPoint", [("geoPoint", geoPoint as Any)]) - case .inputMediaInvoice(let flags, let title, let description, let photo, let invoice, let payload, let provider, let providerData, let startParam, let extendedMedia): - return ("inputMediaInvoice", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("payload", payload as Any), ("provider", provider as Any), ("providerData", providerData as Any), ("startParam", startParam as Any), ("extendedMedia", extendedMedia as Any)]) - case .inputMediaPhoto(let flags, let id, let ttlSeconds): - return ("inputMediaPhoto", [("flags", flags as Any), ("id", id as Any), ("ttlSeconds", ttlSeconds as Any)]) - case .inputMediaPhotoExternal(let flags, let url, let ttlSeconds): - return ("inputMediaPhotoExternal", [("flags", flags as Any), ("url", url as Any), ("ttlSeconds", ttlSeconds as Any)]) - case .inputMediaPoll(let flags, let poll, let correctAnswers, let solution, let solutionEntities): - return ("inputMediaPoll", [("flags", flags as Any), ("poll", poll as Any), ("correctAnswers", correctAnswers as Any), ("solution", solution as Any), ("solutionEntities", solutionEntities as Any)]) - case .inputMediaStory(let peer, let id): - return ("inputMediaStory", [("peer", peer as Any), ("id", id as Any)]) - case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let ttlSeconds): - return ("inputMediaUploadedDocument", [("flags", flags as Any), ("file", file as Any), ("thumb", thumb as Any), ("mimeType", mimeType as Any), ("attributes", attributes as Any), ("stickers", stickers as Any), ("ttlSeconds", ttlSeconds as Any)]) - case .inputMediaUploadedPhoto(let flags, let file, let stickers, let ttlSeconds): - return ("inputMediaUploadedPhoto", [("flags", flags as Any), ("file", file as Any), ("stickers", stickers as Any), ("ttlSeconds", ttlSeconds as Any)]) - case .inputMediaVenue(let geoPoint, let title, let address, let provider, let venueId, let venueType): - return ("inputMediaVenue", [("geoPoint", geoPoint as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)]) - case .inputMediaWebPage(let flags, let url): - return ("inputMediaWebPage", [("flags", flags as Any), ("url", url as Any)]) - } - } - - public static func parse_inputMediaContact(_ reader: BufferReader) -> InputMedia? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputMedia.inputMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, vcard: _4!) - } - else { - return nil - } - } - public static func parse_inputMediaDice(_ reader: BufferReader) -> InputMedia? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.InputMedia.inputMediaDice(emoticon: _1!) - } - else { - return nil - } - } - public static func parse_inputMediaDocument(_ reader: BufferReader) -> InputMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputDocument? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputDocument - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputMedia.inputMediaDocument(flags: _1!, id: _2!, ttlSeconds: _3, query: _4) - } - else { - return nil - } - } - public static func parse_inputMediaDocumentExternal(_ reader: BufferReader) -> InputMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputMedia.inputMediaDocumentExternal(flags: _1!, url: _2!, ttlSeconds: _3) - } - else { - return nil - } - } - public static func parse_inputMediaEmpty(_ reader: BufferReader) -> InputMedia? { - return Api.InputMedia.inputMediaEmpty - } - public static func parse_inputMediaGame(_ reader: BufferReader) -> InputMedia? { - var _1: Api.InputGame? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGame - } - let _c1 = _1 != nil - if _c1 { - return Api.InputMedia.inputMediaGame(id: _1!) - } - else { - return nil - } - } - public static func parse_inputMediaGeoLive(_ reader: BufferReader) -> InputMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputGeoPoint? + public static func parse_inputInvoiceStars(_ reader: BufferReader) -> InputInvoice? { + var _1: Api.StarsTopupOption? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint - } - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } - var _5: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputMedia.inputMediaGeoLive(flags: _1!, geoPoint: _2!, heading: _3, period: _4, proximityNotificationRadius: _5) - } - else { - return nil - } - } - public static func parse_inputMediaGeoPoint(_ reader: BufferReader) -> InputMedia? { - var _1: Api.InputGeoPoint? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint + _1 = Api.parse(reader, signature: signature) as? Api.StarsTopupOption } let _c1 = _1 != nil if _c1 { - return Api.InputMedia.inputMediaGeoPoint(geoPoint: _1!) - } - else { - return nil - } - } - public static func parse_inputMediaInvoice(_ reader: BufferReader) -> InputMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: Api.InputWebDocument? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.InputWebDocument - } } - var _5: Api.Invoice? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Invoice - } - var _6: Buffer? - _6 = parseBytes(reader) - var _7: String? - _7 = parseString(reader) - var _8: Api.DataJSON? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - var _9: String? - if Int(_1!) & Int(1 << 1) != 0 {_9 = parseString(reader) } - var _10: Api.InputMedia? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.InputMedia - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.InputMedia.inputMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, invoice: _5!, payload: _6!, provider: _7!, providerData: _8!, startParam: _9, extendedMedia: _10) - } - else { - return nil - } - } - public static func parse_inputMediaPhoto(_ reader: BufferReader) -> InputMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputPhoto? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputPhoto - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputMedia.inputMediaPhoto(flags: _1!, id: _2!, ttlSeconds: _3) - } - else { - return nil - } - } - public static func parse_inputMediaPhotoExternal(_ reader: BufferReader) -> InputMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputMedia.inputMediaPhotoExternal(flags: _1!, url: _2!, ttlSeconds: _3) - } - else { - return nil - } - } - public static func parse_inputMediaPoll(_ reader: BufferReader) -> InputMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Poll? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Poll - } - var _3: [Buffer]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) - } } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - var _5: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputMedia.inputMediaPoll(flags: _1!, poll: _2!, correctAnswers: _3, solution: _4, solutionEntities: _5) - } - else { - return nil - } - } - public static func parse_inputMediaStory(_ reader: BufferReader) -> InputMedia? { - var _1: Api.InputPeer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputMedia.inputMediaStory(peer: _1!, id: _2!) - } - else { - return nil - } - } - public static func parse_inputMediaUploadedDocument(_ reader: BufferReader) -> InputMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputFile? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputFile - } - var _3: Api.InputFile? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.InputFile - } } - var _4: String? - _4 = parseString(reader) - var _5: [Api.DocumentAttribute]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) - } - var _6: [Api.InputDocument]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) - } } - var _7: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_7 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.InputMedia.inputMediaUploadedDocument(flags: _1!, file: _2!, thumb: _3, mimeType: _4!, attributes: _5!, stickers: _6, ttlSeconds: _7) - } - else { - return nil - } - } - public static func parse_inputMediaUploadedPhoto(_ reader: BufferReader) -> InputMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputFile? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputFile - } - var _3: [Api.InputDocument]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) - } } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputMedia.inputMediaUploadedPhoto(flags: _1!, file: _2!, stickers: _3, ttlSeconds: _4) - } - else { - return nil - } - } - public static func parse_inputMediaVenue(_ reader: BufferReader) -> InputMedia? { - var _1: Api.InputGeoPoint? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint - } - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputMedia.inputMediaVenue(geoPoint: _1!, title: _2!, address: _3!, provider: _4!, venueId: _5!, venueType: _6!) - } - else { - return nil - } - } - public static func parse_inputMediaWebPage(_ reader: BufferReader) -> InputMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputMedia.inputMediaWebPage(flags: _1!, url: _2!) + return Api.InputInvoice.inputInvoiceStars(option: _1!) } else { return nil diff --git a/submodules/TelegramAudio/BUILD b/submodules/TelegramAudio/BUILD index 319722988c1..156a5387ea4 100644 --- a/submodules/TelegramAudio/BUILD +++ b/submodules/TelegramAudio/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramBaseController/BUILD b/submodules/TelegramBaseController/BUILD index 52f34d888ae..53737aabac0 100644 --- a/submodules/TelegramBaseController/BUILD +++ b/submodules/TelegramBaseController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index 7193bd11f75..b73b781fa96 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -47,7 +47,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":TelegramCallsUIBundle", diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 49ef3745759..d5844c0efdc 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1704,7 +1704,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let strongSelf = self else { return } @@ -2517,7 +2517,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuDisplayAsItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + c?.setItems(strongSelf.contextMenuDisplayAsItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) items.append(.separator) break @@ -2550,7 +2550,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuAudioItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + c?.setItems(strongSelf.contextMenuAudioItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) } @@ -2587,7 +2587,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuPermissionItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + c?.setItems(strongSelf.contextMenuPermissionItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) } } @@ -2865,7 +2865,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + c?.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) return .single(items) } @@ -2960,7 +2960,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + c?.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) return items } @@ -3006,7 +3006,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + c?.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) } return .single(items) diff --git a/submodules/TelegramCore/BUILD b/submodules/TelegramCore/BUILD index 2d8a1a61bc7..50c87afcee0 100644 --- a/submodules/TelegramCore/BUILD +++ b/submodules/TelegramCore/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramApi:TelegramApi", diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index b761ba9e682..d52b1dc7b51 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -128,7 +128,8 @@ enum AccountStateMutationOperation { case UpdateStorySentReaction(peerId: PeerId, id: Int32, reaction: Api.Reaction) case UpdateNewAuthorization(isUnconfirmed: Bool, hash: Int64, date: Int32, device: String, location: String) case UpdateWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?) - case UpdateRevenueBalances(RevenueStats.Balances) + case UpdateRevenueBalances(peerId: PeerId, balances: RevenueStats.Balances) + case UpdateStarsBalance(peerId: PeerId, balance: Int64) } struct HoleFromPreviousState { @@ -674,13 +675,17 @@ struct AccountMutableState { self.addOperation(.UpdateNewAuthorization(isUnconfirmed: isUnconfirmed, hash: hash, date: date, device: device, location: location)) } - mutating func updateRevenueBalances(_ balances: RevenueStats.Balances) { - self.addOperation(.UpdateRevenueBalances(balances)) + mutating func updateRevenueBalances(peerId: PeerId, balances: RevenueStats.Balances) { + self.addOperation(.UpdateRevenueBalances(peerId: peerId, balances: balances)) + } + + mutating func updateStarsBalance(peerId: PeerId, balance: Int64) { + self.addOperation(.UpdateStarsBalance(peerId: peerId, balance: balance)) } mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateRevenueBalances: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateRevenueBalances, .UpdateStarsBalance: break case let .AddMessages(messages, location): for message in messages { @@ -824,7 +829,8 @@ struct AccountReplayedFinalState { let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] let updateConfig: Bool let isPremiumUpdated: Bool - let updatedRevenueBalances: RevenueStats.Balances? + let updatedRevenueBalances: [PeerId: RevenueStats.Balances] + let updatedStarsBalance: [PeerId: Int64] } struct AccountFinalStateEvents { @@ -851,13 +857,14 @@ struct AccountFinalStateEvents { let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] let updateConfig: Bool let isPremiumUpdated: Bool - let updatedRevenueBalances: RevenueStats.Balances? + let updatedRevenueBalances: [PeerId: RevenueStats.Balances] + let updatedStarsBalance: [PeerId: Int64] var isEmpty: Bool { - return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances == nil + return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty } - init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: RevenueStats.Balances? = nil) { + init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: Int64] = [:]) { self.addedIncomingMessageIds = addedIncomingMessageIds self.addedReactionEvents = addedReactionEvents self.wasScheduledMessageIds = wasScheduledMessageIds @@ -882,6 +889,7 @@ struct AccountFinalStateEvents { self.updateConfig = updateConfig self.isPremiumUpdated = isPremiumUpdated self.updatedRevenueBalances = updatedRevenueBalances + self.updatedStarsBalance = updatedStarsBalance } init(state: AccountReplayedFinalState) { @@ -909,6 +917,7 @@ struct AccountFinalStateEvents { self.updateConfig = state.updateConfig self.isPremiumUpdated = state.isPremiumUpdated self.updatedRevenueBalances = state.updatedRevenueBalances + self.updatedStarsBalance = state.updatedStarsBalance } func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents { @@ -938,6 +947,6 @@ struct AccountFinalStateEvents { let isPremiumUpdated = self.isPremiumUpdated || other.isPremiumUpdated - return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances != nil ? self.updatedRevenueBalances : other.updatedRevenueBalances) + return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs })) } } diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 266758311bb..6e819bfe916 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -296,6 +296,7 @@ private var declaredEncodables: Void = { declareEncodable(MediaSpoilerMessageAttribute.self, f: { MediaSpoilerMessageAttribute(decoder: $0) }) declareEncodable(AuthSessionInfoAttribute.self, f: { AuthSessionInfoAttribute(decoder: $0) }) declareEncodable(TranslationMessageAttribute.self, f: { TranslationMessageAttribute(decoder: $0) }) + declareEncodable(TranslationMessageAttribute.Additional.self, f: { TranslationMessageAttribute.Additional(decoder: $0) }) declareEncodable(SynchronizeAutosaveItemOperation.self, f: { SynchronizeAutosaveItemOperation(decoder: $0) }) declareEncodable(TelegramMediaStory.self, f: { TelegramMediaStory(decoder: $0) }) declareEncodable(SynchronizeViewStoriesOperation.self, f: { SynchronizeViewStoriesOperation(decoder: $0) }) @@ -304,9 +305,12 @@ private var declaredEncodables: Void = { declareEncodable(TelegramMediaGiveaway.self, f: { TelegramMediaGiveaway(decoder: $0) }) declareEncodable(TelegramMediaGiveawayResults.self, f: { TelegramMediaGiveawayResults(decoder: $0) }) declareEncodable(WebpagePreviewMessageAttribute.self, f: { WebpagePreviewMessageAttribute(decoder: $0) }) + declareEncodable(InvertMediaMessageAttribute.self, f: { InvertMediaMessageAttribute(decoder: $0) }) declareEncodable(DerivedDataMessageAttribute.self, f: { DerivedDataMessageAttribute(decoder: $0) }) declareEncodable(TelegramApplicationIcons.self, f: { TelegramApplicationIcons(decoder: $0) }) declareEncodable(OutgoingQuickReplyMessageAttribute.self, f: { OutgoingQuickReplyMessageAttribute(decoder: $0) }) + declareEncodable(EffectMessageAttribute.self, f: { EffectMessageAttribute(decoder: $0) }) + declareEncodable(FactCheckMessageAttribute.self, f: { FactCheckMessageAttribute(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index d6b2e230e1f..f6656ad29b1 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 @@ -593,8 +593,8 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Underline)) case let .messageEntityStrike(offset, length): result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Strikethrough)) - case let .messageEntityBlockquote(offset, length): - result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BlockQuote)) + case let .messageEntityBlockquote(flags, offset, length): + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BlockQuote(isCollapsed: (flags & (1 << 0)) != 0))) case let .messageEntityBankCard(offset, length): result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BankCard)) case let .messageEntitySpoiler(offset, length): @@ -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): + 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, factCheck): let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId var namespace = namespace @@ -781,6 +781,12 @@ extension StoreMessage { attributes.append(WebpagePreviewMessageAttribute(leadingPreview: leadingPreview, forceLargeMedia: webpageAttributes.forceLargeMedia, isManuallyAdded: webpageAttributes.isManuallyAdded, isSafe: webpageAttributes.isSafe)) } } + + let leadingPreview = (flags & (1 << 27)) != 0 + if leadingPreview { + attributes.append(InvertMediaMessageAttribute()) + } + } } @@ -887,6 +893,26 @@ extension StoreMessage { if let restrictionReason = restrictionReason { attributes.append(RestrictedContentMessageAttribute(rules: restrictionReason.map(RestrictionRule.init(apiReason:)))) } + + if let messageEffectId { + attributes.append(EffectMessageAttribute(id: messageEffectId)) + } + + if let factCheck { + switch factCheck { + case let .factCheck(_, country, text, hash): + let content: FactCheckMessageAttribute.Content + if let text, let country { + switch text { + case let .textWithEntities(text, entities): + content = .Loaded(text: text, entities: messageTextEntitiesFromApiEntities(entities), country: country) + } + } else { + content = .Pending + } + attributes.append(FactCheckMessageAttribute(content: content, hash: hash)) + } + } var storeFlags = StoreMessageFlags() diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramInvertMediaMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramInvertMediaMessageAttribute.swift new file mode 100644 index 00000000000..d0b06172726 --- /dev/null +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramInvertMediaMessageAttribute.swift @@ -0,0 +1,23 @@ + +import Foundation +import Postbox +import TelegramApi + +public class InvertMediaMessageAttribute: MessageAttribute, Equatable { + public let associatedPeerIds: [PeerId] = [] + public let associatedMediaIds: [MediaId] = [] + + + public init() { + } + + required public init(decoder: PostboxDecoder) { + } + + public func encode(_ encoder: PostboxEncoder) { + } + + public static func ==(lhs: InvertMediaMessageAttribute, rhs: InvertMediaMessageAttribute) -> Bool { + return true + } +} diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebFile.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebFile.swift deleted file mode 100644 index 8b137891791..00000000000 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebFile.swift +++ /dev/null @@ -1 +0,0 @@ - diff --git a/submodules/TelegramCore/Sources/ApiUtils/TextEntitiesMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/TextEntitiesMessageAttribute.swift index 15e1f0d0054..b2ce7048270 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TextEntitiesMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TextEntitiesMessageAttribute.swift @@ -40,8 +40,12 @@ func apiEntitiesFromMessageTextEntities(_ entities: [MessageTextEntity], associa break case .Strikethrough: apiEntities.append(.messageEntityStrike(offset: offset, length: length)) - case .BlockQuote: - apiEntities.append(.messageEntityBlockquote(offset: offset, length: length)) + case let .BlockQuote(isCollapsed): + var flags: Int32 = 0 + if isCollapsed { + flags |= 1 << 0 + } + apiEntities.append(.messageEntityBlockquote(flags: flags, offset: offset, length: length)) case .Underline: apiEntities.append(.messageEntityUnderline(offset: offset, length: length)) case .BankCard: diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index 6ccab4d3aa9..f7e5bd5e3b5 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -144,11 +144,10 @@ enum SendFirebaseAuthorizationCodeError { case generic } -private func sendFirebaseAuthorizationCode(accountManager: AccountManager, account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String, phoneCodeHash: String, timeout: Int32?, firebaseSecret: String, syncContacts: Bool) -> Signal { - //auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool; +func sendFirebaseAuthorizationCode(network: Network, phoneNumber: String, apiId: Int32, apiHash: String, phoneCodeHash: String, timeout: Int32?, firebaseSecret: String) -> Signal { var flags: Int32 = 0 flags |= 1 << 1 - return account.network.request(Api.functions.auth.requestFirebaseSms(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, safetyNetToken: nil, iosPushSecret: firebaseSecret)) + return network.request(Api.functions.auth.requestFirebaseSms(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, safetyNetToken: nil, playIntegrityToken: nil, iosPushSecret: firebaseSecret)) |> mapError { _ -> SendFirebaseAuthorizationCodeError in return .generic } @@ -290,7 +289,7 @@ public func sendAuthorizationCode(accountManager: AccountManager map { mapping -> String? in guard let receipt = receipt else { @@ -310,10 +309,10 @@ public func sendAuthorizationCode(accountManager: AccountManager castError(AuthorizationCodeRequestError.self) |> mapToSignal { firebaseSecret -> Signal in guard let firebaseSecret = firebaseSecret else { - return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: phoneNumber, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream) + return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: phoneNumber, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream, reason: .firebasePushTimeout) } - return sendFirebaseAuthorizationCode(accountManager: accountManager, account: account, phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret, syncContacts: syncContacts) + return sendFirebaseAuthorizationCode(network: account.network, phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret) |> `catch` { _ -> Signal in return .single(false) } @@ -347,7 +346,7 @@ public func sendAuthorizationCode(accountManager: AccountManager castError(AuthorizationCodeRequestError.self) } else { - return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: phoneNumber, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream) + return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: phoneNumber, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream, reason: .firebaseSendCodeError) } } } @@ -409,8 +408,20 @@ public func sendAuthorizationCode(accountManager: AccountManager, account: UnauthorizedAccount, number: String, apiId: Int32, apiHash: String, hash: String, syncContacts: Bool, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal { - return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false) +enum ResendAuthorizationCodeReason: String { + case firebasePushTimeout = "FIREBASE_PUSH_TIMEOUT" + case firebaseSendCodeError = "FIREBASE_SEND_CODE_ERROR" +} + +private func internalResendAuthorizationCode(accountManager: AccountManager, account: UnauthorizedAccount, number: String, apiId: Int32, apiHash: String, hash: String, syncContacts: Bool, firebaseSecretStream: Signal<[String: String], NoError>, reason: ResendAuthorizationCodeReason?) -> Signal { + var flags: Int32 = 0 + var mappedReason: String? + if let reason { + flags |= 1 << 0 + mappedReason = reason.rawValue + } + + return account.network.request(Api.functions.auth.resendCode(flags: flags, phoneNumber: number, phoneCodeHash: hash, reason: mappedReason), automaticFloodWait: false) |> mapError { error -> AuthorizationCodeRequestError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded @@ -436,7 +447,7 @@ private func internalResendAuthorizationCode(accountManager: AccountManager map { mapping -> String? in guard let receipt = receipt else { @@ -456,10 +467,10 @@ private func internalResendAuthorizationCode(accountManager: AccountManager castError(AuthorizationCodeRequestError.self) |> mapToSignal { firebaseSecret -> Signal in guard let firebaseSecret = firebaseSecret else { - return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream) + return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream, reason: .firebasePushTimeout) } - return sendFirebaseAuthorizationCode(accountManager: accountManager, account: account, phoneNumber: number, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret, syncContacts: syncContacts) + return sendFirebaseAuthorizationCode(network: account.network, phoneNumber: number, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret) |> `catch` { _ -> Signal in return .single(false) } @@ -493,7 +504,7 @@ private func internalResendAuthorizationCode(accountManager: AccountManager castError(AuthorizationCodeRequestError.self) } else { - return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream) + return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream, reason: .firebaseSendCodeError) } } } @@ -535,7 +546,7 @@ public func resendAuthorizationCode(accountManager: AccountManager mapError { error -> AuthorizationCodeRequestError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded @@ -578,7 +589,7 @@ public func resendAuthorizationCode(accountManager: AccountManager map { mapping -> String? in guard let receipt = receipt else { @@ -598,10 +609,10 @@ public func resendAuthorizationCode(accountManager: AccountManager castError(AuthorizationCodeRequestError.self) |> mapToSignal { firebaseSecret -> Signal in guard let firebaseSecret = firebaseSecret else { - return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream) + return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream, reason: .firebasePushTimeout) } - return sendFirebaseAuthorizationCode(accountManager: accountManager, account: account, phoneNumber: number, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret, syncContacts: syncContacts) + return sendFirebaseAuthorizationCode(network: account.network, phoneNumber: number, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret) |> `catch` { _ -> Signal in return .single(false) } @@ -617,7 +628,7 @@ public func resendAuthorizationCode(accountManager: AccountManager castError(AuthorizationCodeRequestError.self) } else { - return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream) + return internalResendAuthorizationCode(accountManager: accountManager, account: account, number: number, apiId: apiId, apiHash: apiHash, hash: phoneCodeHash, syncContacts: syncContacts, firebaseSecretStream: firebaseSecretStream, reason: .firebaseSendCodeError) } } } diff --git a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift index d31307d64d2..fd2ee567768 100644 --- a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift +++ b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift @@ -550,8 +550,8 @@ final class MediaReferenceRevalidationContext { return self.genericItem(key: .savedStickers, background: background, request: { next, error in let loadSavedStickers: Signal<[TelegramMediaFile], NoError> = postbox.transaction { transaction -> [TelegramMediaFile] in return transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers).compactMap({ item -> TelegramMediaFile? in - if let contents = item.contents.get(RecentMediaItem.self) { - let file = contents.media + if let contents = item.contents.get(SavedStickerItem.self) { + let file = contents.file return file } return nil diff --git a/submodules/TelegramCore/Sources/Network/Network.swift b/submodules/TelegramCore/Sources/Network/Network.swift index 99dcd097fb5..f7e2c038895 100644 --- a/submodules/TelegramCore/Sources/Network/Network.swift +++ b/submodules/TelegramCore/Sources/Network/Network.swift @@ -434,13 +434,14 @@ public struct NetworkInitializationArguments { public let voipMaxLayer: Int32 public let voipVersions: [CallSessionManagerImplementationVersion] public let appData: Signal + public let externalRequestVerificationStream: Signal<[String: String], NoError> public let autolockDeadine: Signal public let encryptionProvider: EncryptionProvider public let deviceModelName:String? public let useBetaFeatures: Bool public let isICloudEnabled: Bool - public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [CallSessionManagerImplementationVersion], appData: Signal, autolockDeadine: Signal, encryptionProvider: EncryptionProvider, deviceModelName: String?, useBetaFeatures: Bool, isICloudEnabled: Bool) { + public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [CallSessionManagerImplementationVersion], appData: Signal, externalRequestVerificationStream: Signal<[String: String], NoError>, autolockDeadine: Signal, encryptionProvider: EncryptionProvider, deviceModelName: String?, useBetaFeatures: Bool, isICloudEnabled: Bool) { self.apiId = apiId self.apiHash = apiHash self.languagesCategory = languagesCategory @@ -448,6 +449,7 @@ public struct NetworkInitializationArguments { self.voipMaxLayer = voipMaxLayer self.voipVersions = voipVersions self.appData = appData + self.externalRequestVerificationStream = externalRequestVerificationStream self.autolockDeadine = autolockDeadine self.encryptionProvider = encryptionProvider self.deviceModelName = deviceModelName @@ -573,6 +575,25 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa if !supplementary { context.setDiscoverBackupAddressListSignal(MTBackupAddressSignals.fetchBackupIps(testingEnvironment, currentContext: context, additionalSource: wrappedAdditionalSource, phoneNumber: phoneNumber, mainDatacenterId: datacenterId)) + let externalRequestVerificationStream = arguments.externalRequestVerificationStream + context.setExternalRequestVerification({ nonce in + return MTSignal(generator: { subscriber in + let disposable = (externalRequestVerificationStream + |> map { dict -> String? in + return dict[nonce] + } + |> filter { $0 != nil } + |> take(1) + |> timeout(15.0, queue: .mainQueue(), alternate: .single("APNS_PUSH_TIMEOUT"))).start(next: { secret in + subscriber?.putNext(secret) + subscriber?.putCompletion() + }) + + return MTBlockDisposable(block: { + disposable.dispose() + }) + }) + }) } /*#if DEBUG diff --git a/submodules/TelegramCore/Sources/PendingMessages/ChatUpdatingMessageMedia.swift b/submodules/TelegramCore/Sources/PendingMessages/ChatUpdatingMessageMedia.swift index 55ead0e540a..8c0399f82be 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/ChatUpdatingMessageMedia.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/ChatUpdatingMessageMedia.swift @@ -6,13 +6,15 @@ public final class ChatUpdatingMessageMedia: Equatable { public let entities: TextEntitiesMessageAttribute? public let disableUrlPreview: Bool public let media: RequestEditMessageMedia + public let invertMediaAttribute: InvertMediaMessageAttribute? public let progress: Float - init(text: String, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, media: RequestEditMessageMedia, progress: Float) { + init(text: String, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, media: RequestEditMessageMedia, invertMediaAttribute: InvertMediaMessageAttribute?, progress: Float) { self.text = text self.entities = entities self.disableUrlPreview = disableUrlPreview self.media = media + self.invertMediaAttribute = invertMediaAttribute self.progress = progress } @@ -29,6 +31,9 @@ public final class ChatUpdatingMessageMedia: Equatable { if lhs.media != rhs.media { return false } + if (lhs.invertMediaAttribute == nil) != (rhs.invertMediaAttribute == nil) { + return false + } if lhs.progress != rhs.progress { return false } @@ -36,6 +41,6 @@ public final class ChatUpdatingMessageMedia: Equatable { } func withProgress(_ progress: Float) -> ChatUpdatingMessageMedia { - return ChatUpdatingMessageMedia(text: self.text, entities: self.entities, disableUrlPreview: self.disableUrlPreview, media: self.media, progress: progress) + return ChatUpdatingMessageMedia(text: self.text, entities: self.entities, disableUrlPreview: self.disableUrlPreview, media: self.media, invertMediaAttribute: self.invertMediaAttribute, progress: progress) } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 878304411a3..de64ee2fc1a 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -248,6 +248,10 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt return true case _ as WebpagePreviewMessageAttribute: return true + case _ as InvertMediaMessageAttribute: + return true + case _ as EffectMessageAttribute: + return true default: return false } @@ -273,6 +277,8 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt return true case _ as MediaSpoilerMessageAttribute: return true + case _ as InvertMediaMessageAttribute: + return true case let attribute as ReplyMessageAttribute: if attribute.quote != nil { return true diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift index ee1dc96a755..ba3623cb251 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift @@ -64,18 +64,18 @@ private final class PendingUpdateMessageManagerImpl { } } - func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool) { + func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?, disableUrlPreview: Bool) { if let context = self.contexts[messageId] { self.contexts.removeValue(forKey: messageId) context.disposable.dispose() } let disposable = MetaDisposable() - let context = PendingUpdateMessageContext(value: ChatUpdatingMessageMedia(text: text, entities: entities, disableUrlPreview: disableUrlPreview, media: media, progress: 0.0), disposable: disposable) + let context = PendingUpdateMessageContext(value: ChatUpdatingMessageMedia(text: text, entities: entities, disableUrlPreview: disableUrlPreview, media: media, invertMediaAttribute: invertMediaAttribute, progress: 0.0), disposable: disposable) self.contexts[messageId] = context let queue = self.queue - disposable.set((requestEditMessage(accountPeerId: self.stateManager.accountPeerId, postbox: self.postbox, network: self.network, stateManager: self.stateManager, transformOutgoingMessageMedia: self.transformOutgoingMessageMedia, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: nil) + disposable.set((requestEditMessage(accountPeerId: self.stateManager.accountPeerId, postbox: self.postbox, network: self.network, stateManager: self.stateManager, transformOutgoingMessageMedia: self.transformOutgoingMessageMedia, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: nil, invertMediaAttribute: invertMediaAttribute) |> deliverOn(self.queue)).start(next: { [weak self, weak context] value in queue.async { guard let strongSelf = self, let initialContext = context else { @@ -163,9 +163,9 @@ public final class PendingUpdateMessageManager { }) } - public func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, disableUrlPreview: Bool = false) { + public func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, invertMediaAttribute: InvertMediaMessageAttribute? = nil, disableUrlPreview: Bool = false) { self.impl.with { impl in - impl.add(messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview) + impl.add(messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview) } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift index d9dd993fbfa..c298e8aac5b 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift @@ -27,15 +27,15 @@ public enum RequestEditMessageError { case invalidGrouping } -func _internal_requestEditMessage(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?) -> Signal { - return requestEditMessage(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, stateManager: account.stateManager, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime) +func _internal_requestEditMessage(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal { + return requestEditMessage(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, stateManager: account.stateManager, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, invertMediaAttribute: invertMediaAttribute) } -func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?) -> Signal { - return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: false) +func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal { + return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: false) |> `catch` { error -> Signal in if case .invalidReference = error { - return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: true) + return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: true) } else { return .fail(error) } @@ -50,7 +50,7 @@ func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Networ } } -private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?, forceReupload: Bool) -> Signal { +private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?, forceReupload: Bool) -> Signal { let uploadedMedia: Signal switch media { case .keep: @@ -63,6 +63,9 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, if let webpagePreviewAttribute = webpagePreviewAttribute { attributes.append(webpagePreviewAttribute) } + if let invertMediaAttribute { + attributes.append(invertMediaAttribute) + } return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: attributes, mediaReference: nil) } if let uploadSignal = generateUploadSignal(forceReupload) { @@ -168,10 +171,8 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, flags |= Int32(1 << 15) } - if let webpagePreviewAttribute = webpagePreviewAttribute { - if webpagePreviewAttribute.leadingPreview { - flags |= Int32(1 << 16) - } + if let _ = invertMediaAttribute { + flags |= Int32(1 << 16) } var quickReplyShortcutId: Int32? diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index bfeb8299480..7046de43f7c 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -410,7 +410,7 @@ private func sendUploadedMessageContent( } } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil), info: .acknowledgement, tag: dependencyTag) + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -432,7 +432,7 @@ private func sendUploadedMessageContent( } } - sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): var topMsgId: Int32? @@ -633,7 +633,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M } } - sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil)) + sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil)) |> `catch` { _ -> Signal in return .complete() } @@ -651,7 +651,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M } } - sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil)) + sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil)) |> `catch` { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/Sources/State/AccountState.swift b/submodules/TelegramCore/Sources/State/AccountState.swift index 18757d2fb63..554adf98454 100644 --- a/submodules/TelegramCore/Sources/State/AccountState.swift +++ b/submodules/TelegramCore/Sources/State/AccountState.swift @@ -36,7 +36,7 @@ extension SentAuthorizationCodeType { self = .emailSetupRequired(appleSignInAllowed: (flags & (1 << 0)) != 0) case let .sentCodeTypeFragmentSms(url, length): self = .fragment(url: url, length: length) - case let .sentCodeTypeFirebaseSms(_, _, _, pushTimeout, length): + case let .sentCodeTypeFirebaseSms(_, _, _, _, pushTimeout, length): self = .firebase(pushTimeout: pushTimeout, length: length) case let .sentCodeTypeSmsWord(_, beginning): self = .word(startsWith: beginning) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index efc40febf2a..49b340a520f 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1776,8 +1776,10 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.updateNewAuthorization(isUnconfirmed: isUnconfirmed, hash: hash, date: date ?? 0, device: device ?? "", location: location ?? "") case let .updatePeerWallpaper(_, peer, wallpaper): updatedState.updateWallpaper(peerId: peer.peerId, wallpaper: wallpaper.flatMap { TelegramWallpaper(apiWallpaper: $0) }) - case let .updateBroadcastRevenueTransactions(balances): - updatedState.updateRevenueBalances(RevenueStats.Balances(apiRevenueBalances: balances)) + case let .updateBroadcastRevenueTransactions(peer, balances): + updatedState.updateRevenueBalances(peerId: peer.peerId, balances: RevenueStats.Balances(apiRevenueBalances: balances)) + case let .updateStarsBalance(balance): + updatedState.updateStarsBalance(peerId: accountPeerId, balance: balance) default: break } @@ -3269,7 +3271,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddQuickReplyMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateRevenueBalances: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateRevenueBalances, .UpdateStarsBalance: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -3402,7 +3404,8 @@ func replayFinalState( var deletedMessageIds: [DeletedMessageId] = [] var syncAttachMenuBots = false var updateConfig = false - var updatedRevenueBalances: RevenueStats.Balances? + var updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:] + var updatedStarsBalance: [PeerId: Int64] = [:] var holesFromPreviousStateMessageIds: [MessageId] = [] var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:] @@ -3869,6 +3872,13 @@ func replayFinalState( } } + if let previousFactCheckAttribute = previousMessage.attributes.first(where: { $0 is FactCheckMessageAttribute }) as? FactCheckMessageAttribute, let updatedFactCheckAttribute = message.attributes.first(where: { $0 is FactCheckMessageAttribute }) as? FactCheckMessageAttribute { + if case .Pending = updatedFactCheckAttribute.content, updatedFactCheckAttribute.hash == previousFactCheckAttribute.hash { + updatedAttributes.removeAll(where: { $0 is FactCheckMessageAttribute }) + updatedAttributes.append(previousFactCheckAttribute) + } + } + if let message = locallyRenderedMessage(message: message, peers: peers) { generatedEvent = reactionGeneratedEvent(previousMessage.reactionsAttribute, message.reactionsAttribute, message: message, transaction: transaction) } @@ -4828,8 +4838,10 @@ func replayFinalState( } else { transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewSessionReviews, itemId: id.rawValue) } - case let .UpdateRevenueBalances(balances): - updatedRevenueBalances = balances + case let .UpdateRevenueBalances(peerId, balances): + updatedRevenueBalances[peerId] = balances + case let .UpdateStarsBalance(peerId, balance): + updatedStarsBalance[peerId] = balance } } @@ -5324,5 +5336,5 @@ func replayFinalState( } } - return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, addedReactionEvents: addedReactionEvents, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, deletedMessageIds: deletedMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedGroupCallParticipants: updatedGroupCallParticipants, storyUpdates: storyUpdates, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates, updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: updatedRevenueBalances) + return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, addedReactionEvents: addedReactionEvents, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, deletedMessageIds: deletedMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedGroupCallParticipants: updatedGroupCallParticipants, storyUpdates: storyUpdates, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates, updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: updatedRevenueBalances, updatedStarsBalance: updatedStarsBalance) } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index b364a339f60..da4b4d97994 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -45,7 +45,11 @@ private final class UpdatedPeersNearbySubscriberContext { } private final class UpdatedRevenueBalancesSubscriberContext { - let subscribers = Bag<(RevenueStats.Balances) -> Void>() + let subscribers = Bag<([PeerId: RevenueStats.Balances]) -> Void>() +} + +private final class UpdatedStarsBalanceSubscriberContext { + let subscribers = Bag<([PeerId: Int64]) -> Void>() } public enum DeletedMessageId: Hashable { @@ -282,6 +286,7 @@ public final class AccountStateManager { private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:] private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext() private var updatedRevenueBalancesContext = UpdatedRevenueBalancesSubscriberContext() + private var updatedStarsBalanceContext = UpdatedStarsBalanceSubscriberContext() private let delayNotificatonsUntil = Atomic(value: nil) private let appliedMaxMessageIdPromise = Promise(nil) @@ -1027,8 +1032,11 @@ public final class AccountStateManager { if let updatedPeersNearby = events.updatedPeersNearby { strongSelf.notifyUpdatedPeersNearby(updatedPeersNearby) } - if let updatedRevenueBalances = events.updatedRevenueBalances { - strongSelf.notifyUpdatedRevenueBalances(updatedRevenueBalances) + if !events.updatedRevenueBalances.isEmpty { + strongSelf.notifyUpdatedRevenueBalances(events.updatedRevenueBalances) + } + if !events.updatedStarsBalance.isEmpty { + strongSelf.notifyUpdatedStarsBalance(events.updatedStarsBalance) } if !events.updatedCalls.isEmpty { for call in events.updatedCalls { @@ -1602,7 +1610,7 @@ public final class AccountStateManager { } } - public func updatedRevenueBalances() -> Signal { + public func updatedRevenueBalances() -> Signal<[PeerId: RevenueStats.Balances], NoError> { let queue = self.queue return Signal { [weak self] subscriber in let disposable = MetaDisposable() @@ -1623,12 +1631,39 @@ public final class AccountStateManager { } } - private func notifyUpdatedRevenueBalances(_ updatedRevenueBalances: RevenueStats.Balances) { + private func notifyUpdatedRevenueBalances(_ updatedRevenueBalances: [PeerId: RevenueStats.Balances]) { for subscriber in self.updatedRevenueBalancesContext.subscribers.copyItems() { subscriber(updatedRevenueBalances) } } + public func updatedStarsBalance() -> Signal<[PeerId: Int64], NoError> { + let queue = self.queue + return Signal { [weak self] subscriber in + let disposable = MetaDisposable() + queue.async { + if let strongSelf = self { + let index = strongSelf.updatedStarsBalanceContext.subscribers.add({ starsBalance in + subscriber.putNext(starsBalance) + }) + + disposable.set(ActionDisposable { + if let strongSelf = self { + strongSelf.updatedStarsBalanceContext.subscribers.remove(index) + } + }) + } + } + return disposable + } + } + + private func notifyUpdatedStarsBalance(_ updatedStarsBalance: [PeerId: Int64]) { + for subscriber in self.updatedStarsBalanceContext.subscribers.copyItems() { + subscriber(updatedStarsBalance) + } + } + func notifyDeletedMessages(messageIds: [MessageId]) { self.deletedMessagesPipe.putNext(messageIds.map { .messageId($0) }) } @@ -1916,11 +1951,17 @@ public final class AccountStateManager { } } - public func updatedRevenueBalances() -> Signal { + public func updatedRevenueBalances() -> Signal<[PeerId: RevenueStats.Balances], NoError> { return self.impl.signalWith { impl, subscriber in return impl.updatedRevenueBalances().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) } } + + public func updatedStarsBalance() -> Signal<[PeerId: Int64], NoError> { + return self.impl.signalWith { impl, subscriber in + return impl.updatedStarsBalance().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) + } + } func addCustomOperation(_ f: Signal) -> Signal { return self.impl.signalWith { impl, subscriber in diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index 7f92d7df97f..be1cbc0efca 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -90,6 +90,7 @@ final class AccountTaskManager { tasks.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedApplyPendingScheduledMessagesActions(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start()) tasks.add(managedSynchronizeAvailableReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) + tasks.add(managedSynchronizeAvailableMessageEffects(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .emoji).start()) tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .status).start()) tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .avatar).start()) diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index 99d113350cf..d53abc3f954 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/AvailableMessageEffects.swift b/submodules/TelegramCore/Sources/State/AvailableMessageEffects.swift new file mode 100644 index 00000000000..5842b06a9b5 --- /dev/null +++ b/submodules/TelegramCore/Sources/State/AvailableMessageEffects.swift @@ -0,0 +1,310 @@ +import Foundation +import TelegramApi +import Postbox +import SwiftSignalKit + +public final class AvailableMessageEffects: Equatable, Codable { + public final class MessageEffect: Equatable, Codable { + private enum CodingKeys: String, CodingKey { + case id + case isPremium + case emoticon + case staticIcon + case effectSticker + case effectAnimation + } + + public let id: Int64 + public let isPremium: Bool + public let emoticon: String + public let staticIcon: TelegramMediaFile? + public let effectSticker: TelegramMediaFile + public let effectAnimation: TelegramMediaFile? + + public init( + id: Int64, + isPremium: Bool, + emoticon: String, + staticIcon: TelegramMediaFile?, + effectSticker: TelegramMediaFile, + effectAnimation: TelegramMediaFile? + ) { + self.id = id + self.isPremium = isPremium + self.emoticon = emoticon + self.staticIcon = staticIcon + self.effectSticker = effectSticker + self.effectAnimation = effectAnimation + } + + public static func ==(lhs: MessageEffect, rhs: MessageEffect) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.isPremium != rhs.isPremium { + return false + } + if lhs.emoticon != rhs.emoticon { + return false + } + if lhs.staticIcon != rhs.staticIcon { + return false + } + if lhs.effectSticker != rhs.effectSticker { + return false + } + if lhs.effectAnimation != rhs.effectAnimation { + return false + } + return true + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.id = try container.decode(Int64.self, forKey: .id) + self.isPremium = try container.decodeIfPresent(Bool.self, forKey: .isPremium) ?? false + self.emoticon = try container.decode(String.self, forKey: .emoticon) + + if let staticIconData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .staticIcon) { + self.staticIcon = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: staticIconData.data))) + } else { + self.staticIcon = nil + } + + do { + let effectStickerData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectSticker) + self.effectSticker = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectStickerData.data))) + } + + if let effectAnimationData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectAnimation) { + self.effectAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectAnimationData.data))) + } else { + self.effectAnimation = nil + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.id, forKey: .id) + try container.encode(self.emoticon, forKey: .emoticon) + try container.encode(self.isPremium, forKey: .isPremium) + + if let staticIcon = self.staticIcon { + try container.encode(PostboxEncoder().encodeObjectToRawData(staticIcon), forKey: .staticIcon) + } + try container.encode(PostboxEncoder().encodeObjectToRawData(self.effectSticker), forKey: .effectSticker) + if let effectAnimation = self.effectAnimation { + try container.encode(PostboxEncoder().encodeObjectToRawData(effectAnimation), forKey: .effectAnimation) + } + } + } + + private enum CodingKeys: String, CodingKey { + case newHash + case messageEffects + } + + public let hash: Int32 + public let messageEffects: [MessageEffect] + + public init( + hash: Int32, + messageEffects: [MessageEffect] + ) { + self.hash = hash + self.messageEffects = messageEffects + } + + public static func ==(lhs: AvailableMessageEffects, rhs: AvailableMessageEffects) -> Bool { + if lhs.hash != rhs.hash { + return false + } + if lhs.messageEffects != rhs.messageEffects { + return false + } + return true + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.hash = try container.decodeIfPresent(Int32.self, forKey: .newHash) ?? 0 + self.messageEffects = try container.decode([MessageEffect].self, forKey: .messageEffects) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.hash, forKey: .newHash) + try container.encode(self.messageEffects, forKey: .messageEffects) + } +} + +//availableEffect flags:# premium_required:flags.2?true id:long emoticon:string static_icon_id:flags.0?long effect_sticker_id:long effect_animation_id:flags.1?long = AvailableEffect; + +private extension AvailableMessageEffects.MessageEffect { + convenience init?(apiMessageEffect: Api.AvailableEffect, files: [Int64: TelegramMediaFile]) { + switch apiMessageEffect { + case let .availableEffect(flags, id, emoticon, staticIconId, effectStickerId, effectAnimationId): + guard let effectSticker = files[effectStickerId] else { + return nil + } + + let isPremium = (flags & (1 << 2)) != 0 + self.init( + id: id, + isPremium: isPremium, + emoticon: emoticon, + staticIcon: staticIconId.flatMap({ files[$0] }), + effectSticker: effectSticker, + effectAnimation: effectAnimationId.flatMap({ files[$0] }) + ) + } + } +} + +func _internal_cachedAvailableMessageEffects(postbox: Postbox) -> Signal { + return postbox.transaction { transaction -> AvailableMessageEffects? in + return _internal_cachedAvailableMessageEffects(transaction: transaction) + } +} + +func _internal_cachedAvailableMessageEffects(transaction: Transaction) -> AvailableMessageEffects? { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: 0) + + let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.availableMessageEffects, key: key))?.get(AvailableMessageEffects.self) + if let cached = cached { + return cached + } else { + return nil + } +} + +func _internal_setCachedAvailableMessageEffects(transaction: Transaction, availableMessageEffects: AvailableMessageEffects) { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: 0) + + if let entry = CodableEntry(availableMessageEffects) { + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.availableMessageEffects, key: key), entry: entry) + } +} + +func managedSynchronizeAvailableMessageEffects(postbox: Postbox, network: Network) -> Signal { + let poll = Signal { subscriber in + let signal: Signal = _internal_cachedAvailableMessageEffects(postbox: postbox) + |> mapToSignal { current in + let sourceHash: Int32 + #if DEBUG + sourceHash = 0 + #else + sourceHash = current?.hash ?? 0 + #endif + return (network.request(Api.functions.messages.getAvailableEffects(hash: sourceHash)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Signal in + guard let result = result else { + return .complete() + } + switch result { + case let .availableEffects(hash, effects, documents): + var files: [Int64: TelegramMediaFile] = [:] + for document in documents { + if let file = telegramMediaFileFromApiDocument(document) { + files[file.fileId.id] = file + } + } + + var parsedEffects: [AvailableMessageEffects.MessageEffect] = [] + for effect in effects { + if let parsedEffect = AvailableMessageEffects.MessageEffect(apiMessageEffect: effect, files: files) { + parsedEffects.append(parsedEffect) + } + } + _internal_setCachedAvailableMessageEffects(transaction: transaction, availableMessageEffects: AvailableMessageEffects( + hash: hash, + messageEffects: parsedEffects + )) + case .availableEffectsNotModified: + break + } + + /*var signals: [Signal] = [] + + if let availableMessageEffects = _internal_cachedAvailableMessageEffects(transaction: transaction) { + var resources: [MediaResource] = [] + + for messageEffect in availableMessageEffects.messageEffects { + if let staticIcon = messageEffect.staticIcon { + resources.append(staticIcon.resource) + } + if messageEffect.effectSticker.isPremiumSticker { + if let effectFile = messageEffect.effectSticker.videoThumbnails.first { + resources.append(effectFile.resource) + } + } else { + if let effectAnimation = messageEffect.effectAnimation { + resources.append(effectAnimation.resource) + } + } + } + + for resource in resources { + signals.append( + fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .other, reference: .standalone(resource: resource)) + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } + ) + } + } + + return combineLatest(signals) + |> ignoreValues*/ + + return .complete() + } + |> switchToLatest + }) + } + + return signal.start(completed: { + subscriber.putCompletion() + }) + } + + return ( + poll + |> then( + .complete() + |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()) + ) + ) + |> restart +} + +public extension Message { + func messageEffect(availableMessageEffects: AvailableMessageEffects?) -> AvailableMessageEffects.MessageEffect? { + guard let availableMessageEffects else { + return nil + } + for attribute in self.attributes { + if let attribute = attribute as? EffectMessageAttribute { + for effect in availableMessageEffects.messageEffects { + if effect.id == attribute.id { + return effect + } + } + break + } + } + return nil + } +} diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 8ed18226d66..a5e1b25ed47 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -794,6 +794,7 @@ public final class PendingMessageManager { var scheduleTime: Int32? var sendAsPeerId: PeerId? var quickReply: OutgoingQuickReplyMessageAttribute? + var messageEffect: EffectMessageAttribute? var flags: Int32 = 0 @@ -824,6 +825,10 @@ public final class PendingMessageManager { sendAsPeerId = attribute.peerId } else if let attribute = attribute as? OutgoingQuickReplyMessageAttribute { quickReply = attribute + } else if let attribute = attribute as? EffectMessageAttribute { + messageEffect = attribute + } else if let _ = attribute as? InvertMediaMessageAttribute { + flags |= Int32(1 << 16) } } @@ -1016,7 +1021,13 @@ public final class PendingMessageManager { flags |= 1 << 17 } - sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyTo: replyTo, multiMedia: singleMedias, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut)) + var messageEffectId: Int64? + if let messageEffect { + flags |= 1 << 18 + messageEffectId = messageEffect.id + } + + sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyTo: replyTo, multiMedia: singleMedias, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId)) } return sendMessageRequest @@ -1192,6 +1203,7 @@ public final class PendingMessageManager { var sendAsPeerId: PeerId? var bubbleUpEmojiOrStickersets = false var quickReply: OutgoingQuickReplyMessageAttribute? + var messageEffect: EffectMessageAttribute? var flags: Int32 = 0 @@ -1228,6 +1240,8 @@ public final class PendingMessageManager { sendAsPeerId = attribute.peerId } else if let attribute = attribute as? OutgoingQuickReplyMessageAttribute { quickReply = attribute + } else if let attribute = attribute as? EffectMessageAttribute { + messageEffect = attribute } } @@ -1310,12 +1324,14 @@ public final class PendingMessageManager { replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id) } } - if let attribute = message.webpagePreviewAttribute { if attribute.leadingPreview { flags |= 1 << 16 } } + if message.invertMedia { + flags |= 1 << 16 + } var quickReplyShortcut: Api.InputQuickReplyShortcut? if let quickReply { @@ -1327,7 +1343,13 @@ public final class PendingMessageManager { flags |= 1 << 17 } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut), info: .acknowledgement, tag: dependencyTag) + var messageEffectId: Int64? + if let messageEffect { + flags |= 1 << 18 + messageEffectId = messageEffect.id + } + + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -1391,6 +1413,9 @@ public final class PendingMessageManager { flags |= 1 << 16 } } + if message.invertMedia { + flags |= 1 << 16 + } var quickReplyShortcut: Api.InputQuickReplyShortcut? if let quickReply { @@ -1401,8 +1426,14 @@ public final class PendingMessageManager { } flags |= 1 << 17 } + + var messageEffectId: Int64? + if let messageEffect { + flags |= 1 << 18 + messageEffectId = messageEffect.id + } - sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): var topMsgId: Int32? diff --git a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift index 957ab380681..02ee72c513e 100644 --- a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift @@ -1186,7 +1186,7 @@ private func parseEntities(_ entities: [SecretApi101.MessageEntity]) -> TextEnti case let .messageEntityUnderline(offset, length): result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Underline)) case let .messageEntityBlockquote(offset, length): - result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BlockQuote)) + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BlockQuote(isCollapsed: false))) case .messageEntityUnknown: break } @@ -1223,7 +1223,7 @@ private func parseEntities(_ entities: [SecretApi144.MessageEntity]) -> TextEnti case let .messageEntityUnderline(offset, length): result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Underline)) case let .messageEntityBlockquote(offset, length): - result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BlockQuote)) + result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BlockQuote(isCollapsed: false))) case let .messageEntitySpoiler(offset, length): result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Spoiler)) case let .messageEntityCustomEmoji(offset, length, documentId): diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index e133e4f83e7..8c2d998d497 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 179 + return 181 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 117c3ea8206..9ece0b2395a 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) + 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) + 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 93b58ad8931..f938a9ea431 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/Statistics/RevenueStatistics.swift b/submodules/TelegramCore/Sources/Statistics/RevenueStatistics.swift index 2be2c409cb9..31edbae2b21 100644 --- a/submodules/TelegramCore/Sources/Statistics/RevenueStatistics.swift +++ b/submodules/TelegramCore/Sources/Statistics/RevenueStatistics.swift @@ -135,6 +135,7 @@ private final class RevenueStatsContextImpl { assert(Queue.mainQueue().isCurrent()) let account = self.account + let peerId = self.peerId let signal = requestRevenueStats(postbox: self.account.postbox, network: self.account.network, peerId: self.peerId) |> mapToSignal { initial -> Signal in guard let initial else { @@ -143,8 +144,11 @@ private final class RevenueStatsContextImpl { return .single(initial) |> then( account.stateManager.updatedRevenueBalances() - |> map { balances in - return initial.withUpdated(balances: balances) + |> mapToSignal { updates in + if let balances = updates[peerId] { + return .single(initial.withUpdated(balances: balances)) + } + return .complete() } ) } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_EffectMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_EffectMessageAttribute.swift new file mode 100644 index 00000000000..a372696a498 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_EffectMessageAttribute.swift @@ -0,0 +1,18 @@ +import Foundation +import Postbox + +public class EffectMessageAttribute: MessageAttribute { + public let id: Int64 + + public init(id: Int64) { + self.id = id + } + + required public init(decoder: PostboxDecoder) { + self.id = decoder.decodeInt64ForKey("id", orElse: 0) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.id, forKey: "id") + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_FactCheckMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_FactCheckMessageAttribute.swift new file mode 100644 index 00000000000..7f9617147c7 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_FactCheckMessageAttribute.swift @@ -0,0 +1,73 @@ +import Postbox + +public class FactCheckMessageAttribute: MessageAttribute, Equatable { + public enum Content: PostboxCoding, Equatable { + case Pending + case Loaded(text: String, entities: [MessageTextEntity], country: String) + + public init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("_v", orElse: 0) { + case 0: + self = .Pending + case 1: + self = .Loaded( + text: decoder.decodeStringForKey("text", orElse: ""), + entities: decoder.decodeObjectArrayWithDecoderForKey("entities"), + country: decoder.decodeStringForKey("country", orElse: "") + ) + default: + assertionFailure() + self = .Pending + } + + } + + public func encode(_ encoder: PostboxEncoder) { + switch self { + case .Pending: + encoder.encodeInt32(0, forKey: "_v") + case let .Loaded(text, entities, country): + encoder.encodeInt32(1, forKey: "_v") + encoder.encodeString(text, forKey: "text") + encoder.encodeObjectArray(entities, forKey: "entities") + encoder.encodeString(country, forKey: "country") + } + } + } + + public let content: Content + public let hash: Int64 + + public var associatedPeerIds: [PeerId] { + return [] + } + + public init( + content: Content, + hash: Int64 + ) { + self.content = content + self.hash = hash + } + + required public init(decoder: PostboxDecoder) { + self.content = decoder.decodeObjectForKey("content", decoder: { FactCheckMessageAttribute.Content(decoder: $0) }) as! FactCheckMessageAttribute.Content + self.hash = decoder.decodeInt64ForKey("hash", orElse: 0) + + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.content, forKey: "content") + encoder.encodeInt64(self.hash, forKey: "hash") + } + + public static func ==(lhs: FactCheckMessageAttribute, rhs: FactCheckMessageAttribute) -> Bool { + if lhs.content != rhs.content { + return false + } + if lhs.hash != rhs.hash { + return false + } + return true + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index ce131e88594..4c07120c5ee 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -125,6 +125,7 @@ public struct Namespaces { public static let peerColorOptions: Int8 = 34 public static let savedMessageTags: Int8 = 35 public static let applicationIcons: Int8 = 36 + public static let availableMessageEffects: Int8 = 37 } public struct UnorderedItemList { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TextEntitiesMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TextEntitiesMessageAttribute.swift index eddb827c8e0..5e51191dc08 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TextEntitiesMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TextEntitiesMessageAttribute.swift @@ -18,7 +18,7 @@ public enum MessageTextEntityType: Equatable { case TextMention(peerId: PeerId) case PhoneNumber case Strikethrough - case BlockQuote + case BlockQuote(isCollapsed: Bool) case Underline case BankCard case Spoiler @@ -66,7 +66,7 @@ public struct MessageTextEntity: PostboxCoding, Codable, Equatable { case 13: self.type = .Strikethrough case 14: - self.type = .BlockQuote + self.type = .BlockQuote(isCollapsed: decoder.decodeBoolForKey("cl", orElse: false)) case 15: self.type = .Underline case 16: @@ -124,7 +124,7 @@ public struct MessageTextEntity: PostboxCoding, Codable, Equatable { case 13: self.type = .Strikethrough case 14: - self.type = .BlockQuote + self.type = .BlockQuote(isCollapsed: try container.decodeIfPresent(Bool.self, forKey: "cl") ?? false) case 15: self.type = .Underline case 16: @@ -180,8 +180,9 @@ public struct MessageTextEntity: PostboxCoding, Codable, Equatable { encoder.encodeInt32(12, forKey: "_rawValue") case .Strikethrough: encoder.encodeInt32(13, forKey: "_rawValue") - case .BlockQuote: + case let .BlockQuote(isCollapsed): encoder.encodeInt32(14, forKey: "_rawValue") + encoder.encodeBool(isCollapsed, forKey: "cl") case .Underline: encoder.encodeInt32(15, forKey: "_rawValue") case .BankCard: @@ -239,8 +240,9 @@ public struct MessageTextEntity: PostboxCoding, Codable, Equatable { try container.encode(12 as Int32, forKey: "_rawValue") case .Strikethrough: try container.encode(13 as Int32, forKey: "_rawValue") - case .BlockQuote: + case let .BlockQuote(isCollapsed): try container.encode(14 as Int32, forKey: "_rawValue") + try container.encode(isCollapsed, forKey: "cl") case .Underline: try container.encode(15 as Int32, forKey: "_rawValue") case .BankCard: diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TranslationMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TranslationMessageAttribute.swift index 5ac69146ace..5239cae7693 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TranslationMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TranslationMessageAttribute.swift @@ -1,10 +1,34 @@ import Postbox public class TranslationMessageAttribute: MessageAttribute, Equatable { + public struct Additional : PostboxCoding, Equatable { + public let text: String + public let entities: [MessageTextEntity] + + public init(text: String, entities: [MessageTextEntity]) { + self.text = text + self.entities = entities + } + + public init(decoder: PostboxDecoder) { + self.text = decoder.decodeStringForKey("text", orElse: "") + self.entities = decoder.decodeObjectArrayWithDecoderForKey("entities") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.text, forKey: "text") + encoder.encodeObjectArray(self.entities, forKey: "entities") + } + } + public let text: String public let entities: [MessageTextEntity] public let toLang: String + public let additional:[Additional] + public let pollSolution: Additional? + + public var associatedPeerIds: [PeerId] { return [] } @@ -12,23 +36,36 @@ public class TranslationMessageAttribute: MessageAttribute, Equatable { public init( text: String, entities: [MessageTextEntity], + additional:[Additional] = [], + pollSolution: Additional? = nil, toLang: String ) { self.text = text self.entities = entities self.toLang = toLang + self.additional = additional + self.pollSolution = pollSolution } required public init(decoder: PostboxDecoder) { self.text = decoder.decodeStringForKey("text", orElse: "") self.entities = decoder.decodeObjectArrayWithDecoderForKey("entities") + self.additional = decoder.decodeObjectArrayWithDecoderForKey("additional") self.toLang = decoder.decodeStringForKey("toLang", orElse: "") + self.pollSolution = decoder.decodeObjectForKey("pollSolution") as? Additional } public func encode(_ encoder: PostboxEncoder) { encoder.encodeString(self.text, forKey: "text") encoder.encodeObjectArray(self.entities, forKey: "entities") encoder.encodeString(self.toLang, forKey: "toLang") + encoder.encodeObjectArray(self.additional, forKey: "additional") + + if let pollSolution { + encoder.encodeObject(pollSolution, forKey: "pollSolution") + } else { + encoder.encodeNil(forKey: "pollSolution") + } } public static func ==(lhs: TranslationMessageAttribute, rhs: TranslationMessageAttribute) -> Bool { @@ -41,6 +78,12 @@ public class TranslationMessageAttribute: MessageAttribute, Equatable { if lhs.toLang != rhs.toLang { return false } + if lhs.additional != rhs.additional { + return false + } + if lhs.pollSolution != rhs.pollSolution { + return false + } return true } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/ChangeAccountPhoneNumber.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/ChangeAccountPhoneNumber.swift index 778b2e986fa..14e527f5983 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/ChangeAccountPhoneNumber.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/ChangeAccountPhoneNumber.swift @@ -36,7 +36,7 @@ public enum RequestChangeAccountPhoneNumberVerificationError { case generic } -func _internal_requestChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, pushNotificationConfiguration: AuthorizationCodePushNotificationConfiguration?, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal { +func _internal_requestChangeAccountPhoneNumberVerification(account: Account, apiId: Int32, apiHash: String, phoneNumber: String, pushNotificationConfiguration: AuthorizationCodePushNotificationConfiguration?, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal { var flags: Int32 = 0 flags |= 1 << 5 //allowMissedCall @@ -66,20 +66,68 @@ func _internal_requestChangeAccountPhoneNumberVerification(account: Account, pho } |> mapToSignal { sentCode -> Signal in switch sentCode { - case let .sentCode(_, type, phoneCodeHash, nextType, timeout): + case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout): var parsedNextType: AuthorizationCodeNextType? if let nextType = nextType { parsedNextType = AuthorizationCodeNextType(apiType: nextType) } - return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType)) + + if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = type { + return firebaseSecretStream + |> map { mapping -> String? in + guard let receipt = receipt else { + return nil + } + if let value = mapping[receipt] { + return value + } + if receipt == "" && mapping.count == 1 { + return mapping.first?.value + } + return nil + } + |> filter { $0 != nil } + |> take(1) + |> timeout(Double(pushTimeout ?? 15), queue: .mainQueue(), alternate: .single(nil)) + |> castError(RequestChangeAccountPhoneNumberVerificationError.self) + |> mapToSignal { firebaseSecret -> Signal in + guard let firebaseSecret = firebaseSecret else { + return internalResendChangeAccountPhoneNumberVerification(account: account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream, reason: .firebasePushTimeout) + } + + return sendFirebaseAuthorizationCode(network: account.network, phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret) + |> `catch` { _ -> Signal in + return .single(false) + } + |> mapError { _ -> RequestChangeAccountPhoneNumberVerificationError in + return .generic + } + |> mapToSignal { success -> Signal in + if success { + return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType)) + } else { + return internalResendChangeAccountPhoneNumberVerification(account: account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream, reason: .firebaseSendCodeError) + } + } + } + } else { + return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType)) + } case .sentCodeSuccess: return .never() } } } -func _internal_requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String) -> Signal { - return account.network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false) +private func internalResendChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String, apiId: Int32, apiHash: String, firebaseSecretStream: Signal<[String: String], NoError>, reason: ResendAuthorizationCodeReason?) -> Signal { + var flags: Int32 = 0 + var mappedReason: String? + if let reason { + flags |= 1 << 0 + mappedReason = reason.rawValue + } + + return account.network.request(Api.functions.auth.resendCode(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, reason: mappedReason), automaticFloodWait: false) |> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded @@ -93,18 +141,63 @@ func _internal_requestNextChangeAccountPhoneNumberVerification(account: Account, } |> mapToSignal { sentCode -> Signal in switch sentCode { - case let .sentCode(_, type, phoneCodeHash, nextType, timeout): + case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout): var parsedNextType: AuthorizationCodeNextType? if let nextType = nextType { parsedNextType = AuthorizationCodeNextType(apiType: nextType) } - return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType)) + + if case let .sentCodeTypeFirebaseSms(_, _, _, receipt, pushTimeout, _) = type { + return firebaseSecretStream + |> map { mapping -> String? in + guard let receipt = receipt else { + return nil + } + if let value = mapping[receipt] { + return value + } + if receipt == "" && mapping.count == 1 { + return mapping.first?.value + } + return nil + } + |> filter { $0 != nil } + |> take(1) + |> timeout(Double(pushTimeout ?? 15), queue: .mainQueue(), alternate: .single(nil)) + |> castError(RequestChangeAccountPhoneNumberVerificationError.self) + |> mapToSignal { firebaseSecret -> Signal in + guard let firebaseSecret = firebaseSecret else { + return internalResendChangeAccountPhoneNumberVerification(account: account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream, reason: .firebasePushTimeout) + } + + return sendFirebaseAuthorizationCode(network: account.network, phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret) + |> `catch` { _ -> Signal in + return .single(false) + } + |> mapError { _ -> RequestChangeAccountPhoneNumberVerificationError in + return .generic + } + |> mapToSignal { success -> Signal in + if success { + return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType)) + } else { + return internalResendChangeAccountPhoneNumberVerification(account: account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream, reason: .firebaseSendCodeError) + } + } + } + } else { + return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType)) + } case .sentCodeSuccess: return .never() } } } +func _internal_requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String, apiId: Int32, apiHash: String, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal { + return internalResendChangeAccountPhoneNumberVerification(account: account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream, reason: nil) +} + public enum ChangeAccountPhoneNumberError { case generic case invalidCode diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift index 9f48fc7460c..02aa66b6730 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift @@ -15,12 +15,12 @@ public extension TelegramEngine { return _internal_acceptTermsOfService(account: self.account, id: id) } - public func requestChangeAccountPhoneNumberVerification(phoneNumber: String, pushNotificationConfiguration: AuthorizationCodePushNotificationConfiguration?, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal { - return _internal_requestChangeAccountPhoneNumberVerification(account: self.account, phoneNumber: phoneNumber, pushNotificationConfiguration: pushNotificationConfiguration, firebaseSecretStream: firebaseSecretStream) + public func requestChangeAccountPhoneNumberVerification(apiId: Int32, apiHash: String, phoneNumber: String, pushNotificationConfiguration: AuthorizationCodePushNotificationConfiguration?, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal { + return _internal_requestChangeAccountPhoneNumberVerification(account: self.account, apiId: apiId, apiHash: apiHash, phoneNumber: phoneNumber, pushNotificationConfiguration: pushNotificationConfiguration, firebaseSecretStream: firebaseSecretStream) } - public func requestNextChangeAccountPhoneNumberVerification(phoneNumber: String, phoneCodeHash: String) -> Signal { - return _internal_requestNextChangeAccountPhoneNumberVerification(account: self.account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash) + public func requestNextChangeAccountPhoneNumberVerification(phoneNumber: String, phoneCodeHash: String, apiId: Int32, apiHash: String, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal { + return _internal_requestNextChangeAccountPhoneNumberVerification(account: self.account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream) } public func requestChangeAccountPhoneNumber(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> Signal { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Auth/CancelAccountReset.swift b/submodules/TelegramCore/Sources/TelegramEngine/Auth/CancelAccountReset.swift index 234f771c4f6..b0bd632e632 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Auth/CancelAccountReset.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Auth/CancelAccountReset.swift @@ -41,7 +41,7 @@ func _internal_requestCancelAccountResetData(network: Network, hash: String) -> } func _internal_requestNextCancelAccountResetOption(network: Network, phoneNumber: String, phoneCodeHash: String) -> Signal { - return network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false) + return network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, reason: nil), automaticFloodWait: false) |> mapError { error -> RequestCancelAccountResetDataError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/FactCheck.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/FactCheck.swift new file mode 100644 index 00000000000..426913e2b77 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/FactCheck.swift @@ -0,0 +1,123 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit + +func _internal_editMessageFactCheck(account: Account, messageId: EngineMessage.Id, text: String, entities: [MessageTextEntity]) -> Signal { + return account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer else { + return .complete() + } + + return account.network.request(Api.functions.messages.editFactCheck( + peer: inputPeer, + msgId: messageId.id, + text: .textWithEntities( + text: text, + entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary()) + ) + )) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { updates -> Signal in + if let updates = updates { + account.stateManager.addUpdates(updates) + } + return .complete() + } + } +} + +func _internal_deleteMessageFactCheck(account: Account, messageId: EngineMessage.Id) -> Signal { + return account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer else { + return .complete() + } + return account.network.request(Api.functions.messages.deleteFactCheck(peer: inputPeer, msgId: messageId.id)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { updates -> Signal in + if let updates = updates { + account.stateManager.addUpdates(updates) + } + return .complete() + } + } +} + +func _internal_getMessagesFactCheck(account: Account, messageIds: [EngineMessage.Id]) -> Signal { + var signals: [Signal] = [] + for (peerId, messageIds) in messagesIdsGroupedByPeerId(messageIds) { + signals.append(_internal_getMessagesFactCheckByPeerId(account: account, peerId: peerId, messageIds: messageIds)) + } + return combineLatest(signals) + |> ignoreValues +} + +func _internal_getMessagesFactCheckByPeerId(account: Account, peerId: EnginePeer.Id, messageIds: [EngineMessage.Id]) -> Signal { + return account.postbox.transaction { transaction -> (Api.InputPeer?, [Message]) in + return (transaction.getPeer(peerId).flatMap(apiInputPeer), messageIds.compactMap({ transaction.getMessage($0) })) + } + |> mapToSignal { (inputPeer, messages) -> Signal in + guard let inputPeer = inputPeer else { + return .never() + } + + let ids: [Int32] = messageIds.map { $0.id } + let results: Signal<[Api.FactCheck]?, NoError> + if ids.isEmpty { + results = .single(nil) + } else { + results = account.network.request(Api.functions.messages.getFactCheck(peer: inputPeer, msgId: ids)) + |> map(Optional.init) + |> `catch` { _ in + return .single(nil) + } + } + + return results + |> mapToSignal { results -> Signal in + guard let results else { + return .complete() + } + return account.postbox.transaction { transaction in + var index = 0 + for result in results { + let messageId = messageIds[index] + switch result { + case let .factCheck(_, country, text, hash): + let content: FactCheckMessageAttribute.Content + if let text, let country { + switch text { + case let .textWithEntities(text, entities): + content = .Loaded(text: text, entities: messageTextEntitiesFromApiEntities(entities), country: country) + } + } else { + content = .Pending + } + let attribute = FactCheckMessageAttribute(content: content, hash: hash) + transaction.updateMessage(messageId, update: { currentMessage in + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + var attributes = currentMessage.attributes.filter { !($0 is FactCheckMessageAttribute) } + attributes.append(attribute) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } + index += 1 + } + } + |> ignoreValues + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift index 62584b0e518..84aedac1f25 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift @@ -513,6 +513,38 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation } } +func _internal_searchHashtagPosts(account: Account, hashtag: String, state: SearchMessagesState?, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> { + let remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in + var lowerBound: MessageIndex? + var peer: Peer? + if let state = state, let message = state.main.messages.last { + lowerBound = message.index + peer = message.peers[message.id.peerId] + } + if let lowerBound = lowerBound, let peer, let inputPeer = apiInputPeer(peer) { + return (state?.main.nextRate ?? 0, lowerBound, inputPeer) + } else { + return (0, lowerBound, .inputPeerEmpty) + } + } + |> mapToSignal { (nextRate, lowerBound, inputPeer) in + return account.network.request(Api.functions.channels.searchPosts(hashtag: hashtag, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false) + |> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in + return (result, nil) + } + |> `catch` { _ -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in + return .single((nil, nil)) + } + } + return remoteSearchResult + |> mapToSignal { result, additionalResult -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> in + return account.postbox.transaction { transaction -> (SearchMessagesResult, SearchMessagesState) in + let updatedState = SearchMessagesState(main: mergedState(transaction: transaction, seedConfiguration: account.postbox.seedConfiguration, accountPeerId: account.peerId, state: state?.main, result: result) ?? SearchMessagesPeerState(messages: [], readStates: [:], threadInfo: [:], totalCount: 0, completed: true, nextRate: nil), additional: nil) + return (mergedResult(updatedState), updatedState) + } + } +} + func _internal_downloadMessage(accountPeerId: PeerId, postbox: Postbox, network: Network, messageId: MessageId) -> Signal { return postbox.transaction { transaction -> Message? in return transaction.getMessage(messageId) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index 50645e56945..7346f5fa529 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -1175,7 +1175,7 @@ public final class PeerStoryListContext { public var hasCache: Bool public var allEntityFiles: [MediaId: TelegramMediaFile] - init( + public init( peerReference: PeerReference?, items: [EngineStoryItem], pinnedIds: Set, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 546eeaf58c9..544704635f1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -75,6 +75,10 @@ public extension TelegramEngine { public func searchMessages(location: SearchMessagesLocation, query: String, state: SearchMessagesState?, centerId: MessageId? = nil, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> { return _internal_searchMessages(account: self.account, location: location, query: query, state: state, centerId: centerId, limit: limit) } + + public func searchHashtagPosts(hashtag: String, state: SearchMessagesState?, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> { + return _internal_searchHashtagPosts(account: self.account, hashtag: hashtag, state: state, limit: limit) + } public func downloadMessage(messageId: MessageId) -> Signal { return _internal_downloadMessage(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, messageId: messageId) @@ -140,8 +144,8 @@ public extension TelegramEngine { return _internal_clearAuthorHistory(account: self.account, peerId: peerId, memberId: memberId) } - public func requestEditMessage(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, disableUrlPreview: Bool = false, scheduleTime: Int32? = nil) -> Signal { - return _internal_requestEditMessage(account: self.account, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime) + public func requestEditMessage(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, invertMediaAttribute: InvertMediaMessageAttribute? = nil, disableUrlPreview: Bool = false, scheduleTime: Int32? = nil) -> Signal { + return _internal_requestEditMessage(account: self.account, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, invertMediaAttribute: invertMediaAttribute) } public func requestEditLiveLocation(messageId: MessageId, stop: Bool, coordinate: (latitude: Double, longitude: Double, accuracyRadius: Int32?)?, heading: Int32?, proximityNotificationRadius: Int32?, extendPeriod: Int32?) -> Signal { @@ -518,11 +522,15 @@ public extension TelegramEngine { return EngineMessageReactionListContext(account: self.account, message: message, readStats: readStats, reaction: reaction) } - public func translate(text: String, toLang: String) -> Signal { - return _internal_translate(network: self.account.network, text: text, toLang: toLang) + public func translate(text: String, toLang: String, entities: [MessageTextEntity] = []) -> Signal<(String, [MessageTextEntity])?, TranslationError> { + return _internal_translate(network: self.account.network, text: text, toLang: toLang, entities: entities) } - public func translateMessages(messageIds: [EngineMessage.Id], toLang: String) -> Signal { + public func translate(texts: [(String, [MessageTextEntity])], toLang: String) -> Signal<[(String, [MessageTextEntity])], TranslationError> { + return _internal_translate_texts(network: self.account.network, texts: texts, toLang: toLang) + } + + public func translateMessages(messageIds: [EngineMessage.Id], toLang: String) -> Signal { return _internal_translateMessages(account: self.account, messageIds: messageIds, toLang: toLang) } @@ -698,6 +706,18 @@ public extension TelegramEngine { return _internal_searchForumTopics(account: self.account, peerId: peerId, query: query) } + public func editMessageFactCheck(messageId: EngineMessage.Id, text: String, entities: [MessageTextEntity]) -> Signal { + return _internal_editMessageFactCheck(account: self.account, messageId: messageId, text: text, entities: entities) + } + + public func deleteMessageFactCheck(messageId: EngineMessage.Id) -> Signal { + return _internal_deleteMessageFactCheck(account: self.account, messageId: messageId) + } + + public func getMessagesFactCheck(messageIds: [EngineMessage.Id]) -> Signal { + return _internal_getMessagesFactCheck(account: self.account, messageIds: messageIds) + } + public func debugAddHoles() -> Signal { return self.account.postbox.transaction { transaction -> Void in transaction.addHolesEverywhere(peerNamespaces: [Namespaces.Peer.CloudUser, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel], holeNamespace: Namespaces.Message.Cloud) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift index 562d549a7c4..6ddcd439867 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift @@ -13,11 +13,11 @@ public enum TranslationError { case limitExceeded } -func _internal_translate(network: Network, text: String, toLang: String) -> Signal { +func _internal_translate(network: Network, text: String, toLang: String, entities: [MessageTextEntity] = []) -> Signal<(String, [MessageTextEntity])?, TranslationError> { var flags: Int32 = 0 flags |= (1 << 1) - return network.request(Api.functions.messages.translateText(flags: flags, peer: nil, id: nil, text: [.textWithEntities(text: text, entities: [])], toLang: toLang)) + return network.request(Api.functions.messages.translateText(flags: flags, peer: nil, id: nil, text: [.textWithEntities(text: text, entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary()))], toLang: toLang)) |> mapError { error -> TranslationError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded @@ -33,11 +33,11 @@ func _internal_translate(network: Network, text: String, toLang: String) -> Sign return .generic } } - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal<(String, [MessageTextEntity])?, TranslationError> in switch result { case let .translateResult(results): - if case let .textWithEntities(text, _) = results.first { - return .single(text) + if case let .textWithEntities(text, entities) = results.first { + return .single((text, messageTextEntitiesFromApiEntities(entities))) } else { return .single(nil) } @@ -45,59 +45,165 @@ func _internal_translate(network: Network, text: String, toLang: String) -> Sign } } -func _internal_translateMessages(account: Account, messageIds: [EngineMessage.Id], toLang: String) -> Signal { - guard let peerId = messageIds.first?.peerId else { - return .never() +func _internal_translate_texts(network: Network, texts: [(String, [MessageTextEntity])], toLang: String) -> Signal<[(String, [MessageTextEntity])], TranslationError> { + var flags: Int32 = 0 + flags |= (1 << 1) + + var apiTexts: [Api.TextWithEntities] = [] + for text in texts { + apiTexts.append(.textWithEntities(text: text.0, entities: apiEntitiesFromMessageTextEntities(text.1, associatedPeers: SimpleDictionary()))) } - return account.postbox.transaction { transaction -> Api.InputPeer? in - return transaction.getPeer(peerId).flatMap(apiInputPeer) + + return network.request(Api.functions.messages.translateText(flags: flags, peer: nil, id: nil, text: apiTexts, toLang: toLang)) + |> mapError { error -> TranslationError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else if error.errorDescription == "MSG_ID_INVALID" { + return .invalidMessageId + } else if error.errorDescription == "INPUT_TEXT_EMPTY" { + return .textIsEmpty + } else if error.errorDescription == "INPUT_TEXT_TOO_LONG" { + return .textTooLong + } else if error.errorDescription == "TO_LANG_INVALID" { + return .invalidLanguage + } else { + return .generic + } + } + |> mapToSignal { result -> Signal<[(String, [MessageTextEntity])], TranslationError> in + var texts: [(String, [MessageTextEntity])] = [] + switch result { + case let .translateResult(results): + for result in results { + if case let .textWithEntities(text, entities) = result { + texts.append((text, messageTextEntitiesFromApiEntities(entities))) + } + } + } + return .single(texts) + } +} + +func _internal_translateMessages(account: Account, messageIds: [EngineMessage.Id], toLang: String) -> Signal { + var signals: [Signal] = [] + for (peerId, messageIds) in messagesIdsGroupedByPeerId(messageIds) { + signals.append(_internal_translateMessagesByPeerId(account: account, peerId: peerId, messageIds: messageIds, toLang: toLang)) + } + return combineLatest(signals) + |> ignoreValues +} + +private func _internal_translateMessagesByPeerId(account: Account, peerId: EnginePeer.Id, messageIds: [EngineMessage.Id], toLang: String) -> Signal { + return account.postbox.transaction { transaction -> (Api.InputPeer?, [Message]) in + return (transaction.getPeer(peerId).flatMap(apiInputPeer), messageIds.compactMap({ transaction.getMessage($0) })) } |> castError(TranslationError.self) - |> mapToSignal { inputPeer -> Signal in + |> mapToSignal { (inputPeer, messages) -> Signal in guard let inputPeer = inputPeer else { return .never() } + let polls = messages.compactMap { msg in + if let poll = msg.media.first as? TelegramMediaPoll { + return (poll, msg.id) + } else { + return nil + } + } + let pollSignals = polls.map { (poll, id) in + var texts: [(String, [MessageTextEntity])] = [] + texts.append((poll.text, poll.textEntities)) + for option in poll.options { + texts.append((option.text, option.entities)) + } + if let solution = poll.results.solution { + texts.append((solution.text, solution.entities)) + } + return _internal_translate_texts(network: account.network, texts: texts, toLang: toLang) + } + + var flags: Int32 = 0 flags |= (1 << 0) let id: [Int32] = messageIds.map { $0.id } - return account.network.request(Api.functions.messages.translateText(flags: flags, peer: inputPeer, id: id, text: nil, toLang: toLang)) - |> mapError { error -> TranslationError in - if error.errorDescription.hasPrefix("FLOOD_WAIT") { - return .limitExceeded - } else if error.errorDescription == "MSG_ID_INVALID" { - return .invalidMessageId - } else if error.errorDescription == "INPUT_TEXT_EMPTY" { - return .textIsEmpty - } else if error.errorDescription == "INPUT_TEXT_TOO_LONG" { - return .textTooLong - } else if error.errorDescription == "TO_LANG_INVALID" { - return .invalidLanguage - } else { - return .generic + + let msgs: Signal + if id.isEmpty { + msgs = .single(nil) + } else { + msgs = account.network.request(Api.functions.messages.translateText(flags: flags, peer: inputPeer, id: id, text: nil, toLang: toLang)) + |> map(Optional.init) + |> mapError { error -> TranslationError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else if error.errorDescription == "MSG_ID_INVALID" { + return .invalidMessageId + } else if error.errorDescription == "INPUT_TEXT_EMPTY" { + return .textIsEmpty + } else if error.errorDescription == "INPUT_TEXT_TOO_LONG" { + return .textTooLong + } else if error.errorDescription == "TO_LANG_INVALID" { + return .invalidLanguage + } else { + return .generic + } } } - |> mapToSignal { result -> Signal in - guard case let .translateResult(results) = result else { - return .complete() - } + + return combineLatest(msgs, combineLatest(pollSignals)) + |> mapToSignal { (result, pollResults) -> Signal in return account.postbox.transaction { transaction in - var index = 0 - for result in results { - let messageId = messageIds[index] - if case let .textWithEntities(text, entities) = result { - let updatedAttribute: TranslationMessageAttribute = TranslationMessageAttribute(text: text, entities: messageTextEntitiesFromApiEntities(entities), toLang: toLang) - transaction.updateMessage(messageId, update: { currentMessage in - let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) - var attributes = currentMessage.attributes.filter { !($0 is TranslationMessageAttribute) } - - attributes.append(updatedAttribute) - - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) - }) + if case let .translateResult(results) = result { + var index = 0 + for result in results { + let messageId = messageIds[index] + if case let .textWithEntities(text, entities) = result { + let updatedAttribute: TranslationMessageAttribute = TranslationMessageAttribute(text: text, entities: messageTextEntitiesFromApiEntities(entities), toLang: toLang) + transaction.updateMessage(messageId, update: { currentMessage in + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + var attributes = currentMessage.attributes.filter { !($0 is TranslationMessageAttribute) } + + attributes.append(updatedAttribute) + + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } + index += 1 + } + } + if !pollResults.isEmpty { + for (i, poll) in polls.enumerated() { + let result = pollResults[i] + if !result.isEmpty { + transaction.updateMessage(poll.1, update: { currentMessage in + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + var attributes = currentMessage.attributes.filter { !($0 is TranslationMessageAttribute) } + var attrOptions: [TranslationMessageAttribute.Additional] = [] + for (i, _) in poll.0.options.enumerated() { + var translated = result.count > i + 1 ? result[i + 1] : (poll.0.options[i].text, poll.0.options[i].entities) + if translated.0.isEmpty { + translated = (poll.0.options[i].text, poll.0.options[i].entities) + } + attrOptions.append(.init(text: translated.0, entities: translated.1)) + } + + let solution: TranslationMessageAttribute.Additional? + if result.count > 1 + poll.0.options.count, !result[result.count - 1].0.isEmpty { + solution = .init(text: result[result.count - 1].0, entities: result[result.count - 1].1) + } else { + solution = nil + } + + let title = result[0].0.isEmpty ? (poll.0.text, poll.0.textEntities) : result[0] + + let updatedAttribute: TranslationMessageAttribute = TranslationMessageAttribute(text: title.0, entities: title.1, additional: attrOptions, pollSolution: solution, toLang: toLang) + attributes.append(updatedAttribute) + + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } } - index += 1 } } |> castError(TranslationError.self) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift index 49c7d41aa78..32a13749310 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift @@ -17,6 +17,7 @@ public enum AppStoreTransactionPurpose { case gift(peerId: EnginePeer.Id, currency: String, amount: Int64) case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?, currency: String, amount: Int64) case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64) + case stars(count: Int64, currency: String, amount: Int64) } private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTransactionPurpose) -> Signal { @@ -89,6 +90,8 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran return .single(.inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)) } |> switchToLatest + case let .stars(count, currency, amount): + return .single(.inputStorePaymentStars(flags: 0, stars: count, currency: currency, amount: amount)) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index f2b77784347..8ab99b612c2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -9,9 +9,9 @@ public enum BotPaymentInvoiceSource { case slug(String) case premiumGiveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, option: PremiumGiftCodeOption) case giftCode(users: [PeerId], currency: String, amount: Int64, option: PremiumGiftCodeOption) + case stars(option: StarsTopUpOption) } - public struct BotPaymentInvoiceFields: OptionSet { public var rawValue: Int32 @@ -119,8 +119,8 @@ public struct BotPaymentForm : Equatable { public let passwordMissing: Bool public let invoice: BotPaymentInvoice public let paymentBotId: PeerId - public let providerId: PeerId - public let url: String + public let providerId: PeerId? + public let url: String? public let nativeProvider: BotPaymentNativeProvider? public let savedInfo: BotPaymentRequestedInfo? public let savedCredentials: [BotPaymentSavedCredentials] @@ -206,7 +206,7 @@ extension BotPaymentRequestedInfo { } } -private func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInvoiceSource) -> Api.InputInvoice? { +func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInvoiceSource) -> Api.InputInvoice? { switch source { case let .message(messageId): guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else { @@ -257,8 +257,6 @@ private func _internal_parseInputInvoice(transaction: Transaction, source: BotPa return .inputInvoicePremiumGiftCode(purpose: inputPurpose, option: option) case let .giftCode(users, currency, amount, option): - - var inputUsers: [Api.InputUser] = [] if !users.isEmpty { for peerId in users { @@ -269,7 +267,6 @@ private func _internal_parseInputInvoice(transaction: Transaction, source: BotPa } let inputPurpose: Api.InputStorePaymentPurpose = .inputStorePaymentPremiumGiftCode(flags: 0, users: inputUsers, boostPeer: nil, currency: currency, amount: amount) - var flags: Int32 = 0 if let _ = option.storeProductId { @@ -282,7 +279,14 @@ private func _internal_parseInputInvoice(transaction: Transaction, source: BotPa let option: Api.PremiumGiftCodeOption = .premiumGiftCodeOption(flags: flags, users: option.users, months: option.months, storeProduct: option.storeProductId, storeQuantity: option.storeQuantity, currency: option.currency, amount: option.amount) return .inputInvoicePremiumGiftCode(purpose: inputPurpose, option: option) - + case let .stars(option): + var flags: Int32 = 0 + if let _ = option.storeProductId { + flags |= (1 << 0) + } + return .inputInvoiceStars( + option: .starsTopupOption(flags: flags, stars: option.count, storeProduct: option.storeProductId, currency: option.currency, amount: option.amount) + ) } } @@ -317,6 +321,9 @@ func _internal_fetchBotPaymentInvoice(postbox: Postbox, network: Network, source } return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: 0, startParam: "", extendedMedia: nil, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion) + case let .paymentFormStars(_, _, _, title, description, photo, invoice, _): + let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) + return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: parsedInvoice.prices.reduce(0, { $0 + $1.amount }), startParam: "", extendedMedia: nil, flags: [], version: TelegramMediaInvoice.lastVersion) } } |> mapError { _ -> BotPaymentFormRequestError in } @@ -350,32 +357,43 @@ func _internal_fetchBotPaymentForm(accountPeerId: PeerId, postbox: Postbox, netw |> mapToSignal { result -> Signal in return postbox.transaction { transaction -> BotPaymentForm in switch result { - case let .paymentForm(flags, id, botId, title, description, photo, invoice, providerId, url, nativeProvider, nativeParams, additionalMethods, savedInfo, savedCredentials, apiUsers): - let _ = title - let _ = description - let _ = photo - - let parsedPeers = AccumulatedPeers(users: apiUsers) - updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) - - let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) - var parsedNativeProvider: BotPaymentNativeProvider? - if let nativeProvider = nativeProvider, let nativeParams = nativeParams { - switch nativeParams { - case let .dataJSON(data): - parsedNativeProvider = BotPaymentNativeProvider(name: nativeProvider, params: data) - } - } - let parsedSavedInfo = savedInfo.flatMap(BotPaymentRequestedInfo.init) - let parsedSavedCredentials = savedCredentials?.map({ savedCredentials -> BotPaymentSavedCredentials in - switch savedCredentials { - case let .paymentSavedCredentialsCard(id, title): - return .card(id: id, title: title) - } - }) ?? [] + case let .paymentForm(flags, id, botId, title, description, photo, invoice, providerId, url, nativeProvider, nativeParams, additionalMethods, savedInfo, savedCredentials, apiUsers): + let _ = title + let _ = description + let _ = photo + + let parsedPeers = AccumulatedPeers(users: apiUsers) + updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) - let additionalPaymentMethods = additionalMethods?.map({ BotPaymentMethod(apiPaymentFormMethod: $0) }) ?? [] - return BotPaymentForm(id: id, canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, paymentBotId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), providerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(providerId)), url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials, additionalPaymentMethods: additionalPaymentMethods) + let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) + var parsedNativeProvider: BotPaymentNativeProvider? + if let nativeProvider = nativeProvider, let nativeParams = nativeParams { + switch nativeParams { + case let .dataJSON(data): + parsedNativeProvider = BotPaymentNativeProvider(name: nativeProvider, params: data) + } + } + let parsedSavedInfo = savedInfo.flatMap(BotPaymentRequestedInfo.init) + let parsedSavedCredentials = savedCredentials?.map({ savedCredentials -> BotPaymentSavedCredentials in + switch savedCredentials { + case let .paymentSavedCredentialsCard(id, title): + return .card(id: id, title: title) + } + }) ?? [] + + let additionalPaymentMethods = additionalMethods?.map({ BotPaymentMethod(apiPaymentFormMethod: $0) }) ?? [] + return BotPaymentForm(id: id, canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, paymentBotId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), providerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(providerId)), url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials, additionalPaymentMethods: additionalPaymentMethods) + case let .paymentFormStars(flags, id, botId, title, description, photo, invoice, apiUsers): + let _ = flags + let _ = title + let _ = description + let _ = photo + + let parsedPeers = AccumulatedPeers(users: apiUsers) + updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) + + let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) + return BotPaymentForm(id: id, canSaveCredentials: false, passwordMissing: false, invoice: parsedInvoice, paymentBotId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), providerId: nil, url: nil, nativeProvider: nil, savedInfo: nil, savedCredentials: [], additionalPaymentMethods: []) } } |> mapError { _ -> BotPaymentFormRequestError in } @@ -569,6 +587,8 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa } case .giftCode: receiptMessageId = nil + case .stars: + receiptMessageId = nil } } } @@ -595,16 +615,22 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa public struct BotPaymentReceipt : Equatable { public let invoice: BotPaymentInvoice + public let date: Int32 public let info: BotPaymentRequestedInfo? public let shippingOption: BotPaymentShippingOption? public let credentialsTitle: String public let invoiceMedia: TelegramMediaInvoice public let tipAmount: Int64? public let botPaymentId: PeerId + public let transactionId: String? + public static func ==(lhs: BotPaymentReceipt, rhs: BotPaymentReceipt) -> Bool { if lhs.invoice != rhs.invoice { return false } + if lhs.date != rhs.date { + return false + } if lhs.info != rhs.info { return false } @@ -623,6 +649,9 @@ public struct BotPaymentReceipt : Equatable { if lhs.botPaymentId != rhs.botPaymentId { return false } + if lhs.transactionId != rhs.transactionId { + return false + } return true } } @@ -649,7 +678,7 @@ func _internal_requestBotPaymentReceipt(account: Account, messageId: MessageId) |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction -> BotPaymentReceipt in switch result { - case let .paymentReceipt(_, _, botId, _, title, description, photo, invoice, info, shipping, tipAmount, currency, totalAmount, credentialsTitle, users): + case let .paymentReceipt(_, date, botId, _, title, description, photo, invoice, info, shipping, tipAmount, currency, totalAmount, credentialsTitle, users): let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [], users: users) updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) @@ -672,7 +701,28 @@ func _internal_requestBotPaymentReceipt(account: Account, messageId: MessageId) let botPaymentId = PeerId.init(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)) - return BotPaymentReceipt(invoice: parsedInvoice, info: parsedInfo, shippingOption: shippingOption, credentialsTitle: credentialsTitle, invoiceMedia: invoiceMedia, tipAmount: tipAmount, botPaymentId: botPaymentId) + return BotPaymentReceipt(invoice: parsedInvoice, date: date, info: parsedInfo, shippingOption: shippingOption, credentialsTitle: credentialsTitle, invoiceMedia: invoiceMedia, tipAmount: tipAmount, botPaymentId: botPaymentId, transactionId: nil) + case let .paymentReceiptStars(_, date, botId, title, description, photo, invoice, currency, totalAmount, transactionId, users): + let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [], users: users) + updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) + + let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) + + let invoiceMedia = TelegramMediaInvoice( + title: title, + description: description, + photo: photo.flatMap(TelegramMediaWebFile.init), + receiptMessageId: nil, + currency: currency, + totalAmount: totalAmount, + startParam: "", + extendedMedia: nil, + flags: [], + version: TelegramMediaInvoice.lastVersion + ) + + let botPaymentId = PeerId.init(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)) + return BotPaymentReceipt(invoice: parsedInvoice, date: date, info: nil, shippingOption: nil, credentialsTitle: "", invoiceMedia: invoiceMedia, tipAmount: nil, botPaymentId: botPaymentId, transactionId: transactionId) } } |> castError(RequestBotPaymentReceiptError.self) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift index 92af17aa86b..b3b21209d68 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift @@ -31,6 +31,7 @@ public struct PremiumGiftCodeOption: Codable, Equatable { public let storeQuantity: Int32 public let currency: String public let amount: Int64 + public init(users: Int32, months: Int32, storeProductId: String?, storeQuantity: Int32, currency: String, amount: Int64) { self.users = users self.months = months diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift new file mode 100644 index 00000000000..2283d9aae8a --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -0,0 +1,678 @@ +import Foundation +import Postbox +import MtProtoKit +import SwiftSignalKit +import TelegramApi + +public struct StarsTopUpOption: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case count + case storeProductId + case currency + case amount + } + + public let count: Int64 + public let storeProductId: String? + public let currency: String + public let amount: Int64 + + public init(count: Int64, storeProductId: String?, currency: String, amount: Int64) { + self.count = count + self.storeProductId = storeProductId + self.currency = currency + self.amount = amount + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.count = try container.decode(Int64.self, forKey: .count) + self.storeProductId = try container.decodeIfPresent(String.self, forKey: .storeProductId) + self.currency = try container.decode(String.self, forKey: .currency) + self.amount = try container.decode(Int64.self, forKey: .amount) + + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.count, forKey: .count) + try container.encodeIfPresent(self.storeProductId, forKey: .storeProductId) + try container.encode(self.currency, forKey: .currency) + try container.encode(self.amount, forKey: .amount) + } +} + +extension StarsTopUpOption { + init(apiStarsTopupOption: Api.StarsTopupOption) { + switch apiStarsTopupOption { + case let .starsTopupOption(_, stars, storeProduct, currency, amount): + self.init(count: stars, storeProductId: storeProduct, currency: currency, amount: amount) + } + } +} + +func _internal_starsTopUpOptions(account: Account) -> Signal<[StarsTopUpOption], NoError> { + return account.network.request(Api.functions.payments.getStarsTopupOptions()) + |> map(Optional.init) + |> `catch` { _ -> Signal<[Api.StarsTopupOption]?, NoError> in + return .single(nil) + } + |> mapToSignal { results -> Signal<[StarsTopUpOption], NoError> in + if let results = results { + return .single(results.map { StarsTopUpOption(apiStarsTopupOption: $0) }) + } else { + return .single([]) + } + } +} + +struct InternalStarsStatus { + let balance: Int64 + let transactions: [StarsContext.State.Transaction] + let nextOffset: String? +} + +private enum RequestStarsStateError { + case generic +} + +private func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, subject: StarsTransactionsContext.Subject, offset: String?) -> Signal { + return account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(peerId) + } + |> castError(RequestStarsStateError.self) + |> mapToSignal { peer -> Signal in + guard let peer, let inputPeer = apiInputPeer(peer) else { + return .fail(.generic) + } + + let signal: Signal + if let offset { + var flags: Int32 = 0 + switch subject { + case .incoming: + flags = 1 << 0 + case .outgoing: + flags = 1 << 1 + default: + break + } + signal = account.network.request(Api.functions.payments.getStarsTransactions(flags: flags, peer: inputPeer, offset: offset)) + } else { + signal = account.network.request(Api.functions.payments.getStarsStatus(peer: inputPeer)) + } + + return signal + |> retryRequest + |> castError(RequestStarsStateError.self) + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> InternalStarsStatus in + switch result { + case let .starsStatus(_, balance, history, nextOffset, chats, users): + let peers = AccumulatedPeers(chats: chats, users: users) + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: peers) + + var parsedTransactions: [StarsContext.State.Transaction] = [] + for entry in history { + if let parsedTransaction = StarsContext.State.Transaction(apiTransaction: entry, transaction: transaction) { + parsedTransactions.append(parsedTransaction) + } + } + return InternalStarsStatus(balance: balance, transactions: parsedTransactions, nextOffset: nextOffset) + } + } + |> castError(RequestStarsStateError.self) + } + } +} + +private final class StarsContextImpl { + private let account: Account + fileprivate let peerId: EnginePeer.Id + + fileprivate var _state: StarsContext.State? + private let _statePromise = Promise() + var state: Signal { + return self._statePromise.get() + } + private var nextOffset: String? + + private let disposable = MetaDisposable() + private var updateDisposable: Disposable? + + init(account: Account, peerId: EnginePeer.Id) { + assert(Queue.mainQueue().isCurrent()) + + self.account = account + self.peerId = peerId + + self._state = nil + self._statePromise.set(.single(nil)) + + self.load(force: true) + + self.updateDisposable = (account.stateManager.updatedStarsBalance() + |> deliverOnMainQueue).startStrict(next: { [weak self] balances in + guard let self, let state = self._state, let balance = balances[peerId] else { + return + } + self.updateState(StarsContext.State(flags: [], balance: balance, transactions: state.transactions, canLoadMore: nextOffset != nil, isLoading: false)) + self.load(force: true) + }) + } + + deinit { + assert(Queue.mainQueue().isCurrent()) + self.disposable.dispose() + self.updateDisposable?.dispose() + } + + private var previousLoadTimestamp: Double? + func load(force: Bool) { + assert(Queue.mainQueue().isCurrent()) + + let currentTimestamp = CFAbsoluteTimeGetCurrent() + if let previousLoadTimestamp = self.previousLoadTimestamp, currentTimestamp - previousLoadTimestamp < 60 && !force { + return + } + self.previousLoadTimestamp = currentTimestamp + + self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, subject: .all, offset: nil) + |> deliverOnMainQueue).start(next: { [weak self] status in + guard let self else { + return + } + self.updateState(StarsContext.State(flags: [], balance: status.balance, transactions: status.transactions, canLoadMore: status.nextOffset != nil, isLoading: false)) + self.nextOffset = status.nextOffset + }, error: { [weak self] _ in + guard let self else { + return + } + Queue.mainQueue().after(2.5, { + self.load(force: true) + }) + })) + } + + func add(balance: Int64) { + guard let state = self._state else { + return + } + var transactions = state.transactions + transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil), at: 0) + + self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: state.balance + balance, transactions: transactions, canLoadMore: state.canLoadMore, isLoading: state.isLoading)) + } + + fileprivate func updateBalance(_ balance: Int64, transactions: [StarsContext.State.Transaction]?) { + guard let state = self._state else { + return + } + self.updateState(StarsContext.State(flags: [], balance: balance, transactions: transactions ?? state.transactions, canLoadMore: state.canLoadMore, isLoading: state.isLoading)) + } + + func loadMore() { + assert(Queue.mainQueue().isCurrent()) + + guard let currentState = self._state, let nextOffset = self.nextOffset else { + return + } + + self._state?.isLoading = true + + self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, subject: .all, offset: nextOffset) + |> deliverOnMainQueue).start(next: { [weak self] status in + if let self { + self.updateState(StarsContext.State(flags: [], balance: status.balance, transactions: currentState.transactions + status.transactions, canLoadMore: status.nextOffset != nil, isLoading: false)) + self.nextOffset = status.nextOffset + } + })) + } + + private func updateState(_ state: StarsContext.State) { + self._state = state + self._statePromise.set(.single(state)) + } +} + +private extension StarsContext.State.Transaction { + init?(apiTransaction: Api.StarsTransaction, transaction: Transaction) { + switch apiTransaction { + case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo): + let parsedPeer: StarsContext.State.Transaction.Peer + switch transactionPeer { + case .starsTransactionPeerAppStore: + parsedPeer = .appStore + case .starsTransactionPeerPlayMarket: + parsedPeer = .playMarket + case .starsTransactionPeerFragment: + parsedPeer = .fragment + case .starsTransactionPeerPremiumBot: + parsedPeer = .premiumBot + case .starsTransactionPeerUnsupported: + parsedPeer = .unsupported + case let .starsTransactionPeer(apiPeer): + guard let peer = transaction.getPeer(apiPeer.peerId) else { + return nil + } + parsedPeer = .peer(EnginePeer(peer)) + } + + var flags: Flags = [] + if (apiFlags & (1 << 3)) != 0 { + flags.insert(.isRefund) + } + self.init(flags: flags, id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init)) + } + } +} + +public final class StarsContext { + public struct State: Equatable { + public struct Transaction: Equatable { + public struct Flags: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let isRefund = Flags(rawValue: 1 << 0) + public static let isLocal = Flags(rawValue: 1 << 1) + } + + public enum Peer: Equatable { + case appStore + case playMarket + case fragment + case premiumBot + case unsupported + case peer(EnginePeer) + } + + public let flags: Flags + public let id: String + public let count: Int64 + public let date: Int32 + public let peer: Peer + public let title: String? + public let description: String? + public let photo: TelegramMediaWebFile? + + public init( + flags: Flags, + id: String, + count: Int64, + date: Int32, + peer: Peer, + title: String?, + description: String?, + photo: TelegramMediaWebFile? + ) { + self.flags = flags + self.id = id + self.count = count + self.date = date + self.peer = peer + self.title = title + self.description = description + self.photo = photo + } + } + + public struct Flags: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let isPendingBalance = Flags(rawValue: 1 << 0) + } + + public var flags: Flags + public var balance: Int64 + public var transactions: [Transaction] + public var canLoadMore: Bool + public var isLoading: Bool + + init(flags: Flags, balance: Int64, transactions: [Transaction], canLoadMore: Bool, isLoading: Bool) { + self.flags = flags + self.balance = balance + self.transactions = transactions + self.canLoadMore = canLoadMore + self.isLoading = isLoading + } + + public static func == (lhs: State, rhs: State) -> Bool { + if lhs.flags != rhs.flags { + return true + } + if lhs.balance != rhs.balance { + return false + } + if lhs.transactions != rhs.transactions { + return false + } + if lhs.canLoadMore != rhs.canLoadMore { + return false + } + if lhs.isLoading != rhs.isLoading { + return false + } + return true + } + } + + private let impl: QueueLocalObject + + public var state: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.state.start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } + + var peerId: EnginePeer.Id { + var peerId: EnginePeer.Id? + self.impl.syncWith { impl in + peerId = impl.peerId + } + return peerId! + } + + var currentState: StarsContext.State? { + var state: StarsContext.State? + self.impl.syncWith { impl in + state = impl._state + } + return state + } + + public func add(balance: Int64) { + self.impl.with { + $0.add(balance: balance) + } + } + + fileprivate func updateBalance(_ balance: Int64, transactions: [StarsContext.State.Transaction]?) { + self.impl.with { + $0.updateBalance(balance, transactions: transactions) + } + } + + + public func load(force: Bool) { + self.impl.with { + $0.load(force: force) + } + } + + public func loadMore() { + self.impl.with { + $0.loadMore() + } + } + + init(account: Account, peerId: EnginePeer.Id) { + self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: { + return StarsContextImpl(account: account, peerId: peerId) + }) + } +} + +private final class StarsTransactionsContextImpl { + private let account: Account + private weak var starsContext: StarsContext? + private let peerId: EnginePeer.Id + private let subject: StarsTransactionsContext.Subject + + private var _state: StarsTransactionsContext.State + private let _statePromise = Promise() + var state: Signal { + return self._statePromise.get() + } + private var nextOffset: String? = "" + + private let disposable = MetaDisposable() + private var stateDisposable: Disposable? + + init(account: Account, starsContext: StarsContext, subject: StarsTransactionsContext.Subject) { + assert(Queue.mainQueue().isCurrent()) + + self.account = account + self.starsContext = starsContext + self.peerId = starsContext.peerId + self.subject = subject + + let currentTransactions = starsContext.currentState?.transactions ?? [] + let initialTransactions: [StarsContext.State.Transaction] + switch subject { + case .all: + initialTransactions = currentTransactions + case .incoming: + initialTransactions = currentTransactions.filter { $0.count > 0 } + case .outgoing: + initialTransactions = currentTransactions.filter { $0.count < 0 } + } + + self._state = StarsTransactionsContext.State(transactions: initialTransactions, canLoadMore: true, isLoading: false) + self._statePromise.set(.single(self._state)) + + self.stateDisposable = (starsContext.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self, let state else { + return + } + + let currentTransactions = state.transactions + let filteredTransactions: [StarsContext.State.Transaction] + switch subject { + case .all: + filteredTransactions = currentTransactions + case .incoming: + filteredTransactions = currentTransactions.filter { $0.count > 0 } + case .outgoing: + filteredTransactions = currentTransactions.filter { $0.count < 0 } + } + + if filteredTransactions != initialTransactions { + var existingIds = Set() + for transaction in self._state.transactions { + if !transaction.flags.contains(.isLocal) { + existingIds.insert(transaction.id) + } + } + + var updatedState = self._state + updatedState.transactions.removeAll(where: { $0.flags.contains(.isLocal) }) + for transaction in filteredTransactions.reversed() { + if !existingIds.contains(transaction.id) { + updatedState.transactions.insert(transaction, at: 0) + } + } + self.updateState(updatedState) + } + }) + } + + deinit { + assert(Queue.mainQueue().isCurrent()) + self.disposable.dispose() + self.stateDisposable?.dispose() + } + + func loadMore(reload: Bool = false) { + assert(Queue.mainQueue().isCurrent()) + + if reload { + self.nextOffset = "" + } + + guard !self._state.isLoading, let nextOffset = self.nextOffset else { + return + } + + var updatedState = self._state + updatedState.isLoading = true + self.updateState(updatedState) + + self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, subject: self.subject, offset: nextOffset) + |> deliverOnMainQueue).start(next: { [weak self] status in + guard let self else { + return + } + self.nextOffset = status.nextOffset + + var updatedState = self._state + updatedState.transactions = nextOffset.isEmpty ? status.transactions : updatedState.transactions + status.transactions + updatedState.isLoading = false + updatedState.canLoadMore = self.nextOffset != nil + self.updateState(updatedState) + + if case .all = self.subject, nextOffset.isEmpty { + self.starsContext?.updateBalance(status.balance, transactions: status.transactions) + } else { + self.starsContext?.updateBalance(status.balance, transactions: nil) + } + })) + } + + private func updateState(_ state: StarsTransactionsContext.State) { + self._state = state + self._statePromise.set(.single(state)) + } +} + +public final class StarsTransactionsContext { + public struct State: Equatable { + public var transactions: [StarsContext.State.Transaction] + public var canLoadMore: Bool + public var isLoading: Bool + + init(transactions: [StarsContext.State.Transaction], canLoadMore: Bool, isLoading: Bool) { + self.transactions = transactions + self.canLoadMore = canLoadMore + self.isLoading = isLoading + } + } + + fileprivate let impl: QueueLocalObject + + public enum Subject { + case all + case incoming + case outgoing + } + + public var state: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.state.start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } + + public func reload() { + self.impl.with { + $0.loadMore(reload: true) + } + } + + public func loadMore() { + self.impl.with { + $0.loadMore() + } + } + + init(account: Account, starsContext: StarsContext, subject: Subject) { + self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: { + return StarsTransactionsContextImpl(account: account, starsContext: starsContext, subject: subject) + }) + } +} + +func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: BotPaymentInvoiceSource) -> Signal { + return account.postbox.transaction { transaction -> Api.InputInvoice? in + return _internal_parseInputInvoice(transaction: transaction, source: source) + } + |> castError(SendBotPaymentFormError.self) + |> mapToSignal { invoice -> Signal in + guard let invoice = invoice else { + return .fail(.generic) + } + + let flags: Int32 = 0 + + return account.network.request(Api.functions.payments.sendStarsForm(flags: flags, formId: formId, invoice: invoice)) + |> map { result -> SendBotPaymentResult in + switch result { + case let .paymentResult(updates): + account.stateManager.addUpdates(updates) + var receiptMessageId: MessageId? + for apiMessage in updates.messages { + if let message = StoreMessage(apiMessage: apiMessage, accountPeerId: account.peerId, peerIsForum: false) { + for media in message.media { + if let action = media as? TelegramMediaAction { + if case .paymentSent = action.action { + switch source { + case let .slug(slug): + for media in message.media { + if let action = media as? TelegramMediaAction, case let .paymentSent(_, _, invoiceSlug?, _, _) = action.action, invoiceSlug == slug { + if case let .Id(id) = message.id { + receiptMessageId = id + } + } + } + case let .message(messageId): + for attribute in message.attributes { + if let reply = attribute as? ReplyMessageAttribute { + if reply.messageId == messageId { + if case let .Id(id) = message.id { + receiptMessageId = id + } + } + } + } + case let .premiumGiveaway(_, _, _, _, _, _, randomId, _, _, _, _): + if message.globallyUniqueId == randomId { + if case let .Id(id) = message.id { + receiptMessageId = id + } + } + case .giftCode: + receiptMessageId = nil + case .stars: + receiptMessageId = nil + } + } + } + } + } + } + return .done(receiptMessageId: receiptMessageId) + case let .paymentVerificationNeeded(url): + return .externalVerificationRequired(url: url) + } + } + |> `catch` { error -> Signal in + if error.errorDescription == "BOT_PRECHECKOUT_FAILED" { + return .fail(.precheckoutFailed) + } else if error.errorDescription == "PAYMENT_FAILED" { + return .fail(.paymentFailed) + } else if error.errorDescription == "INVOICE_ALREADY_PAID" { + return .fail(.alreadyPaid) + } + return .fail(.generic) + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index 0fe3e752cae..fa68190c7c4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -29,7 +29,7 @@ public extension TelegramEngine { public func sendBotPaymentForm(source: BotPaymentInvoiceSource, formId: Int64, validatedInfoId: String?, shippingOptionId: String?, tipAmount: Int64?, credentials: BotPaymentCredentials) -> Signal { return _internal_sendBotPaymentForm(account: self.account, formId: formId, source: source, validatedInfoId: validatedInfoId, shippingOptionId: shippingOptionId, tipAmount: tipAmount, credentials: credentials) } - + public func requestBotPaymentReceipt(messageId: MessageId) -> Signal { return _internal_requestBotPaymentReceipt(account: self.account, messageId: messageId) } @@ -65,5 +65,22 @@ public extension TelegramEngine { public func launchPrepaidGiveaway(peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32) -> Signal { return _internal_launchPrepaidGiveaway(account: self.account, peerId: peerId, id: id, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate) } + + public func starsTopUpOptions() -> Signal<[StarsTopUpOption], NoError> { + return _internal_starsTopUpOptions(account: self.account) + } + + public func peerStarsContext(peerId: EnginePeer.Id) -> StarsContext { + return StarsContext(account: self.account, peerId: peerId) + } + + + public func peerStarsTransactionsContext(starsContext: StarsContext, subject: StarsTransactionsContext.Subject) -> StarsTransactionsContext { + return StarsTransactionsContext(account: self.account, starsContext: starsContext, subject: subject) + } + + public func sendStarsPaymentForm(formId: Int64, source: BotPaymentInvoiceSource) -> Signal { + return _internal_sendStarsPaymentForm(account: self.account, formId: formId, source: source) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelRecommendation.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelRecommendation.swift index 7353cd25031..6d186fb8211 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelRecommendation.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelRecommendation.swift @@ -171,9 +171,16 @@ func _internal_recommendedChannels(account: Account, peerId: EnginePeer.Id?) -> let key = PostboxViewKey.cachedItem(entryId(peerId: peerId)) return account.postbox.combinedView(keys: [key]) |> mapToSignal { views -> Signal in - guard let cachedChannels = (views.views[key] as? CachedItemView)?.value?.get(CachedRecommendedChannels.self), !cachedChannels.peerIds.isEmpty else { + guard let cachedChannels = (views.views[key] as? CachedItemView)?.value?.get(CachedRecommendedChannels.self) else { return .single(nil) } + if cachedChannels.peerIds.isEmpty { + if peerId != nil { + return .single(nil) + } else { + return .single(RecommendedChannels(channels: [], count: 0, isHidden: false)) + } + } return account.postbox.multiplePeersView(cachedChannels.peerIds) |> mapToSignal { view in return account.postbox.transaction { transaction -> RecommendedChannels? in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index 42ee3b92fa0..a3cf7bfac56 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -145,6 +145,10 @@ public extension TelegramEngine { return _internal_cachedAvailableReactions(postbox: self.account.postbox) } + public func availableMessageEffects() -> Signal { + return _internal_cachedAvailableMessageEffects(postbox: self.account.postbox) + } + public func savedMessageTagData() -> Signal { return self.account.postbox.combinedView(keys: [PostboxViewKey.cachedItem(_internal_savedMessageTagsCacheKey())]) |> mapToSignal { views -> Signal in diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index e234e47effc..741799e6c31 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 { @@ -437,6 +441,16 @@ public extension Message { } return nil } + + var factCheckAttribute: FactCheckMessageAttribute? { + for attribute in self.attributes { + if let attribute = attribute as? FactCheckMessageAttribute { + return attribute + } + } + return nil + } + var inlineBotAttribute: InlineBusinessBotMessageAttribute? { for attribute in self.attributes { if let attribute = attribute as? InlineBusinessBotMessageAttribute { @@ -523,6 +537,22 @@ public extension Message { } return nil } + var invertMedia: Bool { + for attribute in self.attributes { + if let _ = attribute as? InvertMediaMessageAttribute { + return true + } + } + return false + } + var invertMediaAttribute: InvertMediaMessageAttribute? { + for attribute in self.attributes { + if let attribute = attribute as? InvertMediaMessageAttribute { + return attribute + } + } + return nil + } } public extension Message { diff --git a/submodules/TelegramIntents/BUILD b/submodules/TelegramIntents/BUILD index a9539a5ca13..d9c2768e2aa 100644 --- a/submodules/TelegramIntents/BUILD +++ b/submodules/TelegramIntents/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/TelegramNotices/BUILD b/submodules/TelegramNotices/BUILD index 5a8b8127a46..caab7ea801f 100644 --- a/submodules/TelegramNotices/BUILD +++ b/submodules/TelegramNotices/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox:Postbox", diff --git a/submodules/TelegramPermissions/BUILD b/submodules/TelegramPermissions/BUILD index a5fb22cea82..e5fb370eb6f 100644 --- a/submodules/TelegramPermissions/BUILD +++ b/submodules/TelegramPermissions/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/TelegramPermissionsUI/BUILD b/submodules/TelegramPermissionsUI/BUILD index 54bc103cd89..cbf5f7f97aa 100644 --- a/submodules/TelegramPermissionsUI/BUILD +++ b/submodules/TelegramPermissionsUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramPresentationData/BUILD b/submodules/TelegramPresentationData/BUILD index 75689afad79..c3905af8973 100644 --- a/submodules/TelegramPresentationData/BUILD +++ b/submodules/TelegramPresentationData/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index a6e7427793b..98143bf6d92 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -371,6 +371,7 @@ public struct PresentationResourcesChat { context.setLineWidth(2.0) context.setLineCap(.round) context.setLineJoin(.round) + context.translateBy(x: -UIScreenPixel, y: 0.0) let _ = try? drawSvgPath(context, path: "M11,14.6666667 L16.4310816,9.40016333 L16.4310816,9.40016333 C16.4694824,9.36292619 16.5305176,9.36292619 16.5689184,9.40016333 L22,14.6666667 S ") let _ = try? drawSvgPath(context, path: "M16.5,9.33333333 C17.0522847,9.33333333 17.5,9.78104858 17.5,10.3333333 L17.5,24 C17.5,24.5522847 17.0522847,25 16.5,25 C15.9477153,25 15.5,24.5522847 15.5,24 L15.5,10.3333333 C15.5,9.78104858 15.9477153,9.33333333 16.5,9.33333333 Z ") }) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift index 811dc3df03c..4fffe198997 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift @@ -57,7 +57,12 @@ private func renderIcon(name: String, scaleFactor: CGFloat = 1.0, backgroundColo } } else { if let image = UIImage(bundleImageName: name), let cgImage = image.cgImage { - let imageSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor) + let imageSize: CGSize + if scaleFactor == 1.0 { + imageSize = size + } else { + imageSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor) + } context.draw(cgImage, in: CGRect(origin: CGPoint(x: (bounds.width - imageSize.width) * 0.5, y: (bounds.height - imageSize.height) * 0.5), size: imageSize)) } } @@ -117,6 +122,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/BUILD b/submodules/TelegramStringFormatting/BUILD index 932916cf5c2..7a1d3fb2c46 100644 --- a/submodules/TelegramStringFormatting/BUILD +++ b/submodules/TelegramStringFormatting/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index a9d665563cd..b343de0591f 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -547,7 +547,16 @@ 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: "#") { + amountAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars), 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 860d13951c4..d231828b10a 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -13,7 +13,6 @@ NGDEPS = [ "//Nicegram/NGLab:NGLab", "//Nicegram/NGAppCache:NGAppCache", "//Nicegram/NGEnv:NGEnv", - "//Nicegram/NGOnboarding:NGOnboarding", "//Nicegram/NGQuickReplies:NGQuickReplies", "//Nicegram/NGRemoteConfig:NGRemoteConfig", "//Nicegram/NGStats:NGStats", @@ -23,7 +22,9 @@ NGDEPS = [ "@swiftpkg_nicegram_assistant_ios//:FeatAvatarGeneratorUI", "@swiftpkg_nicegram_assistant_ios//:FeatCardUI", "@swiftpkg_nicegram_assistant_ios//:FeatChatBanner", + "@swiftpkg_nicegram_assistant_ios//:FeatOnboarding", "@swiftpkg_nicegram_assistant_ios//:FeatPremium", + "@swiftpkg_nicegram_assistant_ios//:FeatTgChatButton", "@swiftpkg_nicegram_assistant_ios//:NGAssistantUI", "@swiftpkg_nicegram_assistant_ios//:NGAuth", "@swiftpkg_nicegram_assistant_ios//:NGEntryPoint", @@ -76,7 +77,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", @@ -402,7 +403,6 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode", "//submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageItem", - "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", "//submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode", "//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode", "//submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode", @@ -478,6 +478,11 @@ swift_library( "//submodules/TelegramUI/Components/Ads/AdsReportScreen", "//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", + "//submodules/TelegramUI/Components/Chat/FactCheckAlertController", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/ActionPanelComponent/BUILD b/submodules/TelegramUI/Components/ActionPanelComponent/BUILD index e7bc81265aa..eb570d08018 100644 --- a/submodules/TelegramUI/Components/ActionPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/ActionPanelComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/BUILD b/submodules/TelegramUI/Components/AdminUserActionsSheet/BUILD index d6ef632670b..5f15d761bef 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/BUILD +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent copy.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent copy.swift deleted file mode 100644 index dcd78b898ff..00000000000 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent copy.swift +++ /dev/null @@ -1,342 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import ComponentFlow -import SwiftSignalKit -import AccountContext -import TelegramCore -import MultilineTextComponent -import AvatarNode -import TelegramPresentationData -import CheckNode -import TelegramStringFormatting -import ListSectionComponent - -/*final class AdminUserActionsSwitchComponent: Component { - enum SelectionState: Equatable { - case none - case editing(isSelected: Bool) - } - - enum SubtitleIcon { - case lock - } - - enum Subtitle: Equatable { - case presence(EnginePeer.Presence?) - case text(text: String, icon: SubtitleIcon) - } - - let context: AccountContext - let theme: PresentationTheme - let strings: PresentationStrings - let sideInset: CGFloat - let title: String - let subtitle: Subtitle - let peer: EnginePeer? - let selectionState: SelectionState - let hasNext: Bool - let action: (EnginePeer) -> Void - - init( - context: AccountContext, - theme: PresentationTheme, - strings: PresentationStrings, - sideInset: CGFloat, - title: String, - subtitle: Subtitle, - peer: EnginePeer?, - selectionState: SelectionState, - hasNext: Bool, - action: @escaping (EnginePeer) -> Void - ) { - self.context = context - self.theme = theme - self.strings = strings - self.sideInset = sideInset - self.title = title - self.subtitle = subtitle - self.peer = peer - self.selectionState = selectionState - self.hasNext = hasNext - self.action = action - } - - static func ==(lhs: AdminUserActionsSwitchComponent, rhs: AdminUserActionsSwitchComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.theme !== rhs.theme { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.sideInset != rhs.sideInset { - return false - } - if lhs.title != rhs.title { - return false - } - if lhs.subtitle != rhs.subtitle { - return false - } - if lhs.peer != rhs.peer { - return false - } - if lhs.selectionState != rhs.selectionState { - return false - } - if lhs.hasNext != rhs.hasNext { - return false - } - return true - } - - final class View: UIView { - private let containerButton: HighlightTrackingButton - - private let title = ComponentView() - private let label = ComponentView() - private let separatorLayer: SimpleLayer - private let avatarNode: AvatarNode - - private var labelIconView: UIImageView? - private var checkLayer: CheckLayer? - - private var component: AdminUserActionsSwitchComponent? - private weak var state: EmptyComponentState? - - override init(frame: CGRect) { - self.separatorLayer = SimpleLayer() - - self.containerButton = HighlightTrackingButton() - - self.avatarNode = AvatarNode(font: avatarFont) - self.avatarNode.isLayerBacked = true - - super.init(frame: frame) - - self.layer.addSublayer(self.separatorLayer) - self.addSubview(self.containerButton) - self.containerButton.layer.addSublayer(self.avatarNode.layer) - - self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc private func pressed() { - guard let component = self.component, let peer = component.peer else { - return - } - component.action(peer) - } - - func update(component: AdminUserActionsSwitchComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - let themeUpdated = self.component?.theme !== component.theme - - var hasSelectionUpdated = false - if let previousComponent = self.component { - switch previousComponent.selectionState { - case .none: - if case .none = component.selectionState { - } else { - hasSelectionUpdated = true - } - case .editing: - if case .editing = component.selectionState { - } else { - hasSelectionUpdated = true - } - } - } - - self.component = component - self.state = state - - let contextInset: CGFloat = 0.0 - - let height: CGFloat = 60.0 - let verticalInset: CGFloat = 1.0 - let leftInset: CGFloat = 62.0 + component.sideInset - var rightInset: CGFloat = contextInset * 2.0 + 8.0 + component.sideInset - let avatarLeftInset: CGFloat = component.sideInset + 10.0 - - if case let .editing(isSelected) = component.selectionState { - rightInset += 48.0 - - let checkSize: CGFloat = 22.0 - - let checkLayer: CheckLayer - if let current = self.checkLayer { - checkLayer = current - if themeUpdated { - checkLayer.theme = CheckNodeTheme(theme: component.theme, style: .plain) - } - checkLayer.setSelected(isSelected, animated: !transition.animation.isImmediate) - } else { - checkLayer = CheckLayer(theme: CheckNodeTheme(theme: component.theme, style: .plain)) - self.checkLayer = checkLayer - self.containerButton.layer.addSublayer(checkLayer) - checkLayer.frame = CGRect(origin: CGPoint(x: -checkSize, y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)) - checkLayer.setSelected(isSelected, animated: false) - checkLayer.setNeedsDisplay() - } - transition.setFrame(layer: checkLayer, frame: CGRect(origin: CGPoint(x: availableSize.width - rightInset + floor((48.0 - checkSize) * 0.5), y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize))) - } else { - if let checkLayer = self.checkLayer { - self.checkLayer = nil - transition.setPosition(layer: checkLayer, position: CGPoint(x: -checkLayer.bounds.width * 0.5, y: checkLayer.position.y), completion: { [weak checkLayer] _ in - checkLayer?.removeFromSuperlayer() - }) - } - } - - let avatarSize: CGFloat = 40.0 - - let avatarFrame = CGRect(origin: CGPoint(x: avatarLeftInset, y: floor((height - verticalInset * 2.0 - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)) - if self.avatarNode.bounds.isEmpty { - self.avatarNode.frame = avatarFrame - } else { - transition.setFrame(layer: self.avatarNode.layer, frame: avatarFrame) - } - if let peer = component.peer { - let clipStyle: AvatarNodeClipStyle - if case let .channel(channel) = peer, channel.flags.contains(.isForum) { - clipStyle = .roundedRect - } else { - clipStyle = .round - } - if peer.id == component.context.account.peerId { - self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, overrideImage: .savedMessagesIcon, clipStyle: clipStyle, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) - } else { - self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) - } - } - - var labelIcon: UIImage? - let labelData: (String, Bool) - switch component.subtitle { - case let .presence(presence): - if let presence { - labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: PresentationDateTimeFormat(), presence: presence, relativeTo: Int32(Date().timeIntervalSince1970)) - } else { - labelData = (component.strings.LastSeen_Offline, false) - } - case let .text(text, icon): - switch icon { - case .lock: - labelIcon = PresentationResourcesItemList.peerStatusLockedImage(component.theme) - } - labelData = (text, false) - } - - var maxTextSize = availableSize.width - leftInset - rightInset - if labelIcon != nil { - maxTextSize -= 48.0 - } - - let labelSize = self.label.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: labelData.0, font: Font.regular(15.0), textColor: labelData.1 ? component.theme.list.itemAccentColor : component.theme.list.itemSecondaryTextColor)) - )), - environment: {}, - containerSize: CGSize(width: maxTextSize, height: 100.0) - ) - - let previousTitleFrame = self.title.view?.frame - var previousTitleContents: UIView? - if hasSelectionUpdated && !"".isEmpty { - previousTitleContents = self.title.view?.snapshotView(afterScreenUpdates: false) - } - - let titleSize = self.title.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor)) - )), - environment: {}, - containerSize: CGSize(width: maxTextSize, height: 100.0) - ) - - let titleSpacing: CGFloat = 1.0 - let centralContentHeight: CGFloat = titleSize.height + labelSize.height + titleSpacing - - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - verticalInset * 2.0 - centralContentHeight) / 2.0)), size: titleSize) - if let titleView = self.title.view { - if titleView.superview == nil { - titleView.isUserInteractionEnabled = false - self.containerButton.addSubview(titleView) - } - titleView.frame = titleFrame - if let previousTitleFrame, previousTitleFrame.origin.x != titleFrame.origin.x { - transition.animatePosition(view: titleView, from: CGPoint(x: previousTitleFrame.origin.x - titleFrame.origin.x, y: 0.0), to: CGPoint(), additive: true) - } - - if let previousTitleFrame, let previousTitleContents, previousTitleFrame.size != titleSize { - previousTitleContents.frame = CGRect(origin: previousTitleFrame.origin, size: previousTitleFrame.size) - self.addSubview(previousTitleContents) - - transition.setFrame(view: previousTitleContents, frame: CGRect(origin: titleFrame.origin, size: previousTitleFrame.size)) - transition.setAlpha(view: previousTitleContents, alpha: 0.0, completion: { [weak previousTitleContents] _ in - previousTitleContents?.removeFromSuperview() - }) - transition.animateAlpha(view: titleView, from: 0.0, to: 1.0) - } - } - - if let labelIcon { - let labelIconView: UIImageView - if let current = self.labelIconView { - labelIconView = current - } else { - labelIconView = UIImageView() - self.labelIconView = labelIconView - self.containerButton.addSubview(labelIconView) - } - labelIconView.image = labelIcon - - let labelIconFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - 48.0 + floor((48.0 - labelIcon.size.width) * 0.5), y: floor((height - verticalInset * 2.0 - labelIcon.size.height) / 2.0)), size: CGSize(width: labelIcon.size.width, height: labelIcon.size.height)) - transition.setFrame(view: labelIconView, frame: labelIconFrame) - } else { - if let labelIconView = self.labelIconView { - self.labelIconView = nil - labelIconView.removeFromSuperview() - } - } - - if let labelView = self.label.view { - if labelView.superview == nil { - labelView.isUserInteractionEnabled = false - self.containerButton.addSubview(labelView) - } - transition.setFrame(view: labelView, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleSpacing), size: labelSize)) - } - - if themeUpdated { - self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor - } - transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel))) - self.separatorLayer.isHidden = !component.hasNext - - let containerFrame = CGRect(origin: CGPoint(x: contextInset, y: verticalInset), size: CGSize(width: availableSize.width - contextInset * 2.0, height: height - verticalInset * 2.0)) - transition.setFrame(view: self.containerButton, frame: containerFrame) - - return CGSize(width: availableSize.width, height: height) - } - } - - 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/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent.swift index dba2a48811f..47fa71b51f1 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent.swift +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent.swift @@ -37,6 +37,7 @@ final class AdminUserActionsPeerComponent: Component { let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings + let baseFontSize: CGFloat let sideInset: CGFloat let title: String let peer: EnginePeer? @@ -47,6 +48,7 @@ final class AdminUserActionsPeerComponent: Component { context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, + baseFontSize: CGFloat, sideInset: CGFloat, title: String, peer: EnginePeer?, @@ -56,6 +58,7 @@ final class AdminUserActionsPeerComponent: Component { self.context = context self.theme = theme self.strings = strings + self.baseFontSize = baseFontSize self.sideInset = sideInset self.title = title self.peer = peer @@ -73,6 +76,9 @@ final class AdminUserActionsPeerComponent: Component { if lhs.strings !== rhs.strings { return false } + if lhs.baseFontSize != rhs.baseFontSize { + return false + } if lhs.sideInset != rhs.sideInset { return false } @@ -205,11 +211,7 @@ final class AdminUserActionsPeerComponent: Component { } else { clipStyle = .round } - if peer.id == component.context.account.peerId { - self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, overrideImage: .savedMessagesIcon, clipStyle: clipStyle, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) - } else { - self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) - } + self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) } let avatarTitleSpacing: CGFloat = 5.0 @@ -224,7 +226,7 @@ final class AdminUserActionsPeerComponent: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: component.title, font: Font.semibold(component.baseFontSize), textColor: component.theme.list.itemPrimaryTextColor)) )), environment: {}, containerSize: CGSize(width: maxTextSize, height: 100.0) diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift index 2a042c2b4cd..150c2077763 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift @@ -527,8 +527,7 @@ private final class AdminUserActionsSheetComponent: Component { break loop case let .member(_, _, adminInfo, banInfo, _): if adminInfo != nil { - allowedParticipantRights = [] - allowedMediaRights = [] + (allowedParticipantRights, allowedMediaRights) = rightsFromBannedRights([]) break loop } else if let banInfo { (peerParticipantRights, peerMediaRights) = rightsFromBannedRights(banInfo.rights.flags) @@ -825,6 +824,7 @@ private final class AdminUserActionsSheetComponent: Component { context: component.context, theme: environment.theme, strings: environment.strings, + baseFontSize: presentationData.listsFontSize.baseDisplaySize, sideInset: 0.0, title: EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: .firstLast), peer: EnginePeer(peer.peer), @@ -1102,6 +1102,7 @@ private final class AdminUserActionsSheetComponent: Component { guard let self else { return } + switch configItem { case .sendMessages: if self.participantRights.contains(.sendMessages) { @@ -1137,12 +1138,20 @@ private final class AdminUserActionsSheetComponent: Component { self.state?.updated(transition: .spring(duration: 0.35)) } : nil )), - action: (isEnabled && configItem == .sendMedia) ? { [weak self] _ in - guard let self else { + action: ((isEnabled && configItem == .sendMedia) || !isEnabled) ? { [weak self] _ in + guard let self, let component = self.component else { return } - self.isMediaSectionExpanded = !self.isMediaSectionExpanded - self.state?.updated(transition: .spring(duration: 0.35)) + if !isEnabled { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: environment.strings.GroupPermission_PermissionDisabledByDefault, actions: [ + TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: { + }) + ]), in: .window(.root)) + } else { + self.isMediaSectionExpanded = !self.isMediaSectionExpanded + self.state?.updated(transition: .spring(duration: 0.35)) + } } : nil, highlighting: .disabled )))) @@ -1238,7 +1247,7 @@ private final class AdminUserActionsSheetComponent: Component { theme: environment.theme, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environment.strings.Chat_AdminActionSheet_PermissionsSectionHeader, + string: component.peers.count == 1 ? environment.strings.Chat_AdminActionSheet_PermissionsSectionHeader : environment.strings.Chat_AdminActionSheet_PermissionsSectionHeaderMultiple, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -1756,4 +1765,3 @@ private final class OptionsSectionFooterComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } - diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift index 24df544b7f3..2a3c17d32a3 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift @@ -87,7 +87,7 @@ private enum SettingsActionType: Hashable, CaseIterable { var eventFlags: AdminLogEventsFlags { switch self { case .groupInfo: - return [.info, .settings] + return [.info, .settings, .forums] case .inviteLinks: return [.invites] case .videoChats: @@ -759,6 +759,7 @@ private final class RecentActionsSettingsSheetComponent: Component { context: component.context, theme: environment.theme, strings: environment.strings, + baseFontSize: presentationData.listsFontSize.baseDisplaySize, sideInset: 0.0, title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), peer: peer, diff --git a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD index 94836156a63..545c1739631 100644 --- a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD b/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD index 1cab69556f3..0a909de49bf 100644 --- a/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD +++ b/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/AnimatedCounterComponent/BUILD b/submodules/TelegramUI/Components/AnimatedCounterComponent/BUILD index d62d5882a61..38d6d9485fe 100644 --- a/submodules/TelegramUI/Components/AnimatedCounterComponent/BUILD +++ b/submodules/TelegramUI/Components/AnimatedCounterComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/AnimatedTextComponent/BUILD b/submodules/TelegramUI/Components/AnimatedTextComponent/BUILD index 53ec1414a61..553ce77a7db 100644 --- a/submodules/TelegramUI/Components/AnimatedTextComponent/BUILD +++ b/submodules/TelegramUI/Components/AnimatedTextComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift index cf7766a76ac..d30ca5dcb41 100644 --- a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift +++ b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift @@ -123,7 +123,7 @@ public final class AnimatedTextComponent: Component { color: component.color )), environment: {}, - containerSize: CGSize(width: 100.0, height: 100.0) + containerSize: CGSize(width: availableSize.width, height: 100.0) ) let characterFrame = CGRect(origin: CGPoint(x: size.width, y: 0.0), size: characterSize) if let characterComponentView = characterView.view { diff --git a/submodules/TelegramUI/Components/AnimationCache/BUILD b/submodules/TelegramUI/Components/AnimationCache/BUILD index e655aa9fe6d..8ded3964885 100644 --- a/submodules/TelegramUI/Components/AnimationCache/BUILD +++ b/submodules/TelegramUI/Components/AnimationCache/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/AudioTranscriptionButtonComponent/BUILD b/submodules/TelegramUI/Components/AudioTranscriptionButtonComponent/BUILD index b181aca9e7e..8766ed92ffb 100644 --- a/submodules/TelegramUI/Components/AudioTranscriptionButtonComponent/BUILD +++ b/submodules/TelegramUI/Components/AudioTranscriptionButtonComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/TelegramUI/Components/AudioTranscriptionPendingIndicatorComponent/BUILD b/submodules/TelegramUI/Components/AudioTranscriptionPendingIndicatorComponent/BUILD index a7077463f64..cd0709544a9 100644 --- a/submodules/TelegramUI/Components/AudioTranscriptionPendingIndicatorComponent/BUILD +++ b/submodules/TelegramUI/Components/AudioTranscriptionPendingIndicatorComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/TelegramUI/Components/AudioWaveformComponent/BUILD b/submodules/TelegramUI/Components/AudioWaveformComponent/BUILD index 61206329444..0988580ff80 100644 --- a/submodules/TelegramUI/Components/AudioWaveformComponent/BUILD +++ b/submodules/TelegramUI/Components/AudioWaveformComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/TelegramUI/Components/AudioWaveformNode/BUILD b/submodules/TelegramUI/Components/AudioWaveformNode/BUILD index e1dce2c6872..ae70931d443 100644 --- a/submodules/TelegramUI/Components/AudioWaveformNode/BUILD +++ b/submodules/TelegramUI/Components/AudioWaveformNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD b/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD index 92230690fd6..56ff62c0eed 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/BackButtonComponent/BUILD b/submodules/TelegramUI/Components/BackButtonComponent/BUILD index 0ef42567cfc..76fc24d3b64 100644 --- a/submodules/TelegramUI/Components/BackButtonComponent/BUILD +++ b/submodules/TelegramUI/Components/BackButtonComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/BottomButtonPanelComponent/BUILD b/submodules/TelegramUI/Components/BottomButtonPanelComponent/BUILD index ebd3662a5cc..adfd237081a 100644 --- a/submodules/TelegramUI/Components/BottomButtonPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/BottomButtonPanelComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/ButtonComponent/BUILD b/submodules/TelegramUI/Components/ButtonComponent/BUILD index 8d1557f50b0..8110e8a5275 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/BUILD +++ b/submodules/TelegramUI/Components/ButtonComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/BUILD b/submodules/TelegramUI/Components/Calls/CallScreen/BUILD index 6773bd07e81..2e98539da1a 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/BUILD +++ b/submodules/TelegramUI/Components/Calls/CallScreen/BUILD @@ -54,7 +54,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":CallScreenMetalSourcesBundle", diff --git a/submodules/TelegramUI/Components/CameraButtonComponent/BUILD b/submodules/TelegramUI/Components/CameraButtonComponent/BUILD index 6aa84906a4e..80939472fac 100644 --- a/submodules/TelegramUI/Components/CameraButtonComponent/BUILD +++ b/submodules/TelegramUI/Components/CameraButtonComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/CameraScreen/BUILD b/submodules/TelegramUI/Components/CameraScreen/BUILD index 6e070546b70..87ebce1513d 100644 --- a/submodules/TelegramUI/Components/CameraScreen/BUILD +++ b/submodules/TelegramUI/Components/CameraScreen/BUILD @@ -47,7 +47,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":CameraScreenBundle", diff --git a/submodules/TelegramUI/Components/Chat/AccessoryPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/AccessoryPanelNode/BUILD index c8947235550..72f1e10fb69 100644 --- a/submodules/TelegramUI/Components/Chat/AccessoryPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/AccessoryPanelNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/BUILD index 01012aac942..55cdbbec6a4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/BUILD index 85fd55a2dfa..81913fbea89 100644 --- a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift index 335db1704d7..ecfe9f9f54b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift @@ -175,7 +175,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode { break case .ignore: return .fail - case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji: + case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .custom: return .waitForSingleTap } } diff --git a/submodules/TelegramUI/Components/Chat/ChatBotStartInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatBotStartInputPanelNode/BUILD index 8edacf5a555..a660f4eeae5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatBotStartInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatBotStartInputPanelNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/BUILD index 8a7107a81e0..35267c15e1b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD index b8b5efecb27..ee803ef3845 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD @@ -11,7 +11,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/BUILD b/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/BUILD index 436d2751716..254a5fcdead 100644 --- a/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD index 405bbcd734d..1a9fe9c855a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift index 218ca14ac63..ac62e628a69 100644 --- a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift @@ -779,6 +779,8 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC insets.top = -9.0 imageSpacing = 4.0 titleSpacing = 5.0 + case .hashTagSearch: + break } } @@ -838,6 +840,9 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC } self.businessLink = link + case .hashTagSearch: + titleString = "" + strings = [] } } else { titleString = interfaceState.strings.Conversation_CloudStorageInfo_Title diff --git a/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/BUILD b/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/BUILD index 881c4fbbf06..1d884b45166 100644 --- a/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/BUILD index f192f2f4096..8246d0e2957 100644 --- a/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD index f82054dbad1..c4e05e795d8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", @@ -27,6 +27,8 @@ swift_library( "//submodules/ContactsPeerItem", "//submodules/ItemListUI", "//submodules/ChatListSearchItemHeader", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/Components/MultilineTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index d82c7bd409f..13552cf68f0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -16,6 +16,8 @@ import ChatPresentationInterfaceState import ContactsPeerItem import ItemListUI import ChatListSearchItemHeader +import LottieComponent +import MultilineTextComponent public final class ChatInlineSearchResultsListComponent: Component { public struct Presentation: Equatable { @@ -73,9 +75,11 @@ public final class ChatInlineSearchResultsListComponent: Component { public let context: AccountContext public let presentation: Presentation - public let peerId: EnginePeer.Id + public let peerId: EnginePeer.Id? public let contents: Contents public let insets: UIEdgeInsets + public let inputHeight: CGFloat + public let showEmptyResults: Bool public let messageSelected: (EngineMessage) -> Void public let peerSelected: (EnginePeer) -> Void public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal? @@ -86,9 +90,11 @@ public final class ChatInlineSearchResultsListComponent: Component { public init( context: AccountContext, presentation: Presentation, - peerId: EnginePeer.Id, + peerId: EnginePeer.Id?, contents: Contents, insets: UIEdgeInsets, + inputHeight: CGFloat, + showEmptyResults: Bool, messageSelected: @escaping (EngineMessage) -> Void, peerSelected: @escaping (EnginePeer) -> Void, loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal?, @@ -101,6 +107,8 @@ public final class ChatInlineSearchResultsListComponent: Component { self.peerId = peerId self.contents = contents self.insets = insets + self.inputHeight = inputHeight + self.showEmptyResults = showEmptyResults self.messageSelected = messageSelected self.peerSelected = peerSelected self.loadTagMessages = loadTagMessages @@ -125,6 +133,12 @@ public final class ChatInlineSearchResultsListComponent: Component { if lhs.insets != rhs.insets { return false } + if lhs.inputHeight != rhs.inputHeight { + return false + } + if lhs.showEmptyResults != rhs.showEmptyResults { + return false + } return true } @@ -216,6 +230,9 @@ public final class ChatInlineSearchResultsListComponent: Component { private var isUpdating: Bool = false private let listNode: ListView + private let emptyResultsTitle = ComponentView() + private let emptyResultsText = ComponentView() + private let emptyResultsAnimation = ComponentView() private var tagContents: (index: MessageIndex?, disposable: Disposable?)? private var searchContents: (index: MessageIndex?, disposable: Disposable?)? @@ -239,6 +256,13 @@ public final class ChatInlineSearchResultsListComponent: Component { super.init(frame: frame) self.addSubnode(self.listNode) + + self.listNode.beganInteractiveDragging = { [weak self] _ in + guard let self else { + return + } + self.window?.endEditing(true) + } } required public init?(coder: NSCoder) { @@ -250,6 +274,10 @@ public final class ChatInlineSearchResultsListComponent: Component { self.searchContents?.disposable?.dispose() } + public func scrollToTop() { + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + public func animateIn() { self.listNode.layer.animateSublayerScale(from: 0.95, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) @@ -740,10 +768,14 @@ public final class ChatInlineSearchResultsListComponent: Component { } let renderedPeer: EngineRenderedPeer - if let effectiveAuthor { + if let effectiveAuthor, !component.showEmptyResults { renderedPeer = EngineRenderedPeer(peer: effectiveAuthor) } else { - renderedPeer = EngineRenderedPeer(peerId: message.id.peerId, peers: [:], associatedMedia: [:]) + var peers: [EnginePeer.Id: EnginePeer] = [:] + if let peer = message.peers[message.id.peerId] { + peers[message.id.peerId] = EnginePeer(peer) + } + renderedPeer = EngineRenderedPeer(peerId: message.id.peerId, peers: peers, associatedMedia: [:]) } return ChatListItem( @@ -772,7 +804,7 @@ public final class ChatInlineSearchResultsListComponent: Component { inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, - displayAsMessage: component.peerId != component.context.account.peerId, + displayAsMessage: component.peerId != component.context.account.peerId && !component.showEmptyResults, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: [], @@ -839,6 +871,103 @@ public final class ChatInlineSearchResultsListComponent: Component { } } + let fadeTransition = Transition.easeInOut(duration: 0.25) + if component.showEmptyResults, let appliedContentsState = self.appliedContentsState, appliedContentsState.entries.isEmpty, case let .search(query, _) = component.contents, !query.isEmpty { + let sideInset: CGFloat = 44.0 + let emptyAnimationHeight = 148.0 + let topInset: CGFloat = component.insets.top + let bottomInset: CGFloat = max(component.insets.bottom, component.inputHeight) + let visibleHeight = availableSize.height + let emptyAnimationSpacing: CGFloat = 8.0 + let emptyTextSpacing: CGFloat = 8.0 + + let emptyResultsTitleSize = self.emptyResultsTitle.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: component.presentation.strings.HashtagSearch_NoResults, font: Font.semibold(17.0), textColor: component.presentation.theme.list.itemSecondaryTextColor)), + horizontalAlignment: .center + ) + ), + environment: {}, + containerSize: availableSize + ) + let emptyResultsTextSize = self.emptyResultsText.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: component.presentation.strings.HashtagSearch_NoResultsQueryDescription(query).string, font: Font.regular(15.0), textColor: component.presentation.theme.list.itemSecondaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0 + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) + ) + let emptyResultsAnimationSize = self.emptyResultsAnimation.update( + transition: .immediate, + component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "ChatListNoResults") + )), + environment: {}, + containerSize: CGSize(width: emptyAnimationHeight, height: emptyAnimationHeight) + ) + + let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyResultsTitleSize.height + emptyResultsTextSize.height + emptyTextSpacing + let emptyAnimationY = topInset + floorToScreenPixels((visibleHeight - topInset - bottomInset - emptyTotalHeight) / 2.0) + + let emptyResultsAnimationFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - emptyResultsAnimationSize.width) / 2.0), y: emptyAnimationY), size: emptyResultsAnimationSize) + + let emptyResultsTitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - emptyResultsTitleSize.width) / 2.0), y: emptyResultsAnimationFrame.maxY + emptyAnimationSpacing), size: emptyResultsTitleSize) + + let emptyResultsTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - emptyResultsTextSize.width) / 2.0), y: emptyResultsTitleFrame.maxY + emptyTextSpacing), size: emptyResultsTextSize) + + if let view = self.emptyResultsAnimation.view as? LottieComponent.View { + if view.superview == nil { + view.alpha = 0.0 + fadeTransition.setAlpha(view: view, alpha: 1.0) + self.addSubview(view) + view.playOnce() + } + view.bounds = CGRect(origin: .zero, size: emptyResultsAnimationFrame.size) + transition.setPosition(view: view, position: emptyResultsAnimationFrame.center) + } + if let view = self.emptyResultsTitle.view { + if view.superview == nil { + view.alpha = 0.0 + fadeTransition.setAlpha(view: view, alpha: 1.0) + self.addSubview(view) + } + view.bounds = CGRect(origin: .zero, size: emptyResultsTitleFrame.size) + transition.setPosition(view: view, position: emptyResultsTitleFrame.center) + } + if let view = self.emptyResultsText.view { + if view.superview == nil { + view.alpha = 0.0 + fadeTransition.setAlpha(view: view, alpha: 1.0) + self.addSubview(view) + } + view.bounds = CGRect(origin: .zero, size: emptyResultsTextFrame.size) + transition.setPosition(view: view, position: emptyResultsTextFrame.center) + } + } else { + if let view = self.emptyResultsAnimation.view { + fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in + view.removeFromSuperview() + }) + } + if let view = self.emptyResultsTitle.view { + fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in + view.removeFromSuperview() + }) + } + if let view = self.emptyResultsText.view { + fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in + view.removeFromSuperview() + }) + } + } + return availableSize } } diff --git a/submodules/TelegramUI/Components/Chat/ChatInputContextPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatInputContextPanelNode/BUILD index 39b8a7ad0b1..425f8436e7b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputContextPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatInputContextPanelNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/BUILD index 57e1434665a..4f770f28354 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/BUILD index a9607fe68e4..5223491b3ff 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", @@ -15,6 +15,8 @@ swift_library( "//submodules/AppBundle", "//submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl", "//submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView", + "//submodules/AccountContext", + "//submodules/TelegramUI/Components/TextNodeWithEntities", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m index eb3e9c739b1..a3356a56de4 100755 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m @@ -46,6 +46,10 @@ - (instancetype _Nonnull)initWithFrame:(CGRect)frame textContainer:(NSTextContai return self; } +- (BOOL)touchesShouldCancelInContentView:(UIView *)view { + return false; +} + - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return true; } diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift index 2c3823e1f38..cab565a18b2 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift @@ -6,6 +6,8 @@ import AppBundle import ChatInputTextViewImpl import MessageInlineBlockBackgroundView import TextFormat +import AccountContext +import TextNodeWithEntities public protocol ChatInputTextNodeDelegate: AnyObject { func chatInputTextNodeDidUpdateText() @@ -26,6 +28,171 @@ public protocol ChatInputTextNodeDelegate: AnyObject { func chatInputTextNodeTargetForAction(action: Selector) -> ChatInputTextNode.TargetForAction? } +@available(iOS 15.0, *) +private final class ChatInputTextLayoutManager: NSTextLayoutManager { + weak var contentStorage: ChatInputTextContentStorage? + + init(contentStorage: ChatInputTextContentStorage) { + self.contentStorage = contentStorage + + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @discardableResult + override func enumerateTextLayoutFragments(from location: NSTextLocation?, options: NSTextLayoutFragment.EnumerationOptions = [], using block: (NSTextLayoutFragment) -> Bool) -> NSTextLocation? { + /*guard let contentStorage = self.contentStorage else { + return nil + } + + var layoutFragments: [NSTextLayoutFragment] = [] + contentStorage.enumerateTextElements(from: contentStorage.documentRange.location, options: [], using: { textElement in + if let textElement = textElement as? NSTextParagraph { + let layoutFragment = BubbleLayoutFragment(textElement: textElement, range: textElement.elementRange) + layoutFragments.append(layoutFragment) + } else { + assertionFailure() + } + return true + }) + + /*super.enumerateTextLayoutFragments(from: self.documentRange.location, options: [.ensuresLayout, .ensuresExtraLineFragment], using: { fragment in + layoutFragments.append(fragment) + return true + })*/ + + let quoteId: (NSTextLayoutFragment) -> ObjectIdentifier? = { fragment in + guard let contentStorage = self.contentStorage else { + return nil + } + let lowerBound = contentStorage.offset(from: contentStorage.documentRange.location, to: fragment.rangeInElement.location) + let upperBound = contentStorage.offset(from: contentStorage.documentRange.location, to: fragment.rangeInElement.endLocation) + + if let textStorage = contentStorage.textStorage, lowerBound != NSNotFound, upperBound != NSNotFound, lowerBound >= 0, upperBound <= textStorage.length { + let fragmentString = textStorage.attributedSubstring(from: NSRange(location: lowerBound, length: upperBound - lowerBound)) + + if fragmentString.length != 0, let attribute = fragmentString.attribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), at: 0, effectiveRange: nil) as? ChatTextInputTextQuoteAttribute { + return ObjectIdentifier(attribute) + } + } + + return nil + } + + return super.enumerateTextLayoutFragments(from: location, options: options, using: { fragment in + var fragment = fragment + if let index = layoutFragments.firstIndex(where: { $0.rangeInElement.isEqual(to: fragment.rangeInElement) }) { + fragment = layoutFragments[index] + + if let fragment = fragment as? BubbleLayoutFragment { + if let fragmentQuoteId = quoteId(fragment) { + if index == 0 { + fragment.quoteIsFirst = false + } else if quoteId(layoutFragments[index - 1]) == fragmentQuoteId { + fragment.quoteIsFirst = false + } else { + fragment.quoteIsFirst = true + } + + if index == layoutFragments.count - 1 { + fragment.quoteIsLast = false + } else if quoteId(layoutFragments[index + 1]) == fragmentQuoteId { + fragment.quoteIsLast = false + } else { + fragment.quoteIsLast = true + } + } else { + fragment.quoteIsFirst = false + fragment.quoteIsLast = false + } + } + } else if layoutFragments.isEmpty { + } else { + assertionFailure() + } + + return block(fragment) + })*/ + return super.enumerateTextLayoutFragments(from: location, options: options, using: block) + } +} + +@available(iOS 15.0, *) +private class BubbleLayoutFragment: NSTextLayoutFragment { + var quoteIsFirst: Bool = false + var quoteIsLast: Bool = false + + override var leadingPadding: CGFloat { + return 0.0 + } + + override var trailingPadding: CGFloat { + return 0.0 + } + + override var topMargin: CGFloat { + return self.quoteIsFirst ? 10.0 : 0.0 + } + + override var bottomMargin: CGFloat { + return self.quoteIsLast ? 10.0 : 0.0 + } + + override var layoutFragmentFrame: CGRect { + let result = super.layoutFragmentFrame + return result + } + + override var renderingSurfaceBounds: CGRect { + return super.renderingSurfaceBounds + } + + private var tightTextBounds: CGRect { + var fragmentTextBounds = CGRect.null + for lineFragment in textLineFragments { + let lineFragmentBounds = lineFragment.typographicBounds + if fragmentTextBounds.isNull { + fragmentTextBounds = lineFragmentBounds + } else { + fragmentTextBounds = fragmentTextBounds.union(lineFragmentBounds) + } + } + return fragmentTextBounds + } + + // Return the bounding rect of the chat bubble, in the space of the first line fragment. + private var bubbleRect: CGRect { return tightTextBounds.insetBy(dx: -3, dy: -3) } + + private var bubbleCornerRadius: CGFloat { return 20 } + + private var bubbleColor: UIColor { return .systemIndigo.withAlphaComponent(0.5) } + + private func createBubblePath(with ctx: CGContext) -> CGPath { + let bubbleRect = self.bubbleRect + let rect = min(bubbleCornerRadius, bubbleRect.size.height / 2, bubbleRect.size.width / 2) + return CGPath(roundedRect: bubbleRect, cornerWidth: rect, cornerHeight: rect, transform: nil) + } + + override func draw(at renderingOrigin: CGPoint, in ctx: CGContext) { + // Draw the bubble and debug outline. + ctx.saveGState() + let bubblePath = createBubblePath(with: ctx) + ctx.addPath(bubblePath) + ctx.setFillColor(bubbleColor.cgColor) + ctx.fillPath() + ctx.restoreGState() + + var offset: CGFloat = 0.0 + for textLineFragment in self.textLineFragments { + textLineFragment.draw(at: CGPoint(x: renderingOrigin.x, y: renderingOrigin.y + offset), in: ctx) + offset += textLineFragment.typographicBounds.height + } + } +} + open class ChatInputTextNode: ASDisplayNode { public final class TargetForAction { public let target: Any? @@ -113,85 +280,676 @@ open class ChatInputTextNode: ASDisplayNode { } } } - - public init(disableTiling: Bool = false) { - super.init() - - self.setViewBlock({ - return ChatInputTextView(disableTiling: disableTiling) - }) - } - - public func resetInitialPrimaryLanguage() { - } + + public init(disableTiling: Bool = false) { + super.init() + + self.setViewBlock({ + return ChatInputTextView(disableTiling: disableTiling) + }) + } + + public func resetInitialPrimaryLanguage() { + } + + public func textHeightForWidth(_ width: CGFloat, rightInset: CGFloat) -> CGFloat { + return self.textView.textHeightForWidth(width, rightInset: rightInset) + } + + public func updateLayout(size: CGSize) { + self.textView.updateLayout(size: size) + } +} + +private final class ChatInputTextContainer: NSTextContainer { + var rightInset: CGFloat = 0.0 + + override var isSimpleRectangularTextContainer: Bool { + return false + } + + override init(size: CGSize) { + super.init(size: size) + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func lineFragmentRect(forProposedRect proposedRect: CGRect, at characterIndex: Int, writingDirection baseWritingDirection: NSWritingDirection, remaining remainingRect: UnsafeMutablePointer?) -> CGRect { + var result = super.lineFragmentRect(forProposedRect: proposedRect, at: characterIndex, writingDirection: baseWritingDirection, remaining: remainingRect) + + result.origin.x -= 5.0 + result.size.width -= 5.0 + result.size.width -= self.rightInset + + var attributedString: NSAttributedString? + if #available(iOS 15.0, *), let textLayoutManager = self.textLayoutManager as? ChatInputTextLayoutManager { + attributedString = textLayoutManager.contentStorage?.attributedString + } else if let textStorage = self.layoutManager?.textStorage { + attributedString = textStorage + } + + if let textStorage = attributedString { + let string: NSString = textStorage.string as NSString + let index = Int(characterIndex) + if index >= 0 && index < string.length { + let attributes = textStorage.attributes(at: index, effectiveRange: nil) + let blockQuote = attributes[NSAttributedString.Key(rawValue: "Attribute__Blockquote")] as? ChatTextInputTextQuoteAttribute + if let blockQuote { + result.origin.x += 9.0 + result.size.width -= 9.0 + result.size.width -= 7.0 + + var isFirstLine = false + if index == 0 { + isFirstLine = true + } else { + let previousAttributes = textStorage.attributes(at: index - 1, effectiveRange: nil) + let previousBlockQuote = previousAttributes[NSAttributedString.Key(rawValue: "Attribute__Blockquote")] as? NSObject + if let previousBlockQuote { + if !blockQuote.isEqual(previousBlockQuote) { + isFirstLine = true + } + } else { + isFirstLine = true + } + } + + if isFirstLine, case .quote = blockQuote.kind { + result.size.width -= 18.0 + } + } + } + } + + result.size.width = max(1.0, result.size.width) + + return result + } +} + +private final class ChatInputLegacyLayoutManager: NSLayoutManager { + override init() { + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func notShownAttribute(forGlyphAt glyphIndex: Int) -> Bool { + return true + } + + override func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) { + guard let context = UIGraphicsGetCurrentContext() else { + super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin) + return + } + let _ = context + /*for i in glyphsToShow.lowerBound ..< glyphsToShow.upperBound { + let rect = self.lineFragmentRect(forGlyphAt: i, effectiveRange: nil, withoutAdditionalLayout: true) + context.setAlpha(max(0.0, min(1.0, rect.minY / 200.0))) + let location = self.location(forGlyphAt: i) + super.drawGlyphs(forGlyphRange: NSRange(location: i, length: 1), at: location) + } + context.setAlpha(1.0)*/ + super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin) + } +} + +private struct DisplayBlockQuote { + var id: Int + var boundingRect: CGRect + var kind: ChatTextInputTextQuoteAttribute.Kind + var isCollapsed: Bool + var range: NSRange + + init(id: Int, boundingRect: CGRect, kind: ChatTextInputTextQuoteAttribute.Kind, isCollapsed: Bool, range: NSRange) { + self.id = id + self.boundingRect = boundingRect + self.kind = kind + self.isCollapsed = isCollapsed + self.range = range + } +} + +private protocol ChatInputTextInternal: AnyObject { + var textContainer: ChatInputTextContainer { get } + + var defaultTextContainerInset: UIEdgeInsets { get set } + + var updateDisplayElements: (() -> Void)? { get set } + var attributedString: NSAttributedString? { get } + + func invalidateLayout() + func setAttributedString(attributedString: NSAttributedString) + func textSize() -> CGSize + func currentTextBoundingRect() -> CGRect + func currentTextLastLineBoundingRect() -> CGRect + func displayBlockQuotes() -> [DisplayBlockQuote] +} + +private final class ChatInputTextLegacyInternal: NSObject, ChatInputTextInternal, NSLayoutManagerDelegate, NSTextStorageDelegate { + let textContainer: ChatInputTextContainer + let customTextStorage: NSTextStorage + let customLayoutManager: ChatInputLegacyLayoutManager + + var defaultTextContainerInset: UIEdgeInsets = UIEdgeInsets() + + var updateDisplayElements: (() -> Void)? + + var attributedString: NSAttributedString? { + return self.customTextStorage + } + + override init() { + self.textContainer = ChatInputTextContainer(size: CGSize(width: 100.0, height: 100000.0)) + self.customTextStorage = NSTextStorage() + self.customLayoutManager = ChatInputLegacyLayoutManager() + self.customTextStorage.addLayoutManager(self.customLayoutManager) + self.customLayoutManager.addTextContainer(self.textContainer) + + super.init() + + self.textContainer.widthTracksTextView = false + self.textContainer.heightTracksTextView = false + + self.customLayoutManager.delegate = self + self.customTextStorage.delegate = self + } + + @objc func layoutManager(_ layoutManager: NSLayoutManager, paragraphSpacingBeforeGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat { + guard let textStorage = layoutManager.textStorage else { + return 0.0 + } + let characterIndex = Int(layoutManager.characterIndexForGlyph(at: glyphIndex)) + if characterIndex < 0 || characterIndex >= textStorage.length { + return 0.0 + } + + let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil) + guard let blockQuote = attributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject else { + return 0.0 + } + + if characterIndex != 0 { + let previousAttributes = textStorage.attributes(at: characterIndex - 1, effectiveRange: nil) + let previousBlockQuote = previousAttributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject + if let previousBlockQuote, blockQuote.isEqual(previousBlockQuote) { + return 0.0 + } + } + + return 8.0 + } + + @objc func layoutManager(_ layoutManager: NSLayoutManager, paragraphSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat { + guard let textStorage = layoutManager.textStorage else { + return 0.0 + } + var characterIndex = Int(layoutManager.characterIndexForGlyph(at: glyphIndex)) + characterIndex -= 1 + if characterIndex < 0 { + characterIndex = 0 + } + if characterIndex < 0 || characterIndex >= textStorage.length { + return 0.0 + } + + let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil) + guard let blockQuote = attributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject else { + return 0.0 + } + + if characterIndex + 1 < textStorage.length { + let nextAttributes = textStorage.attributes(at: characterIndex + 1, effectiveRange: nil) + let nextBlockQuote = nextAttributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject + if let nextBlockQuote, blockQuote.isEqual(nextBlockQuote) { + return 0.0 + } + } + + return 8.0 + } + + @objc func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) { + if textContainer !== self.textContainer { + return + } + self.updateDisplayElements?() + } + + func invalidateLayout() { + self.customLayoutManager.invalidateLayout(forCharacterRange: NSRange(location: 0, length: self.customTextStorage.length), actualCharacterRange: nil) + self.customLayoutManager.ensureLayout(for: self.textContainer) + } + + func setAttributedString(attributedString: NSAttributedString) { + self.customTextStorage.setAttributedString(attributedString) + } + + func textSize() -> CGSize { + return self.customLayoutManager.usedRect(for: self.textContainer).size + } + + func currentTextBoundingRect() -> CGRect { + let glyphRange = self.customLayoutManager.glyphRange(forCharacterRange: NSRange(location: 0, length: self.customTextStorage.length), actualCharacterRange: nil) + + var boundingRect = CGRect() + var startIndex = glyphRange.lowerBound + while startIndex < glyphRange.upperBound { + var effectiveRange = NSRange(location: NSNotFound, length: 0) + var rect = self.customLayoutManager.lineFragmentUsedRect(forGlyphAt: startIndex, effectiveRange: &effectiveRange) + + let characterRange = self.customLayoutManager.characterRange(forGlyphRange: NSRange(location: startIndex, length: 1), actualGlyphRange: nil) + if characterRange.location != NSNotFound { + if let attribute = self.customTextStorage.attribute(NSAttributedString.Key("Attribute__Blockquote"), at: characterRange.location, effectiveRange: nil) { + let _ = attribute + rect.size.width += 13.0 + } else if let attribute = self.customTextStorage.attribute(.attachment, at: characterRange.location, effectiveRange: nil) as? ChatInputTextCollapsedQuoteAttachment { + let _ = attribute + rect.size.width += 8.0 + } + } + + if boundingRect.isEmpty { + boundingRect = rect + } else { + boundingRect = boundingRect.union(rect) + } + if effectiveRange.location != NSNotFound { + startIndex = max(startIndex + 1, effectiveRange.upperBound) + } else { + break + } + } + + return boundingRect + } + + func currentTextLastLineBoundingRect() -> CGRect { + let glyphRange = self.customLayoutManager.glyphRange(forCharacterRange: NSRange(location: 0, length: self.customTextStorage.length), actualCharacterRange: nil) + var boundingRect = CGRect() + var startIndex = glyphRange.lowerBound + while startIndex < glyphRange.upperBound { + var effectiveRange = NSRange(location: NSNotFound, length: 0) + let rect = self.customLayoutManager.lineFragmentUsedRect(forGlyphAt: startIndex, effectiveRange: &effectiveRange) + boundingRect = rect + if effectiveRange.location != NSNotFound { + startIndex = max(startIndex + 1, effectiveRange.upperBound) + } else { + break + } + } + return boundingRect + } + + func displayBlockQuotes() -> [DisplayBlockQuote] { + var result: [DisplayBlockQuote] = [] + var blockQuoteIndex = 0 + self.customTextStorage.enumerateAttribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), in: NSRange(location: 0, length: self.customTextStorage.length), using: { value, range, _ in + if let value = value as? ChatTextInputTextQuoteAttribute { + let glyphRange = self.customLayoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil) + if self.customLayoutManager.isValidGlyphIndex(glyphRange.location) && self.customLayoutManager.isValidGlyphIndex(glyphRange.location + glyphRange.length - 1) { + } else { + return + } + + let id = blockQuoteIndex + + var boundingRect = CGRect() + var startIndex = glyphRange.lowerBound + while startIndex < glyphRange.upperBound { + var effectiveRange = NSRange(location: NSNotFound, length: 0) + let rect = self.customLayoutManager.lineFragmentUsedRect(forGlyphAt: startIndex, effectiveRange: &effectiveRange) + if boundingRect.isEmpty { + boundingRect = rect + } else { + boundingRect = boundingRect.union(rect) + } + if effectiveRange.location != NSNotFound { + startIndex = max(startIndex + 1, effectiveRange.upperBound) + } else { + break + } + } + + boundingRect.origin.y += self.defaultTextContainerInset.top + + boundingRect.origin.x -= 4.0 + boundingRect.size.width += 4.0 + if case .quote = value.kind { + boundingRect.size.width += 18.0 + boundingRect.size.width = min(boundingRect.size.width, self.textContainer.size.width - 18.0) + } + boundingRect.size.width = min(boundingRect.size.width, self.textContainer.size.width) + + boundingRect.origin.y -= 4.0 + boundingRect.size.height += 8.0 + + result.append(DisplayBlockQuote(id: id, boundingRect: boundingRect, kind: value.kind, isCollapsed: value.isCollapsed, range: range)) + + blockQuoteIndex += 1 + } + }) + self.customTextStorage.enumerateAttribute(NSAttributedString.Key.attachment, in: NSRange(location: 0, length: self.customTextStorage.length), using: { value, range, _ in + if let _ = value as? ChatInputTextCollapsedQuoteAttachment { + let glyphRange = self.customLayoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil) + if self.customLayoutManager.isValidGlyphIndex(glyphRange.location) && self.customLayoutManager.isValidGlyphIndex(glyphRange.location + glyphRange.length - 1) { + } else { + return + } + + let id = blockQuoteIndex + + var boundingRect = CGRect() + var startIndex = glyphRange.lowerBound + while startIndex < glyphRange.upperBound { + var effectiveRange = NSRange(location: NSNotFound, length: 0) + let rect = self.customLayoutManager.lineFragmentUsedRect(forGlyphAt: startIndex, effectiveRange: &effectiveRange) + if boundingRect.isEmpty { + boundingRect = rect + } else { + boundingRect = boundingRect.union(rect) + } + if effectiveRange.location != NSNotFound { + startIndex = max(startIndex + 1, effectiveRange.upperBound) + } else { + break + } + } + + boundingRect.origin.y += self.defaultTextContainerInset.top + + boundingRect.origin.x += 5.0 + boundingRect.size.width += 4.0 + boundingRect.size.width += 18.0 + boundingRect.size.width = min(boundingRect.size.width, self.textContainer.size.width - 18.0) + boundingRect.size.width = min(boundingRect.size.width, self.textContainer.size.width) + + boundingRect.origin.y += 4.0 + boundingRect.size.height -= 8.0 + + result.append(DisplayBlockQuote(id: id, boundingRect: boundingRect, kind: .quote, isCollapsed: true, range: range)) + + blockQuoteIndex += 1 + } + }) + return result + } +} + +@available(iOS 15.0, *) +private final class ChatInputTextContentStorage: NSTextContentStorage { + +} + +@available(iOS 15.0, *) +private final class ChatInputTextNewInternal: NSObject, ChatInputTextInternal, NSTextContentStorageDelegate, NSTextLayoutManagerDelegate { + let textContainer: ChatInputTextContainer + let contentStorage: ChatInputTextContentStorage + let customLayoutManager: ChatInputTextLayoutManager + + var defaultTextContainerInset: UIEdgeInsets = UIEdgeInsets() + + var updateDisplayElements: (() -> Void)? + + var attributedString: NSAttributedString? { + return self.contentStorage.attributedString + } + + override init() { + self.textContainer = ChatInputTextContainer(size: CGSize(width: 100.0, height: 100000.0)) + self.contentStorage = ChatInputTextContentStorage() + self.customLayoutManager = ChatInputTextLayoutManager(contentStorage: self.contentStorage) + self.contentStorage.addTextLayoutManager(self.customLayoutManager) + self.customLayoutManager.textContainer = self.textContainer + + super.init() + + self.contentStorage.delegate = self + self.customLayoutManager.delegate = self + } + + func invalidateLayout() { + self.customLayoutManager.invalidateLayout(for: self.contentStorage.documentRange) + self.customLayoutManager.ensureLayout(for: self.contentStorage.documentRange) + } + + func setAttributedString(attributedString: NSAttributedString) { + self.contentStorage.attributedString = attributedString + } + + func textSize() -> CGSize { + return self.currentTextBoundingRect().size + } + + func currentTextBoundingRect() -> CGRect { + var boundingRect = CGRect() + self.customLayoutManager.enumerateTextLayoutFragments(from: self.contentStorage.documentRange.location, options: [.ensuresLayout, .ensuresExtraLineFragment], using: { fragment in + let fragmentFrame = fragment.layoutFragmentFrame + if boundingRect.isEmpty { + boundingRect = fragmentFrame + } else { + boundingRect = boundingRect.union(fragmentFrame) + } + return true + }) + + return boundingRect + } + + func currentTextLastLineBoundingRect() -> CGRect { + var boundingRect = CGRect() + self.customLayoutManager.enumerateTextLayoutFragments(from: self.contentStorage.documentRange.location, options: [.ensuresLayout, .ensuresExtraLineFragment], using: { fragment in + let fragmentFrame = fragment.layoutFragmentFrame + for textLineFragment in fragment.textLineFragments { + boundingRect = textLineFragment.typographicBounds.offsetBy(dx: fragmentFrame.minX, dy: fragmentFrame.minY) + } + return true + }) + + return boundingRect + } + + @objc func textLayoutManager(_ textLayoutManager: NSTextLayoutManager, textLayoutFragmentFor location: NSTextLocation, in textElement: NSTextElement) -> NSTextLayoutFragment { + let layoutFragment = BubbleLayoutFragment(textElement: textElement, range: textElement.elementRange) + return layoutFragment + } + + func displayBlockQuotes() -> [DisplayBlockQuote] { + var nextId = 0 + var result: [ObjectIdentifier: DisplayBlockQuote] = [:] + + self.customLayoutManager.enumerateTextLayoutFragments(from: self.contentStorage.documentRange.location, options: [.ensuresLayout, .ensuresExtraLineFragment], using: { fragment in + let lowerBound = self.contentStorage.offset(from: self.contentStorage.documentRange.location, to: fragment.rangeInElement.location) + let upperBound = self.contentStorage.offset(from: self.contentStorage.documentRange.location, to: fragment.rangeInElement.endLocation) + if let textStorage = self.contentStorage.textStorage, lowerBound != NSNotFound, upperBound != NSNotFound, lowerBound >= 0, upperBound <= textStorage.length { + let fragmentRange = NSRange(location: lowerBound, length: upperBound - lowerBound) + let fragmentString = textStorage.attributedSubstring(from: fragmentRange) + + var fragmentFrame = fragment.layoutFragmentFrame + + if fragmentString.length != 0, let attribute = fragmentString.attribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), at: 0, effectiveRange: nil) as? ChatTextInputTextQuoteAttribute { + fragmentFrame.origin.y += self.defaultTextContainerInset.top + + fragmentFrame.origin.x -= 4.0 + fragmentFrame.size.width += 4.0 + if case .quote = attribute.kind { + fragmentFrame.size.width += 18.0 + fragmentFrame.size.width = min(fragmentFrame.size.width, self.textContainer.size.width - 18.0) + } + fragmentFrame.size.width = min(fragmentFrame.size.width, self.textContainer.size.width) + + let quoteId = ObjectIdentifier(attribute) + if var current = result[quoteId] { + current.boundingRect = current.boundingRect.union(fragmentFrame) + + let newLowerBound = min(current.range.lowerBound, fragmentRange.lowerBound) + let newUpperBound = max(current.range.upperBound, fragmentRange.upperBound) + + current.range = NSRange(location: newLowerBound, length: newUpperBound - newLowerBound) + result[quoteId] = current + } else { + let id = nextId + nextId += 1 + result[quoteId] = DisplayBlockQuote(id: id, boundingRect: fragmentFrame, kind: attribute.kind, isCollapsed: attribute.isCollapsed, range: fragmentRange) + } + } + } + + return true + }) + + return Array(result.values).sorted(by: { lhs, rhs in + return lhs.boundingRect.minY < rhs.boundingRect.minY + }) + } +} + +private let registeredViewProvider: Void = { + if #available(iOS 15.0, *) { + NSTextAttachment.registerViewProviderClass(ChatInputTextCollapsedQuoteAttachmentImpl.ViewProvider.self, forFileType: "public.data") + } +}() + +public final class ChatInputTextCollapsedQuoteAttachmentImpl: NSTextAttachment, ChatInputTextCollapsedQuoteAttachment { + final class View: UIView { + let attachment: ChatInputTextCollapsedQuoteAttachmentImpl + let textNode: ImmediateTextNodeWithEntities + + init(attachment: ChatInputTextCollapsedQuoteAttachmentImpl) { + self.attachment = attachment + self.textNode = ImmediateTextNodeWithEntities() + self.textNode.displaysAsynchronously = false + self.textNode.maximumNumberOfLines = 3 + + super.init(frame: CGRect()) + + self.addSubview(self.textNode.view) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + static func calculateSize(attachment: ChatInputTextCollapsedQuoteAttachmentImpl, constrainedSize: CGSize) -> CGSize { + guard let context = attachment.attributes.context as? AccountContext else { + return CGSize(width: 10.0, height: 10.0) + } + + let renderingText = textAttributedStringForStateText( + context: context, + stateText: attachment.text, + fontSize: attachment.attributes.fontSize, + textColor: attachment.attributes.textColor, + accentTextColor: attachment.attributes.accentTextColor, + writingDirection: nil, + spoilersRevealed: false, + availableEmojis: Set(context.animatedEmojiStickersValue.keys), + emojiViewProvider: nil, + makeCollapsedQuoteAttachment: nil + ) + + let textNode = ImmediateTextNode() + textNode.maximumNumberOfLines = 3 + + textNode.attributedText = renderingText + textNode.cutout = TextNodeCutout(topRight: CGSize(width: 30.0, height: 10.0)) + + let layoutSize = textNode.updateLayout(CGSize(width: constrainedSize.width - 9.0, height: constrainedSize.height)) + + return CGSize(width: constrainedSize.width, height: 8.0 + layoutSize.height + 8.0) + } + + override func layoutSubviews() { + super.layoutSubviews() + + guard let context = self.attachment.attributes.context as? AccountContext else { + return + } + + let renderingText = textAttributedStringForStateText( + context: context, stateText: self.attachment.text, + fontSize: self.attachment.attributes.fontSize, + textColor: self.attachment.attributes.textColor, + accentTextColor: self.attachment.attributes.accentTextColor, + writingDirection: nil, + spoilersRevealed: false, + availableEmojis: Set(context.animatedEmojiStickersValue.keys), + emojiViewProvider: nil, + makeCollapsedQuoteAttachment: nil + ) + + /*let renderingText = NSMutableAttributedString(attributedString: attachment.text) + renderingText.addAttribute(.font, value: attachment.attributes.font, range: NSRange(location: 0, length: renderingText.length)) + renderingText.addAttribute(.foregroundColor, value: attachment.attributes.textColor, range: NSRange(location: 0, length: renderingText.length))*/ + + self.textNode.arguments = TextNodeWithEntities.Arguments( + context: context, + cache: context.animationCache, + renderer: context.animationRenderer, + placeholderColor: .gray, + attemptSynchronous: true + ) + + self.textNode.attributedText = renderingText + self.textNode.cutout = TextNodeCutout(topRight: CGSize(width: 30.0, height: 10.0)) + + self.textNode.displaySpoilerEffect = true + self.textNode.visibility = true + + let maxTextSize = CGSize(width: self.bounds.size.width - 9.0, height: self.bounds.size.height) + let layoutSize = self.textNode.updateLayout(maxTextSize) + + self.textNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 8.0), size: layoutSize) + } + } - public func textHeightForWidth(_ width: CGFloat, rightInset: CGFloat) -> CGFloat { - return self.textView.textHeightForWidth(width, rightInset: rightInset) + @available(iOS 15.0, *) + final class ViewProvider: NSTextAttachmentViewProvider { + override init( + textAttachment: NSTextAttachment, + parentView: UIView?, + textLayoutManager: NSTextLayoutManager?, + location: NSTextLocation + ) { + super.init(textAttachment: textAttachment, parentView: parentView, textLayoutManager: textLayoutManager, location: location) + } + + override public func loadView() { + if let textAttachment = self.textAttachment as? ChatInputTextCollapsedQuoteAttachmentImpl { + self.view = View(attachment: textAttachment) + } else { + self.view = UIView() + } + } } - public func updateLayout(size: CGSize) { - self.textView.updateLayout(size: size) - } -} - -private final class ChatInputTextContainer: NSTextContainer { - var rightInset: CGFloat = 0.0 + public let text: NSAttributedString + public let attributes: ChatInputTextCollapsedQuoteAttributes - override var isSimpleRectangularTextContainer: Bool { - return false + public init(text: NSAttributedString, attributes: ChatInputTextCollapsedQuoteAttributes) { + let _ = registeredViewProvider + + self.text = text + self.attributes = attributes + + super.init(data: nil, ofType: "public.data") } - override init(size: CGSize) { - super.init(size: size) + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } - required init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + override public func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect { + return CGRect(origin: CGPoint(), size: View.calculateSize(attachment: self, constrainedSize: CGSize(width: lineFrag.width, height: 10000.0))) } - override func lineFragmentRect(forProposedRect proposedRect: CGRect, at characterIndex: Int, writingDirection baseWritingDirection: NSWritingDirection, remaining remainingRect: UnsafeMutablePointer?) -> CGRect { - var result = super.lineFragmentRect(forProposedRect: proposedRect, at: characterIndex, writingDirection: baseWritingDirection, remaining: remainingRect) - - result.origin.x -= 5.0 - result.size.width -= 5.0 - result.size.width -= self.rightInset - - if let textStorage = self.layoutManager?.textStorage { - let string: NSString = textStorage.string as NSString - let index = Int(characterIndex) - if index >= 0 && index < string.length { - let attributes = textStorage.attributes(at: index, effectiveRange: nil) - let blockQuote = attributes[NSAttributedString.Key(rawValue: "Attribute__Blockquote")] as? ChatTextInputTextQuoteAttribute - if let blockQuote { - result.origin.x += 9.0 - result.size.width -= 9.0 - result.size.width -= 7.0 - - var isFirstLine = false - if index == 0 { - isFirstLine = true - } else { - let previousAttributes = textStorage.attributes(at: index - 1, effectiveRange: nil) - let previousBlockQuote = previousAttributes[NSAttributedString.Key(rawValue: "Attribute__Blockquote")] as? NSObject - if let previousBlockQuote { - if !blockQuote.isEqual(previousBlockQuote) { - isFirstLine = true - } - } else { - isFirstLine = true - } - } - - if isFirstLine, case .quote = blockQuote.kind { - result.size.width -= 18.0 - } - } - } - } - - result.size.width = max(1.0, result.size.width) - - return result + override public func image(forBounds imageBounds: CGRect, textContainer: NSTextContainer?, characterIndex charIndex: Int) -> UIImage? { + return nil } } @@ -291,13 +1049,10 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, } } - private let customTextContainer: ChatInputTextContainer - private let customTextStorage: NSTextStorage - private let customLayoutManager: NSLayoutManager + public var toggleQuoteCollapse: ((NSRange) -> Void)? - private let measurementTextContainer: ChatInputTextContainer - private let measurementTextStorage: NSTextStorage - private let measurementLayoutManager: NSLayoutManager + private let displayInternal: ChatInputTextInternal + private let measureInternal: ChatInputTextInternal private var validLayoutSize: CGSize? private var isUpdatingLayout: Bool = false @@ -312,6 +1067,10 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, } } + public var currentRightInset: CGFloat { + return self.displayInternal.textContainer.rightInset + } + private var didInitializePrimaryInputLanguage: Bool = false public var initialPrimaryLanguage: String? @@ -338,22 +1097,24 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, } public init(disableTiling: Bool) { - self.customTextContainer = ChatInputTextContainer(size: CGSize(width: 100.0, height: 100000.0)) - self.customLayoutManager = NSLayoutManager() - self.customTextStorage = NSTextStorage() - self.customTextStorage.addLayoutManager(self.customLayoutManager) - self.customLayoutManager.addTextContainer(self.customTextContainer) + let useModernImpl = !"".isEmpty - self.measurementTextContainer = ChatInputTextContainer(size: CGSize(width: 100.0, height: 100000.0)) - self.measurementLayoutManager = NSLayoutManager() - self.measurementTextStorage = NSTextStorage() - self.measurementTextStorage.addLayoutManager(self.measurementLayoutManager) - self.measurementLayoutManager.addTextContainer(self.measurementTextContainer) + if #available(iOS 15.0, *), useModernImpl { + self.displayInternal = ChatInputTextNewInternal() + self.measureInternal = ChatInputTextNewInternal() + } else { + self.displayInternal = ChatInputTextLegacyInternal() + self.measureInternal = ChatInputTextLegacyInternal() + } - super.init(frame: CGRect(), textContainer: self.customTextContainer, disableTiling: disableTiling) + super.init(frame: CGRect(), textContainer: self.displayInternal.textContainer, disableTiling: disableTiling) self.delegate = self + self.displayInternal.updateDisplayElements = { [weak self] in + self?.updateTextElements() + } + self.shouldRespondToAction = { [weak self] action in guard let self, let action else { return false @@ -381,18 +1142,6 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, self.backgroundColor = nil self.isOpaque = false - self.customTextContainer.widthTracksTextView = false - self.customTextContainer.heightTracksTextView = false - - self.measurementTextContainer.widthTracksTextView = false - self.measurementTextContainer.heightTracksTextView = false - - self.customLayoutManager.delegate = self - self.measurementLayoutManager.delegate = self - - self.customTextStorage.delegate = self - self.measurementTextStorage.delegate = self - self.dropAutocorrectioniOS16 = { [weak self] in guard let self else { return @@ -458,73 +1207,6 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, super.scrollRectToVisible(rect, animated: animated) } - @objc public func layoutManager(_ layoutManager: NSLayoutManager, paragraphSpacingBeforeGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat { - guard let textStorage = layoutManager.textStorage else { - return 0.0 - } - let characterIndex = Int(layoutManager.characterIndexForGlyph(at: glyphIndex)) - if characterIndex < 0 || characterIndex >= textStorage.length { - return 0.0 - } - - let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil) - guard let blockQuote = attributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject else { - return 0.0 - } - - if characterIndex != 0 { - let previousAttributes = textStorage.attributes(at: characterIndex - 1, effectiveRange: nil) - let previousBlockQuote = previousAttributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject - if let previousBlockQuote, blockQuote.isEqual(previousBlockQuote) { - return 0.0 - } - } - - return 8.0 - } - - @objc public func layoutManager(_ layoutManager: NSLayoutManager, paragraphSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat { - guard let textStorage = layoutManager.textStorage else { - return 0.0 - } - var characterIndex = Int(layoutManager.characterIndexForGlyph(at: glyphIndex)) - characterIndex -= 1 - if characterIndex < 0 { - characterIndex = 0 - } - if characterIndex < 0 || characterIndex >= textStorage.length { - return 0.0 - } - - let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil) - guard let blockQuote = attributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject else { - return 0.0 - } - - if characterIndex + 1 < textStorage.length { - let nextAttributes = textStorage.attributes(at: characterIndex + 1, effectiveRange: nil) - let nextBlockQuote = nextAttributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject - if let nextBlockQuote, blockQuote.isEqual(nextBlockQuote) { - return 0.0 - } - } - - return 8.0 - } - - public func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) { - if textStorage !== self.customTextStorage { - return - } - } - - public func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) { - if textContainer !== self.customTextContainer { - return - } - self.updateTextElements() - } - @objc public func textViewDidBeginEditing(_ textView: UITextView) { self.customDelegate?.chatInputTextNodeDidBeginEditing() } @@ -574,20 +1256,23 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, } public func updateTextContainerInset() { + self.displayInternal.defaultTextContainerInset = self.defaultTextContainerInset + self.measureInternal.defaultTextContainerInset = self.defaultTextContainerInset + var result = self.defaultTextContainerInset var horizontalInsetsUpdated = false - if self.customTextContainer.rightInset != result.right { + if self.displayInternal.textContainer.rightInset != result.right { horizontalInsetsUpdated = true - self.customTextContainer.rightInset = result.right + self.displayInternal.textContainer.rightInset = result.right } result.left = 0.0 result.right = 0.0 - if self.customTextStorage.length != 0 { - let topAttributes = self.customTextStorage.attributes(at: 0, effectiveRange: nil) - let bottomAttributes = self.customTextStorage.attributes(at: self.customTextStorage.length - 1, effectiveRange: nil) + if let string = self.displayInternal.attributedString, string.length != 0 { + let topAttributes = string.attributes(at: 0, effectiveRange: nil) + let bottomAttributes = string.attributes(at: string.length - 1, effectiveRange: nil) if topAttributes[NSAttributedString.Key("Attribute__Blockquote")] != nil { result.top += 7.0 @@ -601,8 +1286,7 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, self.textContainerInset = result } if horizontalInsetsUpdated { - self.customLayoutManager.invalidateLayout(forCharacterRange: NSRange(location: 0, length: self.customTextStorage.length), actualCharacterRange: nil) - self.customLayoutManager.ensureLayout(for: self.customTextContainer) + self.displayInternal.invalidateLayout() } self.updateTextElements() @@ -618,15 +1302,14 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, measureText = NSAttributedString(string: "A", attributes: self.typingAttributes) } - if self.measurementTextStorage != measureText || self.measurementTextContainer.size != measureSize || self.measurementTextContainer.rightInset != rightInset { - self.measurementTextContainer.rightInset = rightInset - self.measurementTextStorage.setAttributedString(measureText) - self.measurementTextContainer.size = measureSize - self.measurementLayoutManager.invalidateLayout(forCharacterRange: NSRange(location: 0, length: self.measurementTextStorage.length), actualCharacterRange: nil) - self.measurementLayoutManager.ensureLayout(for: self.measurementTextContainer) + if self.measureInternal.attributedString != measureText || self.measureInternal.textContainer.size != measureSize || self.measureInternal.textContainer.rightInset != rightInset { + self.measureInternal.textContainer.rightInset = rightInset + self.measureInternal.setAttributedString(attributedString: measureText) + self.measureInternal.textContainer.size = measureSize + self.measureInternal.invalidateLayout() } - let textSize = self.measurementLayoutManager.usedRect(for: self.measurementTextContainer).size + let textSize = self.measureInternal.textSize() return ceil(textSize.height + self.textContainerInset.top + self.textContainerInset.bottom) } @@ -636,8 +1319,7 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, if self.textContainer.size != measureSize { self.textContainer.size = measureSize - self.customLayoutManager.invalidateLayout(forCharacterRange: NSRange(location: 0, length: self.customTextStorage.length), actualCharacterRange: nil) - self.customLayoutManager.ensureLayout(for: self.customTextContainer) + self.displayInternal.invalidateLayout() } } @@ -656,70 +1338,38 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, self.isUpdatingLayout = false } + public func currentTextBoundingRect() -> CGRect { + return self.displayInternal.currentTextBoundingRect() + } + + public func lastLineBoundingRect() -> CGRect { + return self.displayInternal.currentTextLastLineBoundingRect() + } + public func updateTextElements() { - var blockQuoteIndex = 0 var validBlockQuotes: [Int] = [] - - self.textStorage.enumerateAttribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), in: NSRange(location: 0, length: self.textStorage.length), using: { value, range, _ in - if let value = value as? ChatTextInputTextQuoteAttribute { - let glyphRange = self.customLayoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil) - if self.customLayoutManager.isValidGlyphIndex(glyphRange.location) && self.customLayoutManager.isValidGlyphIndex(glyphRange.location + glyphRange.length - 1) { - } else { - return - } - - let id = blockQuoteIndex - - let blockQuote: QuoteBackgroundView - if let current = self.blockQuotes[id] { - blockQuote = current - } else { - blockQuote = QuoteBackgroundView() - self.blockQuotes[id] = blockQuote - self.insertSubview(blockQuote, at: 0) - } - - var boundingRect = self.customLayoutManager.boundingRect(forGlyphRange: glyphRange, in: self.customTextContainer) - - boundingRect = CGRect() - var startIndex = glyphRange.lowerBound - while startIndex < glyphRange.upperBound { - var effectiveRange = NSRange(location: NSNotFound, length: 0) - let rect = self.customLayoutManager.lineFragmentUsedRect(forGlyphAt: startIndex, effectiveRange: &effectiveRange) - if boundingRect.isEmpty { - boundingRect = rect - } else { - boundingRect = boundingRect.union(rect) - } - if effectiveRange.location != NSNotFound { - startIndex = max(startIndex + 1, effectiveRange.upperBound) - } else { - break + for displayBlockQuote in self.displayInternal.displayBlockQuotes() { + let blockQuote: QuoteBackgroundView + if let current = self.blockQuotes[displayBlockQuote.id] { + blockQuote = current + } else { + blockQuote = QuoteBackgroundView(toggleCollapse: { [weak self] range in + guard let self else { + return } - } - - boundingRect.origin.y += self.defaultTextContainerInset.top - - boundingRect.origin.x -= 4.0 - boundingRect.size.width += 4.0 - if case .quote = value.kind { - boundingRect.size.width += 18.0 - boundingRect.size.width = min(boundingRect.size.width, self.bounds.width - 18.0) - } - boundingRect.size.width = min(boundingRect.size.width, self.bounds.width) - - boundingRect.origin.y -= 4.0 - boundingRect.size.height += 8.0 - - blockQuote.frame = boundingRect - if let theme = self.theme { - blockQuote.update(value: value, size: boundingRect.size, theme: theme.quote) - } - - validBlockQuotes.append(blockQuoteIndex) - blockQuoteIndex += 1 + self.toggleQuoteCollapse?(range) + }) + self.blockQuotes[displayBlockQuote.id] = blockQuote + self.insertSubview(blockQuote, at: 0) } - }) + + blockQuote.frame = displayBlockQuote.boundingRect + if let theme = self.theme { + blockQuote.update(kind: displayBlockQuote.kind, isCollapsed: displayBlockQuote.isCollapsed, range: displayBlockQuote.range, size: displayBlockQuote.boundingRect.size, theme: theme.quote) + } + + validBlockQuotes.append(displayBlockQuote.id) + } var removedBlockQuotes: [Int] = [] for (id, blockQuote) in self.blockQuotes { @@ -734,129 +1384,121 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, } override public func caretRect(for position: UITextPosition) -> CGRect { - var result = super.caretRect(for: position) - - if "".isEmpty { - return result - } - - guard let textStorage = self.customLayoutManager.textStorage else { - return result - } - let _ = textStorage - - let index = self.offset(from: self.beginningOfDocument, to: position) - - let glyphRange = self.customLayoutManager.glyphRange(forCharacterRange: NSMakeRange(index, 1), actualCharacterRange: nil) - var boundingRect = self.customLayoutManager.boundingRect(forGlyphRange: glyphRange, in: self.customTextContainer) - - boundingRect.origin.y += 5.0 - - result.origin.y = boundingRect.minY - result.size.height = boundingRect.height - - return result + return super.caretRect(for: position) } override public func selectionRects(for range: UITextRange) -> [UITextSelectionRect] { - let sourceRects = super.selectionRects(for: range) - - var result: [UITextSelectionRect] = [] - for rect in sourceRects { - var mappedRect = rect.rect - //mappedRect.size.height = 10.0 - mappedRect.size.height += 0.0 - result.append(CustomTextSelectionRect( - rect: mappedRect, - writingDirection: rect.writingDirection, - containsStart: rect.containsStart, - containsEnd: rect.containsEnd, - isVertical: rect.isVertical - )) - } - - return result + return super.selectionRects(for: range) } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.bounds.contains(point) { + for (_, blockQuote) in self.blockQuotes { + if let result = blockQuote.collapseButton.hitTest(self.convert(point, to: blockQuote.collapseButton), with: event) { + return result + } + } + } + let result = super.hitTest(point, with: event) return result } } -private final class CustomTextSelectionRect: UITextSelectionRect { - let rectValue: CGRect - let writingDirectionValue: NSWritingDirection - let containsStartValue: Bool - let containsEndValue: Bool - let isVerticalValue: Bool - - override var rect: CGRect { - return self.rectValue - } - override var writingDirection: NSWritingDirection { - return self.writingDirectionValue - } - override var containsStart: Bool { - return self.containsStartValue - } - override var containsEnd: Bool { - return self.containsEndValue - } - override var isVertical: Bool { - return self.isVerticalValue - } - - init(rect: CGRect, writingDirection: NSWritingDirection, containsStart: Bool, containsEnd: Bool, isVertical: Bool) { - self.rectValue = rect - self.writingDirectionValue = writingDirection - self.containsStartValue = containsStart - self.containsEndValue = containsEnd - self.isVerticalValue = isVertical - } -} - private let quoteIcon: UIImage = { return UIImage(bundleImageName: "Chat/Message/ReplyQuoteIcon")!.precomposed().withRenderingMode(.alwaysTemplate) }() +private let quoteCollapseImage: UIImage = { + return UIImage(bundleImageName: "Media Gallery/Minimize")!.precomposed().withRenderingMode(.alwaysTemplate) +}() + +private let quoteExpandImage: UIImage = { + return UIImage(bundleImageName: "Media Gallery/Fullscreen")!.precomposed().withRenderingMode(.alwaysTemplate) +}() + private final class QuoteBackgroundView: UIView { + private let toggleCollapse: (NSRange) -> Void + private let backgroundView: MessageInlineBlockBackgroundView private let iconView: UIImageView + let collapseButton: UIView + let collapseButtonIconView: UIImageView + private var range: NSRange? private var theme: ChatInputTextView.Theme.Quote? - override init(frame: CGRect) { + init(toggleCollapse: @escaping (NSRange) -> Void) { + self.toggleCollapse = toggleCollapse + self.backgroundView = MessageInlineBlockBackgroundView() self.iconView = UIImageView(image: quoteIcon) - super.init(frame: frame) + self.collapseButton = UIView() + self.collapseButtonIconView = UIImageView() + self.collapseButton.addSubview(self.collapseButtonIconView) + + super.init(frame: CGRect()) self.addSubview(self.backgroundView) self.addSubview(self.iconView) + self.addSubview(self.collapseButton) + + self.collapseButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.toggleCollapsedTapped(_:)))) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - func update(value: ChatTextInputTextQuoteAttribute, size: CGSize, theme: ChatInputTextView.Theme.Quote) { + @objc private func toggleCollapsedTapped(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + if let range = self.range { + self.toggleCollapse(range) + } + } + } + + func update(kind: ChatTextInputTextQuoteAttribute.Kind, isCollapsed: Bool, range: NSRange, size: CGSize, theme: ChatInputTextView.Theme.Quote) { + self.range = range + if self.theme != theme { self.theme = theme self.iconView.tintColor = theme.foreground + self.collapseButtonIconView.tintColor = theme.foreground } self.iconView.frame = CGRect(origin: CGPoint(x: size.width - 4.0 - quoteIcon.size.width, y: 4.0), size: quoteIcon.size) + let collapseButtonSize = CGSize(width: 18.0, height: 18.0) + + if isCollapsed { + self.collapseButtonIconView.image = quoteExpandImage + self.collapseButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size) + } else { + self.collapseButtonIconView.image = quoteCollapseImage + self.collapseButton.frame = CGRect(origin: CGPoint(x: size.width - 2.0 - collapseButtonSize.width, y: 2.0), size: collapseButtonSize) + } + if let image = self.collapseButtonIconView.image { + let iconSize = image.size.aspectFitted(collapseButtonSize) + self.collapseButtonIconView.frame = CGRect(origin: CGPoint(x: self.collapseButton.bounds.width - 4.0 - collapseButtonSize.width + floorToScreenPixels((collapseButtonSize.width - iconSize.width) * 0.5), y: 4.0 + floorToScreenPixels((collapseButtonSize.height - iconSize.height) * 0.5)), size: iconSize) + } + var primaryColor: UIColor var secondaryColor: UIColor? var tertiaryColor: UIColor? let backgroundColor: UIColor? - switch value.kind { + switch kind { case .quote: - self.iconView.isHidden = false + if size.height >= 60.0 || isCollapsed { + self.iconView.isHidden = true + self.collapseButton.isHidden = false + } else { + self.iconView.isHidden = false + self.collapseButton.isHidden = true + } switch theme.lineStyle { case let .solid(color): @@ -873,6 +1515,7 @@ private final class QuoteBackgroundView: UIView { backgroundColor = nil case .code: self.iconView.isHidden = true + self.collapseButton.isHidden = true primaryColor = theme.codeForeground backgroundColor = theme.codeBackground diff --git a/submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode/BUILD index ef0bf22fb1b..16c21917649 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatLoadingNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/BUILD index d3a63d832d4..2a02c42c08a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatLoadingNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD index 9c809d26123..bf63cf7ec09 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD index e487f7b72c7..355b58ebf62 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD index 43f3ea0a24b..a4033863612 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index af49b7cd3cd..49cb6a9f55a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -172,6 +172,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { let incoming = message.effectivelyIncoming(context.account.peerId) let graphics = PresentationResourcesChat.additionalGraphics(theme.theme, wallpaper: theme.wallpaper, bubbleCorners: bubbleCorners) + var isStarsPayment = false let iconImage: UIImage? switch button.action { case .text: @@ -191,7 +192,12 @@ 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("⭐️") { + isStarsPayment = true + iconImage = nil + } else { + iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage + } case .openUserProfile: iconImage = incoming ? graphics.chatBubbleActionButtonIncomingProfileIconImage : graphics.chatBubbleActionButtonOutgoingProfileIconImage case .openWebView: @@ -215,7 +221,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 isStarsPayment { + let updatedTitle = title.replacingOccurrences(of: "⭐️", with: " # ") + let buttonAttributedString = NSMutableAttributedString(string: updatedTitle, 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/ChatMessageAnimatedStickerItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD index 4d008815291..0c356e7a956 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index fa3972edfbc..0b3450db149 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -141,6 +141,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var wasPending: Bool = false private var didChangeFromPendingToSent: Bool = false + private var fetchEffectDisposable: Disposable? + required public init(rotated: Bool) { self.contextSourceNode = ContextExtractedContentContainingNode() self.containerNode = ContextControllerSourceNode() @@ -590,10 +592,16 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - let isPlaying = self.visibilityStatus == true && !self.forceStopAnimations + var isPlaying = self.visibilityStatus == true && !self.forceStopAnimations + if !item.controllerInteraction.canReadHistory { + isPlaying = false + } + if !isPlaying { self.removeAdditionalAnimations() + self.removeEffectAnimations() } + if let animationNode = self.animationNode as? AnimatedStickerNode { if self.isPlaying != isPlaying || (isPlaying && !self.didSetUpAnimationNode) { self.isPlaying = isPlaying @@ -623,6 +631,23 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } if isPlaying, let animationNode = self.animationNode as? AnimatedStickerNode { + var effectAlreadySeen = true + if item.message.flags.contains(.Incoming) { + if let unreadRange = item.controllerInteraction.unreadMessageRange[UnreadMessageRangeKey(peerId: item.message.id.peerId, namespace: item.message.id.namespace)] { + if unreadRange.contains(item.message.id.id) { + if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) { + effectAlreadySeen = false + } + } + } + } else { + if self.didChangeFromPendingToSent { + if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) { + effectAlreadySeen = false + } + } + } + var alreadySeen = true if isEmoji && self.emojiString == nil { if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) { @@ -659,6 +684,10 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { animationNode.playOnce() } } + + if !effectAlreadySeen { + self.playMessageEffect(force: false) + } } } @@ -1030,6 +1059,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { isReplyThread = true } + let messageEffect = item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects) + let statusSuggestedWidthAndContinue = makeDateAndStatusLayout(ChatMessageDateAndStatusNode.Arguments( context: item.context, presentationData: item.presentationData, @@ -1045,6 +1076,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: messageEffect, replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, @@ -1128,6 +1160,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { context: item.context, controllerInteraction: item.controllerInteraction, type: .standalone, + peer: nil, threadId: item.message.threadId ?? 1, parentMessage: item.message, constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude), @@ -1756,6 +1789,13 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode) } + } else if messageEffect != nil { + strongSelf.dateAndStatusNode.pressed = { + guard let strongSelf = weakSelf.value, let item = strongSelf.item else { + return + } + item.controllerInteraction.playMessageEffect(item.message) + } } else { strongSelf.dateAndStatusNode.pressed = nil } @@ -2085,7 +2125,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { } else { - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, false, forwardInfoNode, nil) return } } @@ -2093,7 +2133,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else if let peer = forwardInfo.source ?? forwardInfo.author { item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info(nil) : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default) } else if let _ = forwardInfo.authorSignature { - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, false, forwardInfoNode, nil) } } @@ -2988,6 +3028,14 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { return (image, self.imageNode.frame) } + + override public func messageEffectTargetView() -> UIView? { + if let result = self.dateAndStatusNode.messageEffectTargetView() { + return result + } + + return nil + } } public struct AnimatedEmojiSoundsConfiguration { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/BUILD index 9bbaaa0da4e..3fc3a6249f4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD index 0b5beda6f28..ef64ff2d7d9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 58a9ed5d877..a7fff6e9afb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -724,6 +724,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId), + messageEffect: message.messageEffect(availableMessageEffects: associatedData.availableMessageEffects), replyCount: dateReplies, isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: message.isSelfExpiring, @@ -1308,6 +1309,12 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } controllerInteraction.activateMessagePinch(sourceNode) } + contentMedia.playMessageEffect = { [weak controllerInteraction] message in + guard let controllerInteraction else { + return + } + controllerInteraction.playMessageEffect(message) + } contentMedia.activateLocalContent = { [weak self] mode in guard let self else { return @@ -1711,6 +1718,24 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { return nil } + public func messageEffectTargetView() -> UIView? { + if let statusNode = self.statusNode, !statusNode.isHidden { + if let result = statusNode.messageEffectTargetView() { + return result + } + } + if let result = self.contentFile?.dateAndStatusNode.messageEffectTargetView() { + return result + } + if let result = self.contentMedia?.dateAndStatusNode.messageEffectTargetView() { + return result + } + if let result = self.contentInstantVideo?.dateAndStatusNode.messageEffectTargetView() { + return result + } + return nil + } + public func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return self.contentMedia?.playMediaWithSound() } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/BUILD index 56a646c36b8..50845bae929 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift index 77bf4fe086f..950f58bc9b1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift @@ -7,7 +7,6 @@ import ChatMessageItemCommon public func chatMessageBubbleImageContentCorners(relativeContentPosition position: ChatMessageBubbleContentPosition, normalRadius: CGFloat, mergedRadius: CGFloat, mergedWithAnotherContentRadius: CGFloat, layoutConstants: ChatMessageItemLayoutConstants, chatPresentationData: ChatPresentationData) -> ImageCorners { let topLeftCorner: ImageCorner let topRightCorner: ImageCorner - switch position { case let .linear(top, _): switch top { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift index 59a6d7a543b..68b9fecff98 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift @@ -141,6 +141,7 @@ public struct ChatMessageBubbleContentTapAction { public enum Content { case none case url(Url) + case phone(String) case textMention(String) case peerMention(peerId: PeerId, mention: String, openProfile: Bool) case botCommand(String) @@ -158,6 +159,7 @@ public struct ChatMessageBubbleContentTapAction { case copy(String) case largeEmoji(String, String?, TelegramMediaFile) case customEmoji(TelegramMediaFile) + case custom(() -> Void) } public var content: Content @@ -295,6 +297,10 @@ open class ChatMessageBubbleContentNode: ASDisplayNode { return nil } + open func messageEffectTargetView() -> UIView? { + return nil + } + open func targetForStoryTransition(id: StoryId) -> UIView? { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD index bcba245fc1c..b39d4b165b6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", @@ -80,7 +80,12 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode", "//submodules/UIKitRuntimeUtils", + "//submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/TelegramUI/Components/LottieMetal", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 172361b64d4..d72385755cf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -89,7 +89,12 @@ import ChatMessageWallpaperBubbleContentNode import ChatMessageGiftBubbleContentNode import ChatMessageGiveawayBubbleContentNode import ChatMessageJoinedChannelBubbleContentNode +import ChatMessageFactCheckBubbleContentNode import UIKitRuntimeUtils +import ChatMessageTransitionNode +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import LottieMetal private struct BubbleItemAttributes { var isAttachment: Bool @@ -112,6 +117,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ var result: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] = [] var skipText = false var messageWithCaptionToAdd: (Message, ChatMessageEntryAttributes)? + var messageWithFactCheckToAdd: (Message, ChatMessageEntryAttributes)? var isUnsupportedMedia = false var isStoryWithText = false var isAction = false @@ -124,7 +130,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ let hideAllAdditionalInfo = item.presentationData.isPreview var hasSeparateCommentsButton = false - + outer: for (message, itemAttributes) in item.content { for attribute in message.attributes { if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil { @@ -267,7 +273,18 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ messageWithCaptionToAdd = (message, itemAttributes) skipText = true } else { - result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default))) + var isMediaInverted = false + if let updatingMedia = itemAttributes.updatingMedia { + isMediaInverted = updatingMedia.invertMediaAttribute != nil + } else if let _ = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) { + isMediaInverted = true + } + + if isMediaInverted { + result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: 0) + } else { + result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default))) + } needReactions = false } } else { @@ -277,6 +294,10 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ } } + if let attribute = message.factCheckAttribute, case .Loaded = attribute.content, messageWithFactCheckToAdd == nil { + messageWithFactCheckToAdd = (message, itemAttributes) + } + inner: for media in message.media { if let webpage = media as? TelegramMediaWebpage { if case let .Loaded(content) = webpage.content { @@ -297,7 +318,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ break inner } } - + if message.adAttribute != nil { result.removeAll() @@ -312,7 +333,26 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ } if let (messageWithCaptionToAdd, itemAttributes) = messageWithCaptionToAdd { - result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) + var isMediaInverted = false + if let updatingMedia = itemAttributes.updatingMedia { + isMediaInverted = updatingMedia.invertMediaAttribute != nil + } else if let _ = messageWithCaptionToAdd.attributes.first(where: { $0 is InvertMediaMessageAttribute }) { + isMediaInverted = true + } + + if isMediaInverted { + if result.isEmpty { + needReactions = false + } + result.insert((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: 0) + } else { + result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) + needReactions = false + } + } + + if let (messageWithFactCheckToAdd, itemAttributes) = messageWithFactCheckToAdd, !hasSeparateCommentsButton { + result.append((messageWithFactCheckToAdd, ChatMessageFactCheckBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) needReactions = false } @@ -625,6 +665,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var appliedForwardInfo: (Peer?, String?)? private var disablesComments = true + private var wasPending: Bool = false + private var didChangeFromPendingToSent: Bool = false + private var authorNameColor: UIColor? private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? @@ -632,8 +675,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var replyRecognizer: ChatSwipeToReplyRecognizer? private var currentSwipeAction: ChatControllerInteractionSwipeAction? - //private let debugNode: ASDisplayNode - override public var visibility: ListViewItemNodeVisibility { didSet { if self.visibility != oldValue { @@ -650,6 +691,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } self.visibilityStatus = self.visibility != .none + + self.updateVisibility() } } } @@ -785,24 +828,24 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } self.mainContextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtractedToContextPreview, _ in - guard let strongSelf = self, let _ = strongSelf.item else { + guard let self, let _ = self.item else { return } - for contentNode in strongSelf.contentNodes { + for contentNode in self.contentNodes { contentNode.willUpdateIsExtractedToContextPreview(isExtractedToContextPreview) } } self.mainContextSourceNode.isExtractedToContextPreviewUpdated = { [weak self] isExtractedToContextPreview in - guard let strongSelf = self else { + guard let self else { return } - strongSelf.backgroundWallpaperNode.setMaskMode(strongSelf.backgroundMaskMode) - strongSelf.backgroundNode.setMaskMode(strongSelf.backgroundMaskMode) - if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect { - strongSelf.updateAbsoluteRect(rect, within: size) + self.backgroundWallpaperNode.setMaskMode(self.backgroundMaskMode) + self.backgroundNode.setMaskMode(self.backgroundMaskMode) + if !isExtractedToContextPreview, let (rect, size) = self.absoluteRect { + self.updateAbsoluteRect(rect, within: size) } - - for contentNode in strongSelf.contentNodes { + + for contentNode in self.contentNodes { contentNode.updateIsExtractedToContextPreview(isExtractedToContextPreview) } } @@ -826,10 +869,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.applyAbsoluteOffsetSpringInternal(value: value, duration: duration, damping: damping) } } - + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + deinit { + } override public func cancelInsertionAnimations() { self.shadowNode.layer.removeAllAnimations() @@ -1171,20 +1217,25 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let contentNodePoint = strongSelf.view.convert(point, to: contentNode.view) let tapAction = contentNode.tapActionAtPoint(contentNodePoint, gesture: .tap, isEstimating: true) switch tapAction.content { - case .none: - if let _ = strongSelf.item?.controllerInteraction.tapMessage { - return .waitForSingleTap - } - break - case .ignore: - return .fail - case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji: + case .none: + if let _ = strongSelf.item?.controllerInteraction.tapMessage { return .waitForSingleTap + } + break + case .ignore: + return .fail + case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .custom: + return .waitForSingleTap } } + if !strongSelf.backgroundNode.frame.contains(point) { return .waitForDoubleTap } + + if strongSelf.currentMessageEffect() != nil { + return .waitForDoubleTap + } } return .waitForDoubleTap @@ -1358,7 +1409,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } - private static func beginLayout(selfReference: Weak, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool, + private static func beginLayout( + selfReference: Weak, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool, currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))], authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), viaMeasureLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), @@ -1469,6 +1521,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ignoreNameHiding = true } + if let subject = item.associatedData.subject, case let .customChatContents(contents) = subject, case .hashTagSearch = contents.kind { + ignoreNameHiding = true + } + displayAuthorInfo = !mergedTop.merged && allowAuthor && peerId.isGroupOrChannel && effectiveAuthor != nil if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil { displayAuthorInfo = false @@ -1496,6 +1552,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else if incoming { hasAvatar = true } + + if let subject = item.associatedData.subject, case let .customChatContents(contents) = subject, case .hashTagSearch = contents.kind { + hasAvatar = true + } } var isInstantVideo = false @@ -1563,7 +1623,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI // var needsShareButton = false - if case .pinnedMessages = item.associatedData.subject { + if incoming, case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind { + needsShareButton = true + } else if case .pinnedMessages = item.associatedData.subject { needsShareButton = true for media in item.message.media { if let _ = media as? TelegramMediaExpiredContent { @@ -2138,6 +2200,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if !displayHeader, case .peer = item.chatLocation, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil { displayHeader = true } + if case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind, let peer = item.message.peers[item.message.id.peerId] { + if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + + } else { + displayHeader = true + } + } } let firstNodeTopPosition: ChatMessageBubbleRelativePosition @@ -2168,8 +2237,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI maximumNodeWidth = size.width + var hasText = false + for contentItem in contentNodeMessagesAndClasses { + if let _ = contentItem.1 as? ChatMessageTextBubbleContentNode.Type { + hasText = true + } + } + if case .customChatContents = item.associatedData.subject { - } else if mosaicRange.upperBound == contentPropertiesAndLayouts.count || contentPropertiesAndLayouts[contentPropertiesAndLayouts.count - 1].3.isAttachment { + } else if (mosaicRange.upperBound == contentPropertiesAndLayouts.count || contentPropertiesAndLayouts[contentPropertiesAndLayouts.count - 1].3.isAttachment) && !hasText { let message = item.content.firstMessage var edited = false @@ -2237,6 +2313,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: message.isSelfExpiring, @@ -2452,9 +2529,20 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI headerSize.height += 2.0 } } + + var hasThreadInfo = false + if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1 || item.associatedData.isRecentActions), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil { + hasThreadInfo = true + } else if case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { + + } else { + hasThreadInfo = true + } + } var hasReply = replyMessage != nil || replyForward != nil || replyStory != nil - if !isInstantVideo, case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1 || item.associatedData.isRecentActions), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), item.message.associatedThreadInfo != nil { + if !isInstantVideo, hasThreadInfo { if let threadId = item.message.threadId, let replyMessage = replyMessage, Int64(replyMessage.id.id) == threadId { hasReply = false } @@ -2471,6 +2559,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI context: item.context, controllerInteraction: item.controllerInteraction, type: .bubble(incoming: incoming), + peer: item.message.peers[item.message.id.peerId].flatMap(EnginePeer.init), threadId: item.message.threadId ?? 1, parentMessage: item.message, constrainedSize: CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude), @@ -2821,7 +2910,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let contentNodeFrame = framesAndPositions[mosaicIndex].0.offsetBy(dx: 0.0, dy: contentNodesHeight) contentNodeFramesPropertiesAndApply.append((contentNodeFrame, properties, true, apply)) - if mosaicIndex == mosaicRange.upperBound - 1 { + if i == mosaicRange.upperBound - 1 { contentNodesHeight += size.height totalContentNodesHeight += size.height @@ -3076,6 +3165,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return } + if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal || item.message.id.namespace == Namespaces.Message.QuickReplyLocal { + strongSelf.wasPending = true + } + if strongSelf.wasPending && (item.message.id.namespace != Namespaces.Message.Local && item.message.id.namespace != Namespaces.Message.ScheduledLocal && item.message.id.namespace != Namespaces.Message.QuickReplyLocal) { + strongSelf.didChangeFromPendingToSent = true + } + let themeUpdated = strongSelf.appliedItem?.presentationData.theme.theme !== item.presentationData.theme.theme let previousContextFrame = strongSelf.mainContainerNode.frame strongSelf.mainContainerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) @@ -3509,6 +3605,18 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI credibilityIconView?.removeFromSuperview() }) } + if let boostBadgeNode = strongSelf.boostBadgeNode { + strongSelf.boostBadgeNode = nil + boostBadgeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak boostBadgeNode] _ in + boostBadgeNode?.removeFromSupernode() + }) + } + if let boostIconNode = strongSelf.boostIconNode { + strongSelf.boostIconNode = nil + boostIconNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak boostIconNode] _ in + boostIconNode?.removeFromSuperview() + }) + } } else { strongSelf.nameNode?.removeFromSupernode() strongSelf.nameNode = nil @@ -3516,19 +3624,23 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.adminBadgeNode = nil strongSelf.credibilityIconView?.removeFromSuperview() strongSelf.credibilityIconView = nil - strongSelf.nameButtonNode?.removeFromSupernode() - strongSelf.nameButtonNode = nil - strongSelf.nameHighlightNode?.removeFromSupernode() - strongSelf.nameHighlightNode = nil - strongSelf.credibilityButtonNode?.removeFromSupernode() - strongSelf.credibilityButtonNode = nil - strongSelf.credibilityHighlightNode?.removeFromSupernode() - strongSelf.credibilityHighlightNode = nil - strongSelf.boostButtonNode?.removeFromSupernode() - strongSelf.boostButtonNode = nil - strongSelf.boostHighlightNode?.removeFromSupernode() - strongSelf.boostHighlightNode = nil + strongSelf.boostBadgeNode?.removeFromSupernode() + strongSelf.boostBadgeNode = nil + strongSelf.boostIconNode?.removeFromSuperview() + strongSelf.boostIconNode = nil } + strongSelf.nameButtonNode?.removeFromSupernode() + strongSelf.nameButtonNode = nil + strongSelf.nameHighlightNode?.removeFromSupernode() + strongSelf.nameHighlightNode = nil + strongSelf.credibilityButtonNode?.removeFromSupernode() + strongSelf.credibilityButtonNode = nil + strongSelf.credibilityHighlightNode?.removeFromSupernode() + strongSelf.credibilityHighlightNode = nil + strongSelf.boostButtonNode?.removeFromSupernode() + strongSelf.boostButtonNode = nil + strongSelf.boostHighlightNode?.removeFromSupernode() + strongSelf.boostHighlightNode = nil } let timingFunction = kCAMediaTimingFunctionSpring @@ -4020,6 +4132,17 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } let absoluteOrigin = mosaicStatusOrigin.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y) statusNodeAnimation.animator.updateFrame(layer: mosaicStatusNode.layer, frame: CGRect(origin: CGPoint(x: absoluteOrigin.x - layoutConstants.image.statusInsets.right - size.width, y: absoluteOrigin.y - layoutConstants.image.statusInsets.bottom - size.height), size: size), completion: nil) + + if item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects) != nil { + mosaicStatusNode.pressed = { [weak strongSelf] in + guard let strongSelf, let item = strongSelf.item else { + return + } + item.controllerInteraction.playMessageEffect(item.message) + } + } else { + mosaicStatusNode.pressed = nil + } } else if let mosaicStatusNode = strongSelf.mosaicStatusNode { strongSelf.mosaicStatusNode = nil mosaicStatusNode.removeFromSupernode() @@ -4396,6 +4519,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.updateSearchTextHighlightState() + strongSelf.updateVisibility() + if let (_, f) = strongSelf.awaitingAppliedReaction { strongSelf.awaitingAppliedReaction = nil @@ -4654,7 +4779,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { } else if !item.message.id.peerId.isReplies { - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, false, forwardInfoNode, nil) return } } @@ -4666,7 +4791,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let textNode = forwardInfoNode.nameNode { subRect = textNode.frame } - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, subRect) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, false, forwardInfoNode, subRect) } } @@ -4711,6 +4836,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return .action(InternalBubbleTapAction.Action { }) } + case let .custom(action): + return .action(InternalBubbleTapAction.Action({ + action() + }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) case let .url(url): if case .longTap = gesture, !tapAction.hasLongTapAction, let item = self.item { let tapMessage = item.content.firstMessage @@ -4732,6 +4861,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url.url, concealed: url.concealed, message: item.content.firstMessage, allowInlineWebpageResolution: url.allowInlineWebpageResolution, progress: tapAction.activate?())) }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) } + case let .phone(number): + return .action(InternalBubbleTapAction.Action({ [weak self] in + guard let self, let item = self.item, let contentNode = self.contextContentNodeForPhoneNumber(number) else { + return + } + + self.addSubnode(contentNode) + + item.controllerInteraction.openPhoneContextMenu(ChatControllerInteraction.OpenPhone(number: number, message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) + }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) case let .peerMention(peerId, _, openProfile): return .action(InternalBubbleTapAction.Action { [weak self] in if let item = self?.item { @@ -4806,7 +4945,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI case let .tooltip(text, node, rect): if let item = self.item { return .optionalAction({ - let _ = item.controllerInteraction.displayMessageTooltip(item.message.id, text, node, rect) + let _ = item.controllerInteraction.displayMessageTooltip(item.message.id, text, false, node, rect) }) } case let .openPollResults(option): @@ -4835,6 +4974,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } } + if self.currentMessageEffect() != nil { + if self.backgroundNode.frame.contains(location) { + return .action(InternalBubbleTapAction.Action({ [weak self] in + guard let self else { + return + } + self.playMessageEffect(force: true) + }, contextMenuOnLongPress: true)) + } + } return nil case .longTap, .doubleTap, .secondaryTap: if let item = self.item, self.backgroundNode.frame.contains(location) { @@ -4884,6 +5033,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else { disableDefaultPressAnimation = true } + case let .phone(number): + return .action(InternalBubbleTapAction.Action({ [weak self] in + guard let self, let item = self.item, let contentNode = self.contextContentNodeForPhoneNumber(number) else { + return + } + + self.addSubnode(contentNode) + + item.controllerInteraction.openPhoneContextMenu(ChatControllerInteraction.OpenPhone(number: number, message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) + }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) case let .peerMention(peerId, mention, _): return .action(InternalBubbleTapAction.Action { item.controllerInteraction.longTap(.peerMention(peerId, mention), message) @@ -4930,6 +5089,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI break case .customEmoji: break + case .custom: + break } } if let tapMessage = tapMessage { @@ -4951,6 +5112,39 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return nil } + private func contextContentNodeForPhoneNumber(_ number: String) -> ContextExtractedContentContainingNode? { + guard let item = self.item else { + return nil + } + let containingNode = ContextExtractedContentContainingNode() + + let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) + + let textNode = ImmediateTextNode() + textNode.attributedText = NSAttributedString(string: number, font: Font.regular(item.presentationData.fontSize.baseDisplaySize), textColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.linkTextColor : item.presentationData.theme.theme.chat.message.outgoing.linkTextColor) + let textSize = textNode.updateLayout(CGSize(width: 1000.0, height: 100.0)) + + let backgroundNode = ASDisplayNode() + backgroundNode.backgroundColor = (incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill).first ?? .black + backgroundNode.clipsToBounds = true + backgroundNode.cornerRadius = 10.0 + + let insets = UIEdgeInsets(top: 5.0, left: 8.0, bottom: 5.0, right: 8.0) + let backgroundSize = CGSize(width: textSize.width + insets.left + insets.right, height: textSize.height + insets.top + insets.bottom) + backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize) + textNode.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: textSize) + backgroundNode.addSubnode(textNode) + + containingNode.frame = CGRect(origin: CGPoint(x: self.backgroundNode.frame.minX + 3.0, y: 1.0), size: CGSize(width: backgroundSize.width, height: backgroundSize.height + 20.0)) + containingNode.contentNode.frame = CGRect(origin: .zero, size: backgroundSize) + containingNode.contentRect = CGRect(origin: .zero, size: backgroundSize) + containingNode.contentNode.addSubnode(backgroundNode) + + containingNode.contentNode.alpha = 0.0 + + return containingNode + } + private func traceSelectionNodes(parent: ASDisplayNode, point: CGPoint) -> ASDisplayNode? { if let parent = parent as? FileMessageSelectionNode, parent.bounds.contains(point) { return parent @@ -5023,6 +5217,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } + if let mosaicStatusNode = self.mosaicStatusNode { + if let result = mosaicStatusNode.hitTest(self.view.convert(point, to: mosaicStatusNode.view), with: event) { + return result + } + } + for contentNode in self.contentNodes { if let result = contentNode.hitTest(self.view.convert(point, to: contentNode.view), with: event) { return result @@ -5377,6 +5577,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let item = self.item { if item.message.adAttribute != nil { item.controllerInteraction.openNoAdsDemo() + } else if case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind { + item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, item.content.firstMessage.id, NavigateToMessageParams(timestamp: nil, quote: nil, forceNew: true)) } else if case .pinnedMessages = item.associatedData.subject { item.controllerInteraction.navigateToMessageStandalone(item.content.firstMessage.id) } else if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { @@ -5754,6 +5956,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI for contentNode in self.contentNodes { contentNode.unreadMessageRangeUpdated() } + + self.updateVisibility() } public func animateQuizInvalidOptionSelected() { @@ -5939,4 +6143,64 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } return false } + + private func updateVisibility() { + guard let item = self.item else { + return + } + + var isPlaying = true + if case let .visible(_, subRect) = self.visibility { + if subRect.minY > 32.0 { + isPlaying = false + } + } else { + isPlaying = false + } + if !item.controllerInteraction.canReadHistory { + isPlaying = false + } + + if !isPlaying { + self.removeEffectAnimations() + } + + if isPlaying { + var alreadySeen = true + if item.message.flags.contains(.Incoming) { + if let unreadRange = item.controllerInteraction.unreadMessageRange[UnreadMessageRangeKey(peerId: item.message.id.peerId, namespace: item.message.id.namespace)] { + if unreadRange.contains(item.message.id.id) { + if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) { + alreadySeen = false + } + } + } + } else { + if self.didChangeFromPendingToSent { + if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) { + alreadySeen = false + } + } + } + + if !alreadySeen { + item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id) + + self.playMessageEffect(force: false) + } + } + } + + override public func messageEffectTargetView() -> UIView? { + for contentNode in self.contentNodes { + if let result = contentNode.messageEffectTargetView() { + return result + } + } + if let mosaicStatusNode = self.mosaicStatusNode, let result = mosaicStatusNode.messageEffectTargetView() { + return result + } + + return nil + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/BUILD index 17460293d13..0c530d06989 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD index 488db161a5f..024e1739ccb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD index ce75d53b14d..cdf58e5121c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift index f94d9c3786c..bde09fc8786 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift @@ -254,6 +254,8 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { statusType = nil + } else if item.message.timestamp == 0 { + statusType = nil } else { switch position { case .linear(_, .None), .linear(_, .Neighbour(true, _, _)): @@ -274,6 +276,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))? + let messageEffect = item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects) if let statusType = statusType { var isReplyThread = false if case .replyThread = item.chatLocation { @@ -295,6 +298,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: messageEffect, replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, @@ -446,11 +450,18 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) { strongSelf.dateAndStatusNode.pressed = { - guard let strongSelf = self else { + guard let strongSelf = self, let item = strongSelf.item else { return } item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode) } + } else if messageEffect != nil { + strongSelf.dateAndStatusNode.pressed = { + guard let strongSelf = self, let item = strongSelf.item else { + return + } + item.controllerInteraction.playMessageEffect(item.message) + } } else { strongSelf.dateAndStatusNode.pressed = nil } @@ -573,6 +584,13 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { return nil } + override public func messageEffectTargetView() -> UIView? { + if !self.dateAndStatusNode.isHidden { + return self.dateAndStatusNode.messageEffectTargetView() + } + return nil + } + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.dateAndStatusNode.supernode != nil, let result = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: event) { return result diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/BUILD index 87db0a75553..22c0c4a292e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift index 91e50ef1491..439f7b0b531 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift @@ -59,6 +59,8 @@ private final class StatusReactionNode: ASDisplayNode { private var resolvedFile: TelegramMediaFile? private var fileDisposable: Disposable? + private var alternativeTextView: ImmediateTextView? + override init() { self.iconView = ReactionIconView() @@ -72,66 +74,11 @@ private final class StatusReactionNode: ASDisplayNode { self.fileDisposable?.dispose() } - func update(context: AccountContext, type: ChatMessageDateAndStatusType, value: MessageReaction.Reaction, file: TelegramMediaFile?, fileId: Int64?, isSelected: Bool, count: Int, theme: PresentationTheme, wallpaper: TelegramWallpaper, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, animated: Bool) { + func update(context: AccountContext, type: ChatMessageDateAndStatusType, value: MessageReaction.Reaction, file: TelegramMediaFile?, fileId: Int64?, alternativeText: String, isSelected: Bool, count: Int, theme: PresentationTheme, wallpaper: TelegramWallpaper, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, animated: Bool) { if self.value != value { self.value = value - let boundingImageSize = CGSize(width: 14.0, height: 14.0) - /*let defaultImageSize = CGSize(width: boundingImageSize.width + floor(boundingImageSize.width * 0.5 * 2.0), height: boundingImageSize.height + floor(boundingImageSize.height * 0.5 * 2.0)) - let imageSize: CGSize - if let file = file { - self.iconImageDisposable.set((reactionStaticImage(context: context, animation: file, pixelSize: CGSize(width: 72.0, height: 72.0), queue: sharedReactionStaticImage) - |> deliverOnMainQueue).start(next: { [weak self] data in - guard let strongSelf = self else { - return - } - - if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { - if let image = UIImage(data: dataValue) { - strongSelf.iconView.imageView.image = image - } - } - })) - imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize - - self.fileDisposable?.dispose() - self.fileDisposable = nil - } else if let fileId = fileId { - self.fileDisposable?.dispose() - self.fileDisposable = nil - - imageSize = boundingImageSize - - if let resolvedFile = self.resolvedFile, resolvedFile.fileId.id == fileId { - } else { - self.fileDisposable = (context.engine.stickers.resolveInlineStickers(fileIds: [fileId]) - |> deliverOnMainQueue).start(next: { [weak self] result in - guard let strongSelf = self, let file = result[fileId] else { - return - } - - strongSelf.resolvedFile = file - - strongSelf.iconImageDisposable.set((reactionStaticImage(context: context, animation: file, pixelSize: CGSize(width: 72.0, height: 72.0), queue: sharedReactionStaticImage) - |> deliverOnMainQueue).start(next: { data in - guard let strongSelf = self else { - return - } - - if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { - if let image = UIImage(data: dataValue) { - strongSelf.iconView.imageView.image = image - } - } - })) - }) - } - } else { - imageSize = defaultImageSize - - self.fileDisposable?.dispose() - self.fileDisposable = nil - }*/ + let boundingImageSize = CGSize(width: 8.0, height: 8.0) let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingImageSize.width - boundingImageSize.width) / 2.0), y: floorToScreenPixels((boundingImageSize.height - boundingImageSize.height) / 2.0)), size: boundingImageSize) self.iconView.frame = iconFrame @@ -172,6 +119,22 @@ private final class StatusReactionNode: ASDisplayNode { reaction: value, transition: .immediate ) + if let alternativeTextView = self.alternativeTextView { + self.alternativeTextView = nil + alternativeTextView.removeFromSuperview() + } + } else { + let alternativeTextView: ImmediateTextView + if let current = self.alternativeTextView { + alternativeTextView = current + } else { + alternativeTextView = ImmediateTextView() + alternativeTextView.insets = UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0) + self.view.addSubview(alternativeTextView) + } + alternativeTextView.attributedText = NSAttributedString(string: alternativeText, font: Font.regular(10.0), textColor: .black) + let alternativeTextSize = alternativeTextView.updateLayout(CGSize(width: 100.0, height: 100.0)) + alternativeTextView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingImageSize.width - alternativeTextSize.width) / 2.0), y: floorToScreenPixels((boundingImageSize.height - alternativeTextSize.height) / 2.0)), size: alternativeTextSize) } } } @@ -230,6 +193,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { var reactionPeers: [(MessageReaction.Reaction, EnginePeer)] var displayAllReactionPeers: Bool var areReactionsTags: Bool + var messageEffect: AvailableMessageEffects.MessageEffect? var replyCount: Int var isPinned: Bool var hasAutoremove: Bool @@ -252,6 +216,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { reactionPeers: [(MessageReaction.Reaction, EnginePeer)], displayAllReactionPeers: Bool, areReactionsTags: Bool, + messageEffect: AvailableMessageEffects.MessageEffect?, replyCount: Int, isPinned: Bool, hasAutoremove: Bool, @@ -273,6 +238,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { self.reactionPeers = reactionPeers self.displayAllReactionPeers = displayAllReactionPeers self.areReactionsTags = areReactionsTags + self.messageEffect = messageEffect self.replyCount = replyCount self.isPinned = isPinned self.hasAutoremove = hasAutoremove @@ -292,7 +258,6 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { private var impressionIcon: ASImageNode? private var reactionNodes: [MessageReaction.Reaction: StatusReactionNode] = [:] private let reactionButtonsContainer = ReactionButtonsAsyncLayoutContainer() - private var reactionCountNode: TextNode? private var reactionButtonNode: HighlightTrackingButtonNode? private var repliesIcon: ASImageNode? private var selfExpiringIcon: ASImageNode? @@ -356,7 +321,6 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { let currentTheme = self.theme let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode) - let makeReactionCountLayout = TextNode.asyncLayout(self.reactionCountNode) let reactionButtonsContainer = self.reactionButtonsContainer @@ -679,35 +643,11 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? - let reactionSize: CGFloat = 17.0 - var reactionCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? + let reactionSize: CGFloat = 8.0 let reactionSpacing: CGFloat = 2.0 let reactionTrailingSpacing: CGFloat = 6.0 var reactionInset: CGFloat = 0.0 - if arguments.layoutInput.displayInlineReactions, !arguments.reactions.isEmpty { - reactionInset = -1.0 + CGFloat(arguments.reactions.count) * reactionSize + CGFloat(arguments.reactions.count - 1) * reactionSpacing + reactionTrailingSpacing - - var count = 0 - for reaction in arguments.reactions { - count += Int(reaction.count) - } - - let countString: String - if count > 1000000 { - countString = "\(count / 1000000)M" - } else if count > 1000 { - countString = "\(count / 1000)K" - } else { - countString = "\(count)" - } - - if count > arguments.reactions.count { - let layoutAndApply = makeReactionCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0))) - reactionInset += layoutAndApply.0.size.width + 4.0 - reactionCountLayoutAndApply = layoutAndApply - } - } if arguments.replyCount > 0 { let countString: String @@ -726,6 +666,10 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { reactionInset += 12.0 } + if arguments.messageEffect != nil { + reactionInset += 13.0 + } + leftInset += reactionInset let layoutSize = CGSize(width: leftInset + impressionWidth + date.size.width + statusWidth + backgroundInsets.left + backgroundInsets.right, height: date.size.height + backgroundInsets.top + backgroundInsets.bottom) @@ -1145,59 +1089,34 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } var reactionOffset: CGFloat = leftOffset + leftInset - reactionInset + backgroundInsets.left - if arguments.layoutInput.displayInlineReactions { + + if let messageEffect = arguments.messageEffect { var validReactions = Set() - for reaction in arguments.reactions.sorted(by: { lhs, rhs in - if lhs.isSelected != rhs.isSelected { - if lhs.isSelected { - return true - } else { - return false - } - } else { - if let lhsIndex = lhs.chosenOrder, let rhsIndex = rhs.chosenOrder { - return lhsIndex < rhsIndex - } else { - return lhs.count > rhs.count - } - } - }) { + do { let node: StatusReactionNode var animateNode = true - if let current = strongSelf.reactionNodes[reaction.value] { + if let current = strongSelf.reactionNodes[.custom(messageEffect.id)] { node = current } else { animateNode = false node = StatusReactionNode() - strongSelf.reactionNodes[reaction.value] = node + strongSelf.reactionNodes[.custom(messageEffect.id)] = node } - validReactions.insert(reaction.value) + validReactions.insert(.custom(messageEffect.id)) var centerAnimation: TelegramMediaFile? - var reactionFileId: Int64? - switch reaction.value { - case .builtin: - if let availableReactions = arguments.availableReactions { - for availableReaction in availableReactions.reactions { - if availableReaction.value == reaction.value { - centerAnimation = availableReaction.centerAnimation - break - } - } - } - case let .custom(fileId): - reactionFileId = fileId - } + centerAnimation = messageEffect.staticIcon node.update( context: arguments.context, type: arguments.type, - value: reaction.value, + value: .custom(messageEffect.id), file: centerAnimation, - fileId: reactionFileId, - isSelected: reaction.isSelected, - count: Int(reaction.count), + fileId: centerAnimation?.fileId.id, + alternativeText: messageEffect.emoticon, + isSelected: false, + count: 0, theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper, animationCache: arguments.animationCache, @@ -1210,7 +1129,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } - let nodeFrame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + offset + verticalInset), size: CGSize(width: reactionSize, height: reactionSize)) + let nodeFrame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + offset + 3.0 + UIScreenPixel + verticalInset), size: CGSize(width: reactionSize, height: reactionSize)) if animateNode { animation.animator.updateFrame(layer: node.layer, frame: nodeFrame, completion: nil) } else { @@ -1259,30 +1178,6 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } } - if let (layout, apply) = reactionCountLayoutAndApply { - let node = apply() - if strongSelf.reactionCountNode !== node { - strongSelf.reactionCountNode?.removeFromSupernode() - strongSelf.addSubnode(node) - strongSelf.reactionCountNode = node - if animation.isAnimated { - node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - } - } - let nodeFrame = CGRect(origin: CGPoint(x: reactionOffset - 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size) - animation.animator.updateFrame(layer: node.layer, frame: nodeFrame, completion: nil) - reactionOffset += 1.0 + layout.size.width + 4.0 - } else if let reactionCountNode = strongSelf.reactionCountNode { - strongSelf.reactionCountNode = nil - if animation.isAnimated { - reactionCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionCountNode] _ in - reactionCountNode?.removeFromSupernode() - }) - } else { - reactionCountNode.removeFromSupernode() - } - } - if let currentRepliesIcon = currentRepliesIcon { currentRepliesIcon.displaysAsynchronously = false if currentRepliesIcon.image !== repliesImage { @@ -1363,11 +1258,6 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } public func reactionView(value: MessageReaction.Reaction) -> UIView? { - for (id, node) in self.reactionNodes { - if id == value { - return node.iconView - } - } for (key, button) in self.reactionButtonsContainer.buttons { if key == value { return button.view.iconView @@ -1376,6 +1266,13 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { return nil } + public func messageEffectTargetView() -> UIView? { + for (_, node) in self.reactionNodes { + return node.iconView + } + return nil + } + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for (_, button) in self.reactionButtonsContainer.buttons { if button.view.frame.contains(point) { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/BUILD index 526ff0cdbcd..5442bfa36a5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD index 8ac301a11ed..335ef4a1b22 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD index db002d4d41c..63be753e6d7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD index 0a07f4940ef..cf6751b3736 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD new file mode 100644 index 00000000000..fc309e1bda9 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD @@ -0,0 +1,33 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageFactCheckBubbleContentNode", + module_name = "ChatMessageFactCheckBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramStringFormatting", + "//submodules/TextFormat", + "//submodules/Geocoding", + "//submodules/UrlEscaping", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView", + "//submodules/TextSelectionNode", + ], + visibility = [ + "//visibility:public", + ], +) + diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift new file mode 100644 index 00000000000..62c4f1e6106 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift @@ -0,0 +1,721 @@ +import Foundation +import UIKit +import Postbox +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import TelegramStringFormatting +import TextFormat +import ChatMessageDateAndStatusNode +import ChatMessageBubbleContentNode +import ChatMessageItemCommon +import MessageInlineBlockBackgroundView +import TextSelectionNode +import Geocoding +import UrlEscaping + +private func generateMaskImage() -> UIImage? { + return generateImage(CGSize(width: 140, height: 30), rotatedContext: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + + context.setFillColor(UIColor.white.cgColor) + context.fill(CGRect(origin: .zero, size: size)) + + var locations: [CGFloat] = [0.0, 0.5, 1.0] + let colors: [CGColor] = [UIColor.white.cgColor, UIColor.white.withAlphaComponent(0.0).cgColor, UIColor.white.withAlphaComponent(0.0).cgColor] + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + + context.setBlendMode(.copy) + context.clip(to: CGRect(origin: CGPoint(x: 10.0, y: 8.0), size: CGSize(width: 130.0, height: 22.0))) + context.drawLinearGradient(gradient, start: CGPoint(x: 10.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions()) + })?.resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 22.0, right: 130.0)) +} + +public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode { + private var backgroundView: MessageInlineBlockBackgroundView? + + private var titleNode: TextNode + private var titleBadgeLabel: TextNode + private var titleBadgeButton: HighlightTrackingButtonNode? + private let textClippingNode: ASDisplayNode + private let textNode: TextNode + private let additionalTextNode: TextNode + private var linkHighlightingNode: LinkHighlightingNode? + private var textSelectionNode: TextSelectionNode? + + private let lineNode: ASDisplayNode + + private var maskView: UIImageView? + private var maskOverlayView: UIView? + + private var expandIcon: ASImageNode + + private let statusNode: ChatMessageDateAndStatusNode + + private var isExpanded: Bool = false + private var appliedIsExpanded: Bool = false + + private var countryName: String? + + required public init() { + self.titleNode = TextNode() + self.titleBadgeLabel = TextNode() + self.textClippingNode = ASDisplayNode() + self.textNode = TextNode() + self.additionalTextNode = TextNode() + self.expandIcon = ASImageNode() + self.statusNode = ChatMessageDateAndStatusNode() + self.lineNode = ASDisplayNode() + + super.init() + + self.textClippingNode.clipsToBounds = true + self.addSubnode(self.textClippingNode) + + self.titleNode.isUserInteractionEnabled = false + self.titleNode.contentMode = .topLeft + self.titleNode.contentsScale = UIScreenScale + self.titleNode.displaysAsynchronously = false + self.addSubnode(self.titleNode) + + self.textNode.isUserInteractionEnabled = false + self.textNode.contentMode = .topLeft + self.textNode.contentsScale = UIScreenScale + self.textNode.displaysAsynchronously = false + self.textClippingNode.addSubnode(self.textNode) + + self.additionalTextNode.isUserInteractionEnabled = false + self.additionalTextNode.contentMode = .topLeft + self.additionalTextNode.contentsScale = UIScreenScale + self.additionalTextNode.displaysAsynchronously = false + self.textClippingNode.addSubnode(self.additionalTextNode) + + self.textClippingNode.addSubnode(self.lineNode) + + self.titleBadgeLabel.isUserInteractionEnabled = false + self.titleBadgeLabel.contentMode = .topLeft + self.titleBadgeLabel.contentsScale = UIScreenScale + self.titleBadgeLabel.displaysAsynchronously = false + self.addSubnode(self.titleBadgeLabel) + + self.expandIcon.displaysAsynchronously = false + self.addSubnode(self.expandIcon) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func didLoad() { + self.maskView = UIImageView() + + let maskOverlayView = UIView() + maskOverlayView.alpha = 0.0 + maskOverlayView.backgroundColor = .white + self.maskOverlayView = maskOverlayView + + self.maskView?.addSubview(maskOverlayView) + } + + @objc private func badgePressed() { + guard let item = self.item, let countryName = self.countryName else { + return + } + + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_FactCheck_Description(countryName).string, true, self.titleBadgeButton, nil) + } + + @objc private func expandPressed() { + self.isExpanded = !self.isExpanded + guard let item = self.item else{ + return + } + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + } + + public override func willUpdateIsExtractedToContextPreview(_ value: Bool) { + if !value { + if let textSelectionNode = self.textSelectionNode { + self.textSelectionNode = nil + textSelectionNode.highlightAreaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in + textSelectionNode?.highlightAreaNode.removeFromSupernode() + textSelectionNode?.removeFromSupernode() + }) + } + } + } + + public override func updateIsExtractedToContextPreview(_ value: Bool) { + if value { + if self.textSelectionNode == nil, let item = self.item, let rootNode = item.controllerInteraction.chatControllerNode() { + let selectionColor: UIColor = item.presentationData.theme.theme.chat.message.incoming.textSelectionColor + let knobColor: UIColor = item.presentationData.theme.theme.chat.message.incoming.textSelectionKnobColor + + let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: knobColor, isDark: item.presentationData.theme.theme.overallDarkAppearance), strings: item.presentationData.strings, textNode: self.textNode, updateIsActive: { [weak self] value in + self?.updateIsTextSelectionActive?(value) + }, present: { [weak self] c, a in + self?.item?.controllerInteraction.presentGlobalOverlayController(c, a) + }, rootNode: { [weak rootNode] in + return rootNode + }, performAction: { [weak self] text, action in + guard let strongSelf = self, let item = strongSelf.item else { + return + } + item.controllerInteraction.performTextSelectionAction(item.message, true, text, action) + }) + textSelectionNode.enableQuote = false + self.textSelectionNode = textSelectionNode + self.addSubnode(textSelectionNode) + self.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textClippingNode) + textSelectionNode.frame = self.textClippingNode.view.convert(self.textNode.frame, to: self.view) + textSelectionNode.highlightAreaNode.frame = textSelectionNode.frame + } + } else { + if let textSelectionNode = self.textSelectionNode { + self.textSelectionNode = nil + self.updateIsTextSelectionActive?(false) + textSelectionNode.highlightAreaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in + textSelectionNode?.highlightAreaNode.removeFromSupernode() + textSelectionNode?.removeFromSupernode() + }) + } + } + } + + public override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + if let titleBadgeButton = self.titleBadgeButton, titleBadgeButton.frame.contains(point) { + return ChatMessageBubbleContentTapAction(content: .ignore) + } + + if self.statusNode.supernode != nil, let _ = self.statusNode.hitTest(self.view.convert(point, to: self.statusNode.view), with: nil) { + return ChatMessageBubbleContentTapAction(content: .ignore) + } + + let textNodeFrame = self.textClippingNode.frame + if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: false))) + } else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { + return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)) + } else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { + return ChatMessageBubbleContentTapAction(content: .textMention(peerName)) + } else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String { + return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand)) + } else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { + return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag)) + } + } + if let backgroundView = self.backgroundView, backgroundView.frame.contains(point), case .tap = gesture { + return ChatMessageBubbleContentTapAction(content: .custom({ [weak self] in + self?.expandPressed() + }), hasLongTapAction: false) + } + return ChatMessageBubbleContentTapAction(content: .none) + } + + public override func updateTouchesAtPoint(_ point: CGPoint?) { + guard let item = self.item else { + return + } + var rects: [CGRect]? + if let point = point { + let textNodeFrame = self.textClippingNode.frame + if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + let possibleNames: [String] = [ + TelegramTextAttributes.URL, + TelegramTextAttributes.PeerMention, + TelegramTextAttributes.PeerTextMention, + TelegramTextAttributes.BotCommand, + TelegramTextAttributes.Hashtag, + TelegramTextAttributes.BankCard + ] + for name in possibleNames { + if let _ = attributes[NSAttributedString.Key(rawValue: name)] { + rects = self.textNode.attributeRects(name: name, at: index) + break + } + } + } + } + + if let rects { + let linkHighlightingNode: LinkHighlightingNode + if let current = self.linkHighlightingNode { + linkHighlightingNode = current + } else { + linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor) + self.linkHighlightingNode = linkHighlightingNode + self.insertSubnode(linkHighlightingNode, belowSubnode: self.textClippingNode) + } + linkHighlightingNode.frame = self.textClippingNode.frame + linkHighlightingNode.updateRects(rects) + } else if let linkHighlightingNode = self.linkHighlightingNode { + self.linkHighlightingNode = nil + linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in + linkHighlightingNode?.removeFromSupernode() + }) + } + } + + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + let titleLayout = TextNode.asyncLayout(self.titleNode) + let titleBadgeLayout = TextNode.asyncLayout(self.titleBadgeLabel) + let textLayout = TextNode.asyncLayout(self.textNode) + let additionalTextLayout = TextNode.asyncLayout(self.additionalTextNode) + let measureTextLayout = TextNode.asyncLayout(nil) + let statusLayout = self.statusNode.asyncLayout() + + let currentIsExpanded = self.isExpanded + let currentCountryName = self.countryName + + return { item, layoutConstants, _, _, _, _ in + let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) + + return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in + let message = item.message + + let incoming = item.message.effectivelyIncoming(item.context.account.peerId) + + let maxTextWidth = CGFloat.greatestFiniteMagnitude + + let horizontalInset = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + let textConstrainedSize = CGSize(width: min(maxTextWidth, constrainedSize.width - (horizontalInset - 2.0) * 2.0), height: constrainedSize.height) + + var edited = false + if item.attributes.updatingMedia != nil { + edited = true + } + var viewCount: Int? + var rawText = "" + var rawEntities: [MessageTextEntity] = [] + var dateReplies = 0 + var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) + if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { + dateReactionsAndPeers = ([], []) + } + for attribute in item.message.attributes { + if let attribute = attribute as? EditedMessageAttribute { + edited = !attribute.isHidden + } else if let attribute = attribute as? ViewCountMessageAttribute { + viewCount = attribute.count + } else if let attribute = attribute as? FactCheckMessageAttribute, case let .Loaded(text, entities, _) = attribute.content { + rawText = text + rawEntities = entities + } else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } + } + } + + let dateFormat: MessageTimestampStatusFormat + if item.presentationData.isPreview { + dateFormat = .full + } else if let subject = item.associatedData.subject, case .messageOptions = subject { + dateFormat = .minimal + } else { + dateFormat = .regular + } + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + + let statusType: ChatMessageDateAndStatusType? + if case .customChatContents = item.associatedData.subject { + statusType = nil + } else { + switch position { + case .linear(_, .None), .linear(_, .Neighbour(true, _, _)): + if incoming { + statusType = .BubbleIncoming + } else { + if message.flags.contains(.Failed) { + statusType = .BubbleOutgoing(.Failed) + } else if message.flags.isSending && !message.isSentOrAcknowledged { + statusType = .BubbleOutgoing(.Sending) + } else { + statusType = .BubbleOutgoing(.Sent(read: item.read)) + } + } + default: + statusType = nil + } + } + + let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing + + let fontSize = floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0) + let textFont = Font.regular(fontSize) + let textBoldFont = Font.semibold(fontSize) + let textItalicFont = Font.italic(fontSize) + let textBoldItalicFont = Font.semiboldItalic(fontSize) + let textFixedFont = Font.regular(fontSize) + let textBlockQuoteFont = Font.regular(fontSize) + let badgeFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 11.0 / 17.0)) + + let attributedText = stringWithAppliedEntities(rawText, entities: rawEntities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textBlockQuoteFont, message: nil) + + let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0) + + var backgroundInsets = UIEdgeInsets() + backgroundInsets.left += layoutConstants.text.bubbleInsets.left + backgroundInsets.right += layoutConstants.text.bubbleInsets.right + + let mainColor = messageTheme.scamColor + + let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Message_FactCheck, font: textBoldFont, textColor: mainColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: mainColor)) + + let titleBadgePadding: CGFloat = 5.0 + let titleBadgeSpacing: CGFloat = 5.0 + let titleBadgeString = NSAttributedString(string: item.presentationData.strings.Message_FactCheck_WhatIsThis, font: badgeFont, textColor: mainColor) + let (titleBadgeLayout, titleBadgeApply) = titleBadgeLayout(TextNodeLayoutArguments(attributedString: titleBadgeString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize)) + + let countryName: String + if let currentCountryName { + countryName = currentCountryName + } else { + if let attribute = item.message.factCheckAttribute, case let .Loaded(_, _, countryIdValue) = attribute.content { + let locale = localeWithStrings(item.presentationData.strings) + countryName = displayCountryName(countryIdValue, locale: locale) + } else { + countryName = "" + } + } + + let finalAttributedText = stringWithAppliedEntities(rawText, entities: rawEntities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textBlockQuoteFont, message: nil) as! NSMutableAttributedString + finalAttributedText.append(NSAttributedString(string: "__", font: textFont, textColor: .clear)) + + let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: finalAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor)) + + let additionalAttributedText = NSMutableAttributedString(string: item.presentationData.strings.Conversation_FactCheck_InnerDescription(countryName).string, font: badgeFont, textColor: mainColor) + additionalAttributedText.append(NSAttributedString(string: "__", font: badgeFont, textColor: .clear)) + + let (additionalTextLayout, additionalTextApply) = additionalTextLayout(TextNodeLayoutArguments(attributedString: additionalAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, lineSpacing: 0.0, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor)) + + var canExpand = false + var clippedTextHeight: CGFloat = textLayout.size.height + if textLayout.numberOfLines > 4 { + let (measuredTextLayout, _) = measureTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 4, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor)) + canExpand = true + + if !currentIsExpanded { + clippedTextHeight = measuredTextLayout.size.height + } + } + + var titleFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: titleLayout.size) + titleFrame = titleFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left * 2.0 - 2.0, dy: layoutConstants.text.bubbleInsets.top - 3.0) + var titleFrameWithoutInsets = CGRect(origin: CGPoint(x: titleFrame.origin.x + textInsets.left, y: titleFrame.origin.y + textInsets.top), size: CGSize(width: titleFrame.width - textInsets.left - textInsets.right, height: titleFrame.height - textInsets.top - textInsets.bottom)) + titleFrameWithoutInsets = titleFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top) + + let topInset: CGFloat = 5.0 + let textSpacing: CGFloat = 3.0 + + let textFrame = CGRect(origin: CGPoint(x: titleFrame.origin.x, y: -textInsets.top + titleFrameWithoutInsets.height + textSpacing), size: textLayout.size) + var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: clippedTextHeight - textInsets.top - textInsets.bottom)) + textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top) + + let additionalTextFrame = CGRect(origin: CGPoint(x: titleFrame.origin.x, y: textFrame.maxY), size: additionalTextLayout.size) + var additionalTextFrameWithoutInsets = CGRect(origin: CGPoint(x: additionalTextFrame.origin.x + textInsets.left, y: additionalTextFrame.origin.y + textInsets.top), size: CGSize(width: additionalTextFrame.width - textInsets.left - textInsets.right, height: additionalTextFrame.height - textInsets.top - textInsets.bottom)) + additionalTextFrameWithoutInsets = additionalTextFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top) + + var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))? + if let statusType = statusType { + var isReplyThread = false + if case .replyThread = item.chatLocation { + isReplyThread = true + } + + statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments( + context: item.context, + presentationData: item.presentationData, + edited: edited && !item.presentationData.isPreview, + impressionCount: !item.presentationData.isPreview ? viewCount : nil, + dateText: dateText, + type: statusType, + layoutInput: .trailingContent(contentWidth: nil, reactionSettings: item.presentationData.isPreview ? nil : ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false)), + constrainedSize: textConstrainedSize, + availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, + reactions: dateReactionsAndPeers.reactions, + reactionPeers: dateReactionsAndPeers.peers, + displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, + areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), + replyCount: dateReplies, + isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, + hasAutoremove: item.message.isSelfExpiring, + canViewReactionList: canViewMessageReactionList(message: item.topMessage, isInline: item.associatedData.isInline), + animationCache: item.controllerInteraction.presentationContext.animationCache, + animationRenderer: item.controllerInteraction.presentationContext.animationRenderer + )) + } + + var suggestedBoundingWidth: CGFloat = max(textFrameWithoutInsets.width, titleFrameWithoutInsets.width + titleBadgeLayout.size.width + titleBadgeSpacing + titleBadgePadding * 2.0) + if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue { + suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0) + } + suggestedBoundingWidth = max(suggestedBoundingWidth, additionalTextFrameWithoutInsets.width) + let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + suggestedBoundingWidth += (sideInsets - 2.0) * 2.0 + + return (suggestedBoundingWidth, { boundingWidth in + var boundingSize: CGSize + + let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right) + + var contentHeight = titleFrameWithoutInsets.height + textSpacing + textFrameWithoutInsets.size.height + if canExpand && !currentIsExpanded { + } else { + contentHeight += textSpacing * 2.0 + 1.0 + additionalTextFrameWithoutInsets.height + } + contentHeight += textSpacing + boundingSize = CGSize(width: boundingWidth, height: topInset + contentHeight - textSpacing) + if let statusSizeAndApply = statusSizeAndApply { + boundingSize.height += statusSizeAndApply.0.height + } + boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom + + return (boundingSize, { [weak self] animation, _, info in + if let strongSelf = self { + info?.setInvertOffsetDirection() + + let isFirstTime = strongSelf.item == nil + let themeUpdated = strongSelf.item?.presentationData.theme.theme !== item.presentationData.theme.theme + + strongSelf.item = item + strongSelf.countryName = countryName + + let backgroundView: MessageInlineBlockBackgroundView + if let current = strongSelf.backgroundView { + backgroundView = current + } else { + backgroundView = MessageInlineBlockBackgroundView() + strongSelf.view.insertSubview(backgroundView, at: 0) + strongSelf.backgroundView = backgroundView + } + + if themeUpdated { + strongSelf.lineNode.backgroundColor = mainColor.withAlphaComponent(0.15) + } + + var isExpandedUpdated = false + if strongSelf.appliedIsExpanded != currentIsExpanded { + strongSelf.appliedIsExpanded = currentIsExpanded + info?.setInvertOffsetDirection() + isExpandedUpdated = true + + animation.transition.updateTransformRotation(node: strongSelf.expandIcon, angle: currentIsExpanded ? .pi : 0.0) + if let maskOverlayView = strongSelf.maskOverlayView { + animation.transition.updateAlpha(layer: maskOverlayView.layer, alpha: currentIsExpanded ? 1.0 : 0.0) + } + } + + let cachedLayout = strongSelf.textNode.cachedLayout + + if case .System = animation, !isExpandedUpdated { + if let cachedLayout = cachedLayout { + if !cachedLayout.areLinesEqual(to: textLayout) { + if let textContents = strongSelf.textNode.contents { + let fadeNode = ASDisplayNode() + fadeNode.displaysAsynchronously = false + fadeNode.contents = textContents + fadeNode.frame = strongSelf.textNode.frame + fadeNode.isLayerBacked = true + strongSelf.textClippingNode.addSubnode(fadeNode) + fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in + fadeNode?.removeFromSupernode() + }) + strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + } + } + + if themeUpdated { + strongSelf.expandIcon.image = generateImage(CGSize(width: 15.0, height: 9.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(mainColor.cgColor) + context.setLineWidth(2.0 - UIScreenPixel) + context.setLineCap(.round) + context.setLineJoin(.round) + context.beginPath() + context.move(to: CGPoint(x: 1.0 + UIScreenPixel, y: 1.0)) + context.addLine(to: CGPoint(x: size.width / 2.0, y: size.height - 2.0)) + context.addLine(to: CGPoint(x: size.width - 1.0 - UIScreenPixel, y: 1.0)) + context.strokePath() + }) + } + + let _ = titleApply() + strongSelf.titleNode.frame = titleFrame.offsetBy(dx: 0.0, dy: topInset) + let _ = titleBadgeApply() + + let _ = textApply() + strongSelf.textNode.frame = CGRect(origin: .zero, size: textFrame.size) + + let _ = additionalTextApply() + strongSelf.additionalTextNode.frame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.height - textInsets.bottom + textSpacing + 1.0), size: additionalTextFrame.size) + + let clippingTextFrame = CGRect(origin: textFrame.origin.offsetBy(dx: 0.0, dy: topInset), size: CGSize(width: boundingWidth, height: contentHeight - titleFrame.height + textSpacing)) + + var titleLineWidth: CGFloat = 0.0 + if let firstLine = titleLayout.linesRects().first { + titleLineWidth = firstLine.width + } else { + titleLineWidth = titleFrame.width + } + + let titleBadgeFrame = CGRect(origin: CGPoint(x: titleFrame.minX + titleLineWidth + titleBadgeSpacing + titleBadgePadding, y: topInset + floorToScreenPixels(titleFrame.midY - titleBadgeLayout.size.height / 2.0) - 1.0), size: titleBadgeLayout.size) + let badgeBackgroundFrame = titleBadgeFrame.insetBy(dx: -titleBadgePadding, dy: -1.0 + UIScreenPixel) + + strongSelf.titleBadgeLabel.frame = titleBadgeFrame + + let titleBadgeButton: HighlightTrackingButtonNode + if let current = strongSelf.titleBadgeButton { + titleBadgeButton = current + titleBadgeButton.bounds = CGRect(origin: .zero, size: badgeBackgroundFrame.size) + animation.animator.updatePosition(layer: titleBadgeButton.layer, position: badgeBackgroundFrame.center, completion: nil) + } else { + titleBadgeButton = HighlightTrackingButtonNode() + titleBadgeButton.addTarget(self, action: #selector(strongSelf.badgePressed), forControlEvents: .touchUpInside) + titleBadgeButton.frame = badgeBackgroundFrame + titleBadgeButton.highligthedChanged = { [weak self, weak titleBadgeButton] highlighted in + if let strongSelf = self, let titleBadgeButton { + if highlighted { + titleBadgeButton.layer.removeAnimation(forKey: "opacity") + titleBadgeButton.alpha = 0.4 + strongSelf.titleBadgeLabel.layer.removeAnimation(forKey: "opacity") + strongSelf.titleBadgeLabel.alpha = 0.4 + } else { + titleBadgeButton.alpha = 1.0 + titleBadgeButton.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.titleBadgeLabel.alpha = 1.0 + strongSelf.titleBadgeLabel.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + strongSelf.titleBadgeButton = titleBadgeButton + strongSelf.addSubnode(titleBadgeButton) + } + + titleBadgeButton.isHidden = item.presentationData.isPreview + strongSelf.titleBadgeLabel.isHidden = item.presentationData.isPreview + + if themeUpdated || titleBadgeButton.backgroundImage(for: .normal) == nil { + titleBadgeButton.setBackgroundImage(generateFilledCircleImage(diameter: badgeBackgroundFrame.height, color: mainColor.withMultipliedAlpha(0.1))?.stretchableImage(withLeftCapWidth: Int(badgeBackgroundFrame.height / 2), topCapHeight: Int(badgeBackgroundFrame.height / 2)), for: .normal) + } + + let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top + topInset), size: CGSize(width: boundingWidth - backgroundInsets.left - backgroundInsets.right, height: contentHeight)) + + if isFirstTime { + strongSelf.textClippingNode.frame = clippingTextFrame + } else { + animation.animator.updateFrame(layer: strongSelf.textClippingNode.layer, frame: clippingTextFrame, completion: nil) + } + if let maskView = strongSelf.maskView, let maskOverlayView = strongSelf.maskOverlayView { + animation.animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: .zero, size: CGSize(width: boundingWidth, height: clippingTextFrame.size.height)), completion: nil) + animation.animator.updateFrame(layer: maskOverlayView.layer, frame: CGRect(origin: .zero, size: CGSize(width: boundingWidth, height: clippingTextFrame.size.height)), completion: nil) + } + + if isFirstTime { + backgroundView.frame = backgroundFrame + } else { + animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil) + } + backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: nil, thirdColor: nil, backgroundColor: nil, pattern: nil, patternTopRightPosition: nil, animation: isFirstTime ? .None : animation) + + animation.animator.updateFrame(layer: strongSelf.lineNode.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: textFrame.height - textSpacing + 1.0), size: CGSize(width: backgroundFrame.width - 9.0 - 6.0, height: 1.0 - UIScreenPixel)), completion: nil) + + if canExpand { + let wasHidden = strongSelf.expandIcon.isHidden + strongSelf.expandIcon.isHidden = false + if strongSelf.maskView?.image == nil { + strongSelf.maskView?.image = generateMaskImage() + } + strongSelf.textClippingNode.view.mask = strongSelf.maskView + + var expandIconFrame: CGRect = .zero + if let icon = strongSelf.expandIcon.image { + expandIconFrame = CGRect(origin: CGPoint(x: boundingWidth - icon.size.width - 19.0, y: backgroundFrame.maxY - icon.size.height - 6.0), size: icon.size) + if wasHidden || isFirstTime { + strongSelf.expandIcon.position = expandIconFrame.center + } else { + animation.animator.updatePosition(layer: strongSelf.expandIcon.layer, position: expandIconFrame.center, completion: nil) + } + strongSelf.expandIcon.bounds = CGRect(origin: .zero, size: expandIconFrame.size) + } + } else { + strongSelf.expandIcon.isHidden = true + strongSelf.textClippingNode.view.mask = nil + } + + if let textSelectionNode = strongSelf.textSelectionNode { + let shouldUpdateLayout = textSelectionNode.frame.size != textFrame.size + textSelectionNode.frame = strongSelf.textClippingNode.view.convert(strongSelf.textNode.frame, to: strongSelf.view) + textSelectionNode.highlightAreaNode.frame = textSelectionNode.frame + + if shouldUpdateLayout { + textSelectionNode.updateLayout() + } + } + + if let statusSizeAndApply = statusSizeAndApply { + strongSelf.statusNode.reactionSelected = { [weak strongSelf] _, value, sourceView in + guard let strongSelf, let item = strongSelf.item else { + return + } + item.controllerInteraction.updateMessageReaction(item.topMessage, .reaction(value), false, sourceView) + } + strongSelf.statusNode.openReactionPreview = { [weak strongSelf] gesture, sourceNode, value in + guard let strongSelf, let item = strongSelf.item else { + gesture?.cancel() + return + } + + item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value) + } + + let statusFrame = CGRect(origin: CGPoint(x: boundingWidth - layoutConstants.text.bubbleInsets.right - statusSizeAndApply.0.width, y: backgroundFrame.maxY + 4.0), size: statusSizeAndApply.0) + if isFirstTime { + strongSelf.statusNode.frame = statusFrame + } else { + animation.animator.updateFrame(layer: strongSelf.statusNode.layer, frame: statusFrame, completion: nil) + } + + if strongSelf.statusNode.supernode == nil { + strongSelf.addSubnode(strongSelf.statusNode) + statusSizeAndApply.1(.None) + } else { + statusSizeAndApply.1(animation) + } + } else if strongSelf.statusNode.supernode != nil { + strongSelf.statusNode.removeFromSupernode() + } + } + }) + }) + }) + } + } + + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { + self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { + self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD index a9382eddeb8..d880b02f81f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift index c9ddf26366a..d52d60c7610 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift @@ -114,6 +114,8 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { statusType = nil + } else if item.message.timestamp == 0 { + statusType = nil } else { switch preparePosition { case .linear(_, .None), .linear(_, .Neighbour(true, _, _)): @@ -249,4 +251,11 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } return nil } + + override public func messageEffectTargetView() -> UIView? { + if !self.interactiveFileNode.dateAndStatusNode.isHidden { + return self.interactiveFileNode.dateAndStatusNode.messageEffectTargetView() + } + return nil + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD index dd8301c5f5a..a0993e63e46 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD index 8f178acb21c..4efc3c8e5ac 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift index b4974c403a4..0410d0d68cf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift @@ -149,4 +149,11 @@ public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNod } return nil } + + override public func messageEffectTargetView() -> UIView? { + if let statusNode = self.contentNode.statusNode, !statusNode.isHidden { + return statusNode.messageEffectTargetView() + } + return nil + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD index ba1ade2779c..ca9c72c12c5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD index e8c483b3f46..ad720ca79ed 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index 2951dc60691..466041cbebc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -528,6 +528,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, @@ -863,6 +864,13 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, return nil } + override public func messageEffectTargetView() -> UIView? { + if !self.dateAndStatusNode.isHidden { + return self.dateAndStatusNode.messageEffectTargetView() + } + return nil + } + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.dateAndStatusNode.supernode != nil, let result = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: event) { return result diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD index becb2bbd065..d277e711b4f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift index a010d655e00..8a1e6894081 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift @@ -458,6 +458,13 @@ public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentN return nil } + override public func messageEffectTargetView() -> UIView? { + if !self.interactiveVideoNode.dateAndStatusNode.isHidden { + return self.interactiveVideoNode.dateAndStatusNode.messageEffectTargetView() + } + return nil + } + override public func targetForStoryTransition(id: StoryId) -> UIView? { return self.interactiveVideoNode.targetForStoryTransition(id: id) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD index 5716eabe195..3e6e0297d01 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift index 2d222805e02..02ee9cf6c89 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift @@ -996,7 +996,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { } else { - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, false, forwardInfoNode, nil) return } } @@ -1004,7 +1004,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco } else if let peer = forwardInfo.source ?? forwardInfo.author { item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info(nil) : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default) } else if let _ = forwardInfo.authorSignature { - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, false, forwardInfoNode, nil) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD index e5ac10d87c6..76e0baff227 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD @@ -15,7 +15,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index d5d6de10ccb..c11fa901224 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -378,7 +378,9 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { guard arguments.associatedData.isPremium || isNicegramPremium else { // MARK: Nicegram Speech2Text, routeToNicegramPremium if isNicegram() { - PremiumUITgHelper.routeToPremium() + PremiumUITgHelper.routeToPremium( + source: .speechToText + ) return } // @@ -463,7 +465,9 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { case .success(let text): message.updateAudioTranscriptionAttribute(text: text, error: nil, context: context) case .needsPremium: - PremiumUITgHelper.routeToPremium() + PremiumUITgHelper.routeToPremium( + source: .speechToText + ) case .error(let error): message.updateAudioTranscriptionAttribute(text: "", error: error, context: context) @@ -576,7 +580,9 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { blurred: true, action: { action in if case .info = action { - PremiumUITgHelper.routeToPremium() + PremiumUITgHelper.routeToPremium( + source: .speechToText + ) } return true @@ -1085,6 +1091,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: arguments.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: arguments.message.areReactionsTags(accountPeerId: arguments.context.account.peerId), + messageEffect: arguments.message.messageEffect(availableMessageEffects: arguments.associatedData.availableMessageEffects), replyCount: dateReplies, isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode, hasAutoremove: arguments.message.isSelfExpiring, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/BUILD index 63595c29a23..f24c0f3e086 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", #"-Xfrontend", "-debug-time-function-bodies" ], deps = [ diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index b6ab4de49f7..6e5ebdf780c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -564,6 +564,8 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { isReplyThread = true } + let messageEffect = item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects) + let statusSuggestedWidthAndContinue = makeDateAndStatusLayout(ChatMessageDateAndStatusNode.Arguments( context: item.context, presentationData: item.presentationData, @@ -579,6 +581,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: messageEffect, replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, @@ -996,6 +999,13 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode) } + } else if messageEffect != nil { + strongSelf.dateAndStatusNode.pressed = { [weak strongSelf] in + guard let strongSelf, let item = strongSelf.item else { + return + } + item.controllerInteraction.playMessageEffect(item.message) + } } else { strongSelf.dateAndStatusNode.pressed = nil } @@ -1546,7 +1556,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { } else { - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, false, forwardInfoNode, nil) return } } @@ -1556,7 +1566,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info(nil) : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default) return } else if let _ = forwardInfo.authorSignature { - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, false, forwardInfoNode, nil) return } } @@ -1629,6 +1639,11 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if let statusNode = self.statusNode, statusNode.supernode != nil, !statusNode.isHidden, statusNode.frame.contains(point) { return self.view } + if self.dateAndStatusNode.supernode != nil, !self.dateAndStatusNode.isHidden { + if let result = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: event) { + return result + } + } if let videoNode = self.videoNode, videoNode.view.frame.contains(point) { return self.view diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD index 1e1cb0ffcd7..9ef1bf2bc35 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 02b52b215fc..b497f7abde4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -200,9 +200,22 @@ extension UIBezierPath { } private class ExtendedMediaOverlayNode: ASDisplayNode { + enum Icon { + case lock + case eye + + var image: UIImage { + switch self { + case .lock: + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: .white)! + case .eye: + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AgeRestricted"), color: .white)! + } + } + } private let blurredImageNode: TransformImageNode private let dustNode: MediaDustNode - private let buttonNode: HighlightTrackingButtonNode + fileprivate let buttonNode: HighlightTrackingButtonNode private let highlightedBackgroundNode: ASDisplayNode private let iconNode: ASImageNode private let textNode: ImmediateTextNode @@ -214,7 +227,7 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { var isRevealed = false var tapped: () -> Void = {} - init(hasImageOverlay: Bool, enableAnimations: Bool) { + init(hasImageOverlay: Bool, icon: Icon, enableAnimations: Bool) { self.blurredImageNode = TransformImageNode() self.blurredImageNode.contentAnimations = [] @@ -231,10 +244,11 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false - self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: .white) + self.iconNode.image = icon.image self.textNode = ImmediateTextNode() - + self.textNode.isUserInteractionEnabled = false + super.init() if hasImageOverlay { @@ -244,8 +258,8 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { self.addSubnode(self.buttonNode) self.buttonNode.addSubnode(self.highlightedBackgroundNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.textNode) + self.buttonNode.addSubnode(self.iconNode) + self.buttonNode.addSubnode(self.textNode) self.buttonNode.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { @@ -263,7 +277,7 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { } @objc private func buttonPressed() { - + self.tapped() } override func didLoad() { @@ -284,10 +298,14 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { self.maskLayer = maskLayer } - func reveal() { + func reveal(animated: Bool = false) { self.isRevealed = true - self.blurredImageNode.removeFromSupernode() - self.dustNode.removeFromSupernode() + if animated { + self.dustNode.tap(at: CGPoint(x: self.dustNode.bounds.width / 2.0, y: self.dustNode.bounds.height / 2.0)) + } else { + self.blurredImageNode.removeFromSupernode() + self.dustNode.removeFromSupernode() + } } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -317,12 +335,20 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { self.isRevealed = self.dustNode.isRevealed self.dustNode.revealed = { [weak self] in - self?.isRevealed = true - self?.blurredImageNode.removeFromSupernode() + guard let self else { + return + } + self.isRevealed = true + self.blurredImageNode.removeFromSupernode() + self.buttonNode.removeFromSupernode() } self.dustNode.tapped = { [weak self] in - self?.isRevealed = true - self?.tapped() + guard let self else { + return + } + if !self.isRevealed { + self.tapped() + } } } else { self.blurredImageNode.isHidden = true @@ -347,8 +373,8 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { self.buttonNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - contentSize.width) / 2.0), y: floorToScreenPixels((size.height - contentSize.height) / 2.0)), size: contentSize) self.highlightedBackgroundNode.frame = CGRect(origin: .zero, size: contentSize) - self.iconNode.frame = CGRect(origin: CGPoint(x: self.buttonNode.frame.minX + padding, y: self.buttonNode.frame.minY + floorToScreenPixels((contentSize.height - iconSize.height) / 2.0) + 1.0 - UIScreenPixel), size: iconSize) - self.textNode.frame = CGRect(origin: CGPoint(x: self.iconNode.frame.maxX + spacing, y: self.buttonNode.frame.minY + floorToScreenPixels((contentSize.height - textSize.height) / 2.0)), size: textSize) + self.iconNode.frame = CGRect(origin: CGPoint(x: padding, y: floorToScreenPixels((contentSize.height - iconSize.height) / 2.0) + 1.0 - UIScreenPixel), size: iconSize) + self.textNode.frame = CGRect(origin: CGPoint(x: self.iconNode.frame.maxX + spacing, y: floorToScreenPixels((contentSize.height - textSize.height) / 2.0)), size: textSize) } } @@ -456,7 +482,9 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr public var activateLocalContent: (InteractiveMediaNodeActivateContent) -> Void = { _ in } public var activatePinch: ((PinchSourceContainerNode) -> Void)? public var updateMessageReaction: ((Message, ChatControllerInteractionReaction, Bool, ContextExtractedContentContainingView?) -> Void)? - + public var playMessageEffect: ((Message) -> Void)? + public var activateAgeRestrictedMedia: (() -> Void)? + override public init() { self.pinchContainerNode = PinchSourceContainerNode() @@ -641,6 +669,11 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } } + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + return result + } + @objc private func imageTap(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { let point = recognizer.location(in: self.imageNode.view) @@ -861,6 +894,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr var statusSize = CGSize() var statusApply: ((ListViewItemUpdateAnimation) -> Void)? + + let messageEffect = message.messageEffect(availableMessageEffects: associatedData.availableMessageEffects) if let dateAndStatus = dateAndStatus { let statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments( @@ -878,6 +913,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr reactionPeers: dateAndStatus.dateReactionPeers, displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId), + messageEffect: messageEffect, replyCount: dateAndStatus.dateReplies, isPinned: dateAndStatus.isPinned, hasAutoremove: message.isSelfExpiring, @@ -1469,8 +1505,20 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr transition.animator.updateFrame(layer: strongSelf.dateAndStatusNode.layer, frame: dateAndStatusFrame, completion: nil) statusApply(transition) } + + if messageEffect != nil { + strongSelf.dateAndStatusNode.pressed = { [weak strongSelf] in + guard let strongSelf, let message = strongSelf.message else { + return + } + strongSelf.playMessageEffect?(message) + } + } else { + strongSelf.dateAndStatusNode.pressed = nil + } } else if strongSelf.dateAndStatusNode.supernode != nil { strongSelf.dateAndStatusNode.removeFromSupernode() + strongSelf.dateAndStatusNode.pressed = nil } if let statusNode = strongSelf.statusNode { @@ -1851,7 +1899,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr backgroundColor = messageTheme.mediaDateAndStatusFillColor } - if let invoice = invoice { + if let invoice = invoice, invoice.currency != "XTR" { if let extendedMedia = invoice.extendedMedia { if case let .preview(_, _, maybeVideoDuration) = extendedMedia, let videoDuration = maybeVideoDuration { badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: stringForDuration(videoDuration, position: nil)), iconName: nil) @@ -2200,6 +2248,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr badgeNode.removeFromSupernode() } + var icon: ExtendedMediaOverlayNode.Icon = .lock var displaySpoiler = false if let invoice = invoice, let extendedMedia = invoice.extendedMedia, case .preview = extendedMedia { displaySpoiler = true @@ -2207,14 +2256,26 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr displaySpoiler = true } else if isSecretMedia { displaySpoiler = true + } else if message.isAgeRestricted() { + displaySpoiler = true + icon = .eye } - + if displaySpoiler { if self.extendedMediaOverlayNode == nil { - let extendedMediaOverlayNode = ExtendedMediaOverlayNode(hasImageOverlay: !isSecretMedia, enableAnimations: (self.context?.sharedContext.energyUsageSettings.fullTranslucency ?? true) && !isPreview) + let enableAnimations = (self.context?.sharedContext.energyUsageSettings.fullTranslucency ?? true) && !isPreview + let extendedMediaOverlayNode = ExtendedMediaOverlayNode(hasImageOverlay: !isSecretMedia, icon: icon, enableAnimations: enableAnimations) extendedMediaOverlayNode.tapped = { [weak self] in - self?.internallyVisible = true - self?.updateVisibility() + guard let self else { + return + } + if message.isAgeRestricted() { + self.activateAgeRestrictedMedia?() + } else { + self.internallyVisible = true + self.extendedMediaOverlayNode?.isRevealed = true + self.updateVisibility() + } } self.extendedMediaOverlayNode = extendedMediaOverlayNode self.pinchContainerNode.contentNode.insertSubnode(extendedMediaOverlayNode, aboveSubnode: self.imageNode) @@ -2233,21 +2294,26 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr self.extendedMediaOverlayNode?.isUserInteractionEnabled = tappable - var paymentText: String = "" - outer: for attribute in message.attributes { - if let attribute = attribute as? ReplyMarkupMessageAttribute { - for row in attribute.rows { - for button in row.buttons { - if case .payment = button.action { - paymentText = button.title - break outer + var viewText: String = "" + if message.isAgeRestricted() { + //TODO: localize + viewText = "18+ Content" + } else { + outer: for attribute in message.attributes { + if let attribute = attribute as? ReplyMarkupMessageAttribute { + for row in attribute.rows { + for button in row.buttons { + if case .payment = button.action { + viewText = button.title + break outer + } } } + break } - break } } - self.extendedMediaOverlayNode?.update(size: self.imageNode.frame.size, text: paymentText, imageSignal: self.currentBlurredImageSignal, imageFrame: self.imageNode.view.convert(self.imageNode.bounds, to: self.extendedMediaOverlayNode?.view), corners: self.currentImageArguments?.corners) + self.extendedMediaOverlayNode?.update(size: self.imageNode.frame.size, text: viewText, imageSignal: self.currentBlurredImageSignal, imageFrame: self.imageNode.view.convert(self.imageNode.bounds, to: self.extendedMediaOverlayNode?.view), corners: self.currentImageArguments?.corners) } else if let extendedMediaOverlayNode = self.extendedMediaOverlayNode { self.extendedMediaOverlayNode = nil extendedMediaOverlayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak extendedMediaOverlayNode] _ in @@ -2270,6 +2336,10 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } } + public func reveal() { + self.extendedMediaOverlayNode?.reveal(animated: true) + } + public static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode))) { let currentAsyncLayout = node?.asyncLayout() @@ -2449,4 +2519,14 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr return nil } } + + public func ignoreTapActionAtPoint(_ point: CGPoint) -> Bool { + if let extendedMediaOverlayNode = self.extendedMediaOverlayNode { + let convertedPoint = self.view.convert(point, to: extendedMediaOverlayNode.view) + if extendedMediaOverlayNode.buttonNode.frame.contains(convertedPoint) { + return true + } + } + return false + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD index 78df0732838..bf4e50ecc95 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift index b46926dc472..02855854c97 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift @@ -62,7 +62,7 @@ public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContent if let image = invoice.photo { automaticDownloadSettings = MediaAutoDownloadSettings.defaultSettings mediaAndFlags = ([image], [.preferMediaBeforeText]) - } else { + } else if invoice.currency != "XTR" { let invoiceLabel = item.presentationData.strings.Message_InvoiceLabel var invoiceText = "\(formatCurrencyAmount(invoice.totalAmount, currency: invoice.currency)) " invoiceText += invoiceLabel @@ -145,4 +145,11 @@ public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContent } return nil } + + override public func messageEffectTargetView() -> UIView? { + if let statusNode = self.contentNode.statusNode, !statusNode.isHidden { + return statusNode.messageEffectTargetView() + } + return nil + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD index 8c820694528..a4f81c514b8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD index 0625afc28a2..49517183339 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD @@ -11,7 +11,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD index e704fe56750..26c721aa8e7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index 297e1a4988c..4ba983ce0b0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -752,10 +752,10 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat @objc private func tapGesture(_ recognizer: ListViewTapGestureRecognizer) { if case .ended = recognizer.state { if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, _, id, _, _, _, _) = self.messageReference?.content { - self.controllerInteraction?.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame) + self.controllerInteraction?.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, false, self, self.avatarNode.frame) } else if let peer = self.peer { if let adMessageId = self.adMessageId { - self.controllerInteraction?.activateAdAction(adMessageId) + self.controllerInteraction?.activateAdAction(adMessageId, nil) } else { if let channel = peer as? TelegramChannel, case .broadcast = channel.info { self.controllerInteraction?.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), self.messageReference, .default) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift index c45fdab34e0..605aab862d9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -75,11 +75,16 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs } } + var sameChat = true + if lhs.id.peerId != rhs.id.peerId { + sameChat = false + } + var sameThread = true if let lhsPeer = lhs.peers[lhs.id.peerId], let rhsPeer = rhs.peers[rhs.id.peerId], arePeersEqual(lhsPeer, rhsPeer), let channel = lhsPeer as? TelegramChannel, channel.flags.contains(.isForum), lhs.threadId != rhs.threadId { sameThread = false } - + var sameAuthor = false if lhsEffectiveAuthor?.id == rhsEffectiveAuthor?.id && lhs.effectivelyIncoming(accountPeerId) == rhs.effectivelyIncoming(accountPeerId) { sameAuthor = true @@ -124,7 +129,7 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs } } - if abs(lhsEffectiveTimestamp - rhsEffectiveTimestamp) < Int32(10 * 60) && sameAuthor && sameThread { + if abs(lhsEffectiveTimestamp - rhsEffectiveTimestamp) < Int32(10 * 60) && sameChat && sameAuthor && sameThread { if let channel = lhs.peers[lhs.id.peerId] as? TelegramChannel, case .group = channel.info, lhsEffectiveAuthor?.id == channel.id, !lhs.effectivelyIncoming(accountPeerId) { return .none } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD index 597406f49af..e553f4c3730 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", @@ -24,6 +24,8 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TextFormat", "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + "//submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode", + "//submodules/TelegramAnimatedStickerNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index fc902d7b49b..d818c687b8d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -14,6 +14,10 @@ import ChatControllerInteraction import ChatMessageItemCommon import TextFormat import ChatMessageItem +import ChatMessageTransitionNode +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import LottieMetal public func chatMessageItemLayoutConstants(_ constants: (ChatMessageItemLayoutConstants, ChatMessageItemLayoutConstants), params: ListViewItemLayoutParams, presentationData: ChatPresentationData) -> ChatMessageItemLayoutConstants { var result: ChatMessageItemLayoutConstants @@ -657,6 +661,11 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { public var wantTrButton: [(Bool, [String])] = [(false, [])] // + private var fetchEffectDisposable: Disposable? + + public var playedEffectAnimation: Bool = false + public var effectAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = [] + public required init(rotated: Bool) { super.init(layerBacked: false, dynamicBounce: true, rotated: rotated) if rotated { @@ -668,6 +677,10 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { fatalError("init(coder:) has not been implemented") } + deinit { + self.fetchEffectDisposable?.dispose() + } + override open func reuse() { super.reuse() @@ -714,6 +727,28 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { } } + public func matchesMessage(id: MessageId) -> Bool { + if let item = self.item { + for (message, _) in item.content { + if message.id == id { + return true + } + } + } + return false + } + + public func messages() -> [Message] { + guard let item = self.item else { + return [] + } + var messages: [Message] = [] + for (message, _) in item.content { + messages.append(message) + } + return messages + } + open func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } @@ -891,4 +926,147 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { open func contentFrame() -> CGRect { return self.bounds } + + private func playEffectAnimation(effect: AvailableMessageEffects.MessageEffect, force: Bool) { + guard let item = self.item else { + return + } + if self.playedEffectAnimation && !force { + return + } + self.playedEffectAnimation = true + + if let effectAnimation = effect.effectAnimation { + self.playEffectAnimation(resource: effectAnimation.resource) + if self.fetchEffectDisposable == nil { + self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectAnimation), resource: effectAnimation.resource).startStrict() + } + } else { + let effectSticker = effect.effectSticker + if let effectFile = effectSticker.videoThumbnails.first { + self.playEffectAnimation(resource: effectFile.resource) + if self.fetchEffectDisposable == nil { + self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectSticker), resource: effectFile.resource).startStrict() + } + } + } + } + + open func messageEffectTargetView() -> UIView? { + return nil + } + + private func playEffectAnimation(resource: MediaResource) { + guard let item = self.item else { + return + } + guard let transitionNode = item.controllerInteraction.getMessageTransitionNode() as? ChatMessageTransitionNode else { + return + } + + let source = AnimatedStickerResourceSource(account: item.context.account, resource: resource, fitzModifier: nil) + + let animationSize = CGSize(width: 380.0, height: 380.0) + let animationNodeFrame: CGRect + + guard let messageEffectView = self.messageEffectTargetView() else { + return + } + + animationNodeFrame = animationSize.centered(around: messageEffectView.convert(messageEffectView.bounds, to: self.view).center) + + if self.effectAnimationNodes.count >= 2 { + return + } + + let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId) + + do { + let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id) + + let additionalAnimationNode: AnimatedStickerNode + var effectiveScale: CGFloat = 1.0 + #if targetEnvironment(simulator) + additionalAnimationNode = DirectAnimatedStickerNode() + effectiveScale = 1.4 + #else + if "".isEmpty { + additionalAnimationNode = DirectAnimatedStickerNode() + effectiveScale = 1.4 + } else { + additionalAnimationNode = LottieMetalAnimatedStickerNode() + } + #endif + additionalAnimationNode.updateLayout(size: animationSize) + additionalAnimationNode.setup(source: source, width: Int(animationSize.width * effectiveScale), height: Int(animationSize.height * effectiveScale), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) + var animationFrame: CGRect + let offsetScale: CGFloat = 0.3 + animationFrame = animationNodeFrame.offsetBy(dx: incomingMessage ? animationNodeFrame.width * offsetScale : -animationNodeFrame.width * offsetScale, dy: -10.0) + + animationFrame = animationFrame.offsetBy(dx: 0.0, dy: self.insets.top) + additionalAnimationNode.frame = animationFrame + if incomingMessage { + additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0) + } + + let decorationNode = transitionNode.add(decorationView: additionalAnimationNode.view, itemNode: self) + additionalAnimationNode.completed = { [weak self, weak decorationNode, weak transitionNode] _ in + guard let decorationNode = decorationNode else { + return + } + self?.effectAnimationNodes.removeAll(where: { $0 === decorationNode }) + transitionNode?.remove(decorationNode: decorationNode) + } + additionalAnimationNode.isPlayingChanged = { [weak self, weak decorationNode, weak transitionNode] isPlaying in + if !isPlaying { + guard let decorationNode = decorationNode else { + return + } + self?.effectAnimationNodes.removeAll(where: { $0 === decorationNode }) + transitionNode?.remove(decorationNode: decorationNode) + } + } + + self.effectAnimationNodes.append(decorationNode) + + additionalAnimationNode.visibility = true + } + } + + public func removeEffectAnimations() { + for decorationNode in self.effectAnimationNodes { + if let additionalAnimationNode = decorationNode.contentView.asyncdisplaykit_node as? AnimatedStickerNode { + additionalAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak additionalAnimationNode] _ in + additionalAnimationNode?.visibility = false + }) + } + } + } + + public func currentMessageEffect() -> AvailableMessageEffects.MessageEffect? { + guard let item = self.item else { + return nil + } + var messageEffect: AvailableMessageEffects.MessageEffect? + for attribute in item.message.attributes { + if let attribute = attribute as? EffectMessageAttribute { + if let availableMessageEffects = item.associatedData.availableMessageEffects { + for effect in availableMessageEffects.messageEffects { + if effect.id == attribute.id { + messageEffect = effect + break + } + } + } + break + } + } + return messageEffect + } + + public func playMessageEffect(force: Bool) { + if let messageEffect = self.currentMessageEffect() { + self.playEffectAnimation(effect: messageEffect, force: force) + } + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD index 8f73fb8736c..23dccb35536 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD index 319e0fe1896..46bf137d9f8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift index b5a1394c367..918afe822c7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift @@ -282,6 +282,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, @@ -540,4 +541,11 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } return nil } + + override public func messageEffectTargetView() -> UIView? { + if !self.dateAndStatusNode.isHidden { + return self.dateAndStatusNode.messageEffectTargetView() + } + return nil + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD index 5de32fcb35a..9361bb92768 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift index 58d86e83baa..bedbbf5c46c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift @@ -42,20 +42,28 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.interactiveImageNode) self.interactiveImageNode.activateLocalContent = { [weak self] mode in - if let strongSelf = self { - if let item = strongSelf.item { - let openChatMessageMode: ChatControllerInteractionOpenMessageMode - switch mode { - case .default: - openChatMessageMode = .default - case .stream: - openChatMessageMode = .stream - case .automaticPlayback: - openChatMessageMode = .automaticPlayback - } - let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode)) - } + guard let self, let item = self.item else { + return + } + let openChatMessageMode: ChatControllerInteractionOpenMessageMode + switch mode { + case .default: + openChatMessageMode = .default + case .stream: + openChatMessageMode = .stream + case .automaticPlayback: + openChatMessageMode = .automaticPlayback + } + let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode)) + } + + self.interactiveImageNode.activateAgeRestrictedMedia = { [weak self] in + guard let self, let item = self.item else { + return } + let _ = item.controllerInteraction.openAgeRestrictedMessageMedia(item.message, { [weak self] in + self?.interactiveImageNode.reveal() + }) } self.interactiveImageNode.updateMessageReaction = { [weak self] message, value, force, sourceView in @@ -71,6 +79,12 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } strongSelf.item?.controllerInteraction.activateMessagePinch(sourceNode) } + self.interactiveImageNode.playMessageEffect = { [weak self] message in + guard let strongSelf = self, let _ = strongSelf.item else { + return + } + strongSelf.item?.controllerInteraction.playMessageEffect(message) + } } required public init?(coder aDecoder: NSCoder) { @@ -286,6 +300,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { statusType = nil + } else if item.message.timestamp == 0 { + statusType = nil } else { switch preparePosition { case .linear(_, .None), .linear(_, .Neighbour(true, _, _)): @@ -403,8 +419,6 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } item.controllerInteraction.displayImportedMessageTooltip(strongSelf.interactiveImageNode.dateAndStatusNode) } - } else { - strongSelf.interactiveImageNode.dateAndStatusNode.pressed = nil } } }) @@ -466,6 +480,9 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + if self.interactiveImageNode.ignoreTapActionAtPoint(point) { + return ChatMessageBubbleContentTapAction(content: .ignore) + } return ChatMessageBubbleContentTapAction(content: .none) } @@ -510,4 +527,11 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } return nil } + + override public func messageEffectTargetView() -> UIView? { + if !self.interactiveImageNode.dateAndStatusNode.isHidden { + return self.interactiveImageNode.dateAndStatusNode.messageEffectTargetView() + } + return nil + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD index 0b956ee90fe..d3a71aff93c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD index 7a038fc1013..e4cb8c3a622 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", @@ -27,6 +27,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/PollBubbleTimerNode", "//submodules/TelegramUI/Components/Chat/MergedAvatarsNode", "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TelegramUI/Components/Chat/ShimmeringLinkNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift index d6bfff73f43..4d49dca6b98 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift @@ -17,6 +17,7 @@ import ChatMessageItemCommon import PollBubbleTimerNode import MergedAvatarsNode import TextNodeWithEntities +import ShimmeringLinkNode private final class ChatMessagePollOptionRadioNodeParameters: NSObject { let timestamp: Double @@ -387,7 +388,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { private(set) var radioNode: ChatMessagePollOptionRadioNode? private let percentageNode: ASDisplayNode private var percentageImage: UIImage? - private var titleNode: TextNodeWithEntities? + fileprivate var titleNode: TextNodeWithEntities? private let buttonNode: HighlightTrackingButtonNode let separatorNode: ASDisplayNode private let resultBarNode: ASImageNode @@ -491,22 +492,29 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { } } - static func asyncLayout(_ maybeNode: ChatMessagePollOptionNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode))) { + static func asyncLayout(_ maybeNode: ChatMessagePollOptionNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ translation: TranslationMessageAttribute.Additional?, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode))) { let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode) let currentResult = maybeNode?.currentResult let currentSelection = maybeNode?.currentSelection let currentTheme = maybeNode?.theme - return { context, presentationData, message, poll, option, optionResult, constrainedWidth in + return { context, presentationData, message, poll, option, translation, optionResult, constrainedWidth in let leftInset: CGFloat = 50.0 let rightInset: CGFloat = 12.0 let incoming = message.effectivelyIncoming(context.account.peerId) + var optionText = option.text + var optionEntities = option.entities + if let translation { + optionText = translation.text + optionEntities = translation.entities + } + let optionTextColor: UIColor = incoming ? presentationData.theme.theme.chat.message.incoming.primaryTextColor : presentationData.theme.theme.chat.message.outgoing.primaryTextColor let optionAttributedText = stringWithAppliedEntities( - option.text, - entities: option.entities, + optionText, + entities: optionEntities, baseColor: optionTextColor, linkColor: optionTextColor, baseFont: presentationData.messageFont, @@ -627,6 +635,25 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { node.buttonNode.accessibilityLabel = option.text + if animated { + if let titleNode = node.titleNode, let cachedLayout = titleNode.textNode.cachedLayout { + if !cachedLayout.areLinesEqual(to: titleLayout) { + if let textContents = titleNode.textNode.contents { + let fadeNode = ASDisplayNode() + fadeNode.displaysAsynchronously = false + fadeNode.contents = textContents + fadeNode.frame = titleNode.textNode.frame + fadeNode.isLayerBacked = true + node.addSubnode(fadeNode) + fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in + fadeNode?.removeFromSupernode() + }) + titleNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + } + } + let titleNode = titleApply(TextNodeWithEntities.Arguments( context: context, cache: context.animationCache, @@ -828,6 +855,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { private let buttonNode: HighlightableButtonNode private let statusNode: ChatMessageDateAndStatusNode private var optionNodes: [ChatMessagePollOptionNode] = [] + private var shimmeringNodes: [ShimmeringLinkNode] = [] private var poll: TelegramMediaPoll? @@ -996,7 +1024,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } - var previousOptionNodeLayouts: [Data: (_ contet: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode)))] = [:] + var previousOptionNodeLayouts: [Data: (_ contet: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ translation: TranslationMessageAttribute.Additional?, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode)))] = [:] for optionNode in self.optionNodes { if let option = optionNode.option { previousOptionNodeLayouts[option.opaqueIdentifier] = ChatMessagePollOptionNode.asyncLayout(optionNode) @@ -1095,6 +1123,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, @@ -1114,26 +1143,40 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing - let attributedText: NSAttributedString - if let poll { - attributedText = stringWithAppliedEntities( - poll.text, - entities: poll.textEntities, - baseColor: messageTheme.primaryTextColor, - linkColor: messageTheme.linkTextColor, - baseFont: item.presentationData.messageBoldFont, - linkFont: item.presentationData.messageBoldFont, - boldFont: item.presentationData.messageBoldFont, - italicFont: item.presentationData.messageBoldFont, - boldItalicFont: item.presentationData.messageBoldFont, - fixedFont: item.presentationData.messageBoldFont, - blockQuoteFont: item.presentationData.messageBoldFont, - message: message - ) - } else { - attributedText = NSAttributedString(string: "", font: item.presentationData.messageBoldFont, textColor: messageTheme.primaryTextColor) + + var pollTitleText = poll?.text ?? "" + var pollTitleEntities = poll?.textEntities ?? [] + var pollOptions: [TranslationMessageAttribute.Additional] = [] + + var isTranslating = false + if let poll, let translateToLanguage = item.associatedData.translateToLanguage, !poll.text.isEmpty && incoming { + isTranslating = true + for attribute in item.message.attributes { + if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { + pollTitleText = attribute.text + pollTitleEntities = attribute.entities + pollOptions = attribute.additional + isTranslating = false + break + } + } } + let attributedText = stringWithAppliedEntities( + pollTitleText, + entities: pollTitleEntities, + baseColor: messageTheme.primaryTextColor, + linkColor: messageTheme.linkTextColor, + baseFont: item.presentationData.messageBoldFont, + linkFont: item.presentationData.messageBoldFont, + boldFont: item.presentationData.messageBoldFont, + italicFont: item.presentationData.messageBoldFont, + boldItalicFont: item.presentationData.messageBoldFont, + fixedFont: item.presentationData.messageBoldFont, + blockQuoteFont: item.presentationData.messageBoldFont, + message: message + ) + let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) @@ -1269,7 +1312,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { for i in 0 ..< poll.options.count { let option = poll.options[i] - let makeLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode))) + let makeLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ translation: TranslationMessageAttribute.Additional?, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode))) if let previous = previousOptionNodeLayouts[option.opaqueIdentifier] { makeLayout = previous } else { @@ -1285,7 +1328,13 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } else if isClosed { optionResult = ChatMessagePollOptionResult(normalized: 0, percent: 0, count: 0) } - let result = makeLayout(item.context, item.presentationData, item.message, poll, option, optionResult, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0) + + var translation: TranslationMessageAttribute.Additional? + if !pollOptions.isEmpty && i < pollOptions.count { + translation = pollOptions[i] + } + + let result = makeLayout(item.context, item.presentationData, item.message, poll, option, translation, optionResult, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0) boundingSize.width = max(boundingSize.width, result.minimumWidth + layoutConstants.bubble.borderInset * 2.0) pollOptionsFinalizeLayouts.append(result.1) } @@ -1351,6 +1400,26 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.item = item strongSelf.poll = poll + let cachedLayout = strongSelf.textNode.textNode.cachedLayout + if case .System = animation { + if let cachedLayout = cachedLayout { + if !cachedLayout.areLinesEqual(to: textLayout) { + if let textContents = strongSelf.textNode.textNode.contents { + let fadeNode = ASDisplayNode() + fadeNode.displaysAsynchronously = false + fadeNode.contents = textContents + fadeNode.frame = strongSelf.textNode.textNode.frame + fadeNode.isLayerBacked = true + strongSelf.addSubnode(fadeNode) + fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in + fadeNode?.removeFromSupernode() + }) + strongSelf.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + } + } + let _ = textApply(TextNodeWithEntities.Arguments( context: item.context, cache: item.context.animationCache, @@ -1371,6 +1440,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } let optionNode = apply(animation.isAnimated, isRequesting, synchronousLoad) + let optionNodeFrame = CGRect(origin: CGPoint(x: layoutConstants.bubble.borderInset, y: verticalOffset), size: size) if optionNode.supernode !== strongSelf { strongSelf.addSubnode(optionNode) let option = optionNode.option @@ -1387,8 +1457,11 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } strongSelf.updateSelection() } + optionNode.frame = optionNodeFrame + } else { + animation.animator.updateFrame(layer: optionNode.layer, frame: optionNodeFrame, completion: nil) } - optionNode.frame = CGRect(origin: CGPoint(x: layoutConstants.bubble.borderInset, y: verticalOffset), size: size) + verticalOffset += size.height updatedOptionNodes.append(optionNode) optionNode.isUserInteractionEnabled = canVote && item.controllerInteraction.pollActionState.pollMessageIdsInProgress[item.message.id] == nil @@ -1410,7 +1483,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.textNode.textNode.frame = textFrame } let typeFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: textFrame.maxY + titleTypeSpacing), size: typeLayout.size) - strongSelf.typeNode.frame = typeFrame + animation.animator.updateFrame(layer: strongSelf.typeNode.layer, frame: typeFrame, completion: nil) let deadlineTimeout = poll?.deadlineTimeout var displayDeadline = true @@ -1536,7 +1609,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { let _ = votersApply() let votersFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - votersLayout.size.width) / 2.0), y: verticalOffset + optionsVotersSpacing), size: votersLayout.size) - strongSelf.votersNode.frame = votersFrame + animation.animator.updateFrame(layer: strongSelf.votersNode.layer, frame: votersFrame, completion: nil) + if animation.isAnimated, let previousPoll = previousPoll, let poll = poll { if previousPoll.results.totalVoters == nil && poll.results.totalVoters != nil { strongSelf.votersNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) @@ -1571,6 +1645,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.updateSelection() strongSelf.updatePollTooltipMessageState(animated: false) + + strongSelf.updateIsTranslating(isTranslating) } }) }) @@ -1578,6 +1654,46 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } + private func updateIsTranslating(_ isTranslating: Bool) { + guard let item = self.item else { + return + } + var rects: [[CGRect]] = [] + let titleRects = (self.textNode.textNode.rangeRects(in: NSRange(location: 0, length: self.textNode.textNode.cachedLayout?.attributedString?.length ?? 0))?.rects ?? []).map { self.textNode.textNode.view.convert($0, to: self.view) } + rects.append(titleRects) + + for optionNode in self.optionNodes { + if let titleNode = optionNode.titleNode { + let optionRects = (titleNode.textNode.rangeRects(in: NSRange(location: 0, length: titleNode.textNode.cachedLayout?.attributedString?.length ?? 0))?.rects ?? []).map { titleNode.textNode.view.convert($0, to: self.view) } + rects.append(optionRects) + } + } + + if isTranslating, !rects.isEmpty { + if self.shimmeringNodes.isEmpty { + for rects in rects { + let shimmeringNode = ShimmeringLinkNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.secondaryTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor.withAlphaComponent(0.1)) + shimmeringNode.updateRects(rects) + shimmeringNode.frame = self.bounds + shimmeringNode.updateLayout(self.bounds.size) + shimmeringNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.shimmeringNodes.append(shimmeringNode) + self.insertSubnode(shimmeringNode, belowSubnode: self.textNode.textNode) + } + } + } else if !self.shimmeringNodes.isEmpty { + let shimmeringNodes = self.shimmeringNodes + self.shimmeringNodes = [] + + for shimmeringNode in shimmeringNodes { + shimmeringNode.alpha = 0.0 + shimmeringNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak shimmeringNode] _ in + shimmeringNode?.removeFromSupernode() + }) + } + } + } + private func updateSelection() { guard let item = self.item, let poll = self.poll else { return @@ -1786,4 +1902,11 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } return nil } + + override public func messageEffectTargetView() -> UIView? { + if !self.statusNode.isHidden { + return self.statusNode.messageEffectTargetView() + } + return nil + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD index afa1e075393..2a5776d7042 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD index 5b4f65efebe..563ac46c292 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD index 2d5e53a9b21..292c1216178 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD index a8bc3b4d00b..3874295d9fb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift index 5871ffeff55..2b47aa6ee77 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -138,6 +138,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD index 4955cb74eb2..fcab8f84241 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift index 367664f90c4..5dd68008e51 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift @@ -346,7 +346,7 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { context: context, animationCache: context.animationCache, presentationData: presentationData, - items: reactionItems.map(ReactionContextItem.reaction), + items: reactionItems.map { ReactionContextItem.reaction(item: $0, icon: .none) }, selectedItems: actions.editTags, title: actions.editTags.isEmpty ? presentationData.strings.Chat_ReactionSelectionTitleAddTag : presentationData.strings.Chat_ReactionSelectionTitleEditTag, reactionsLocked: false, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/BUILD index 3e8e689e0d5..093d71bf257 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD index cc323f2e045..d731f94c12f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift index 5b5d1394352..597743996fb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift @@ -122,6 +122,9 @@ public class ChatMessageShareButton: ASDisplayNode { if hasMore { updatedBottomIconImage = PresentationResourcesChat.chatFreeMoreButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) } + } else if case let .customChatContents(contents) = subject, case .hashTagSearch = contents.kind { + updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) } else if case .pinnedMessages = subject { updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD index 54233cea218..e8f10ed8e28 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index f538fe4e492..9962ab06882 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -76,6 +76,9 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { private var enableSynchronousImageApply: Bool = false + private var wasPending: Bool = false + private var didChangeFromPendingToSent: Bool = false + override public var visibility: ListViewItemNodeVisibility { didSet { let wasVisible = oldValue != .none @@ -92,6 +95,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { if self.visibilityStatus != oldValue { self.threadInfoNode?.visibility = self.visibilityStatus == true self.replyInfoNode?.visibility = self.visibilityStatus == true + + self.updateVisibility() } } } @@ -280,6 +285,13 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { override public func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { super.setupItem(item, synchronousLoad: synchronousLoad) + if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal || item.message.id.namespace == Namespaces.Message.QuickReplyLocal { + self.wasPending = true + } + if self.wasPending && (item.message.id.namespace != Namespaces.Message.Local && item.message.id.namespace != Namespaces.Message.ScheduledLocal && item.message.id.namespace != Namespaces.Message.QuickReplyLocal) { + self.didChangeFromPendingToSent = true + } + self.replyRecognizer?.allowBothDirections = false//!item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply if self.isNodeLoaded { self.view.disablesInteractiveTransitionGestureRecognizer = false//!item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply @@ -631,6 +643,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, @@ -718,6 +731,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { context: item.context, controllerInteraction: item.controllerInteraction, type: .standalone, + peer: nil, threadId: item.message.threadId ?? 1, parentMessage: item.message, constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), @@ -1351,6 +1365,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { f() } + + strongSelf.updateVisibility() } } @@ -1455,7 +1471,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { } else { - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, false, forwardInfoNode, nil) return } } @@ -1463,7 +1479,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } else if let peer = forwardInfo.source ?? forwardInfo.author { item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info(nil) : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default) } else if let _ = forwardInfo.authorSignature { - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, false, forwardInfoNode, nil) } } @@ -2130,4 +2146,56 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { return (image, self.imageNode.frame) } + + private func updateVisibility() { + guard let item = self.item else { + return + } + + var isPlaying = true + if case .visible = self.visibility { + } else { + isPlaying = false + } + if !item.controllerInteraction.canReadHistory { + isPlaying = false + } + + if !isPlaying { + self.removeEffectAnimations() + } + + if isPlaying { + var alreadySeen = true + if item.message.flags.contains(.Incoming) { + if let unreadRange = item.controllerInteraction.unreadMessageRange[UnreadMessageRangeKey(peerId: item.message.id.peerId, namespace: item.message.id.namespace)] { + if unreadRange.contains(item.message.id.id) { + if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) { + alreadySeen = false + } + } + } + } else { + if self.didChangeFromPendingToSent { + if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) { + alreadySeen = false + } + } + } + + if !alreadySeen { + item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id) + + self.playMessageEffect(force: false) + } + } + } + + override public func messageEffectTargetView() -> UIView? { + if let result = self.dateAndStatusNode.messageEffectTargetView() { + return result + } + + return nil + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD index 765105167ff..68f69f9d36e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/BUILD index 150eeba1ad0..33e4fd0dc68 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/BUILD index 748b191a917..bd5ffbb960c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", @@ -37,6 +37,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/MessageQuoteComponent", "//submodules/TelegramUI/Components/TextLoadingEffect", "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/InteractiveTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 1a8a6505f6a..ee47fa2bc28 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -8,7 +8,6 @@ import TextFormat import UrlEscaping import TelegramUniversalVideoContent import TextSelectionNode -import InvisibleInkDustNode import Emoji import AnimatedStickerNode import TelegramAnimatedStickerNode @@ -26,6 +25,7 @@ import ShimmeringLinkNode import ChatMessageItemCommon import TextLoadingEffect import ChatControllerInteraction +import InteractiveTextComponent private final class CachedChatMessageText { let text: String @@ -83,9 +83,7 @@ private func findQuoteRange(string: String, quoteText: String, offset: Int?) -> public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { private let containerNode: ASDisplayNode - private let textNode: TextNodeWithEntities - private var spoilerTextNode: TextNodeWithEntities? - private var dustNode: InvisibleInkDustNode? + private let textNode: InteractiveTextNodeWithEntities private let textAccessibilityOverlayNode: TextAccessibilityOverlayNode public var statusNode: ChatMessageDateAndStatusNode? @@ -111,19 +109,21 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { private var codeHighlightState: (id: EngineMessage.Id, specs: [CachedMessageSyntaxHighlight.Spec], disposable: Disposable)? + private var expandedBlockIds: Set = Set() + private var appliedExpandedBlockIds: Set? + private var displayContentsUnderSpoilers: Bool = false + override public var visibility: ListViewItemNodeVisibility { didSet { if oldValue != self.visibility { switch self.visibility { case .none: self.textNode.visibilityRect = nil - self.spoilerTextNode?.visibilityRect = nil case let .visible(_, subRect): var subRect = subRect subRect.origin.x = 0.0 subRect.size.width = 10000.0 self.textNode.visibilityRect = subRect - self.spoilerTextNode?.visibilityRect = subRect } } } @@ -132,7 +132,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { required public init() { self.containerNode = ASDisplayNode() - self.textNode = TextNodeWithEntities() + self.textNode = InteractiveTextNodeWithEntities() self.textAccessibilityOverlayNode = TextAccessibilityOverlayNode() @@ -140,16 +140,46 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.containerNode) - self.textNode.textNode.isUserInteractionEnabled = false + self.textNode.textNode.isUserInteractionEnabled = true self.textNode.textNode.contentMode = .topLeft self.textNode.textNode.contentsScale = UIScreenScale self.textNode.textNode.displaysAsynchronously = true + //self.containerNode.addSubnode(self.textAccessibilityOverlayNode) self.containerNode.addSubnode(self.textNode.textNode) - self.containerNode.addSubnode(self.textAccessibilityOverlayNode) self.textAccessibilityOverlayNode.openUrl = { [weak self] url in self?.item?.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: false, external: false)) } + + self.textNode.textNode.requestToggleBlockCollapsed = { [weak self] blockId in + guard let self, let item = self.item else { + return + } + if self.expandedBlockIds.contains(blockId) { + self.expandedBlockIds.remove(blockId) + } else { + self.expandedBlockIds.insert(blockId) + } + item.controllerInteraction.requestMessageUpdate(item.message.id, false) + } + self.textNode.textNode.requestDisplayContentsUnderSpoilers = { [weak self] in + guard let self else { + return + } + self.updateDisplayContentsUnderSpoilers(value: true) + } + self.textNode.textNode.canHandleTapAtPoint = { [weak self] point in + guard let self else { + return false + } + let localPoint = self.textNode.textNode.view.convert(point, to: self.view) + let action = self.tapActionAtPoint(localPoint, gesture: .tap, isEstimating: true) + if case .none = action.content { + return true + } else { + return false + } + } } required public init?(coder aDecoder: NSCoder) { @@ -163,11 +193,12 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { - let textLayout = TextNodeWithEntities.asyncLayout(self.textNode) - let spoilerTextLayout = TextNodeWithEntities.asyncLayout(self.spoilerTextNode) + let textLayout = InteractiveTextNodeWithEntities.asyncLayout(self.textNode) let statusLayout = ChatMessageDateAndStatusNode.asyncLayout(self.statusNode) let currentCachedChatMessageText = self.cachedChatMessageText + let expandedBlockIds = self.expandedBlockIds + let displayContentsUnderSpoilers = self.displayContentsUnderSpoilers return { item, layoutConstants, _, _, _, _ in let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) @@ -263,8 +294,12 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { default: break } - if case .customChatContents = item.associatedData.subject { - displayStatus = false + if case let .customChatContents(contents) = item.associatedData.subject { + if case .hashTagSearch = contents.kind { + displayStatus = true + } else { + displayStatus = false + } } if displayStatus { if incoming { @@ -290,11 +325,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 +346,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 { @@ -413,7 +453,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { var codeHighlightSpecs: [CachedMessageSyntaxHighlight.Spec] = [] var cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight? - if let entities = entities { + if let entities { var underlineLinks = true if !messageTheme.primaryTextColor.isEqual(messageTheme.linkTextColor) { underlineLinks = false @@ -498,7 +538,6 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let currentDict = updatedString.attributes(at: range.lowerBound, effectiveRange: nil) var updatedAttributes: [NSAttributedString.Key: Any] = currentDict - //updatedAttributes[NSAttributedString.Key.foregroundColor] = UIColor.clear.cgColor updatedAttributes[ChatTextInputAttributes.customEmoji] = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile) let insertString = NSAttributedString(string: updatedString.attributedSubstring(from: range).string, attributes: updatedAttributes) @@ -537,14 +576,20 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0) - let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: maximumNumberOfLines, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor, customTruncationToken: customTruncationToken)) - - let spoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)? - if !textLayout.spoilers.isEmpty { - spoilerTextLayoutAndApply = spoilerTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: maximumNumberOfLines, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor, displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true)) - } else { - spoilerTextLayoutAndApply = nil - } + let (textLayout, textApply) = textLayout(InteractiveTextNodeLayoutArguments( + attributedString: attributedText, + backgroundColor: nil, + maximumNumberOfLines: maximumNumberOfLines, + truncationType: .end, + constrainedSize: textConstrainedSize, + alignment: .natural, + cutout: nil, + insets: textInsets, + lineColor: messageTheme.accentControlColor, + displayContentsUnderSpoilers: displayContentsUnderSpoilers, + customTruncationToken: customTruncationToken, + expandedBlocks: expandedBlockIds + )) var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode))? if let statusType = statusType { @@ -554,8 +599,10 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } let trailingWidthToMeasure: CGFloat - if textLayout.hasRTL { + if let lastSegment = textLayout.segments.last, lastSegment.hasRTL { trailingWidthToMeasure = 10000.0 + } else if let lastSegment = textLayout.segments.last, lastSegment.hasBlockQuote { + trailingWidthToMeasure = textLayout.size.width } else { trailingWidthToMeasure = textLayout.trailingLineWidth } @@ -578,6 +625,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), + messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread), hasAutoremove: item.message.isSelfExpiring, @@ -614,7 +662,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { boundingSize.height += topInset + bottomInset - return (boundingSize, { [weak self] animation, synchronousLoads, _ in + return (boundingSize, { [weak self] animation, synchronousLoads, itemApply in if let strongSelf = self { strongSelf.item = item if let updatedCachedChatMessageText = updatedCachedChatMessageText { @@ -624,75 +672,31 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.textNode.textNode.displaysAsynchronously = !item.presentationData.isPreview strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: boundingSize) - let cachedLayout = strongSelf.textNode.textNode.cachedLayout - - if case .System = animation { - if let cachedLayout = cachedLayout { - if !cachedLayout.areLinesEqual(to: textLayout) { - if let textContents = strongSelf.textNode.textNode.contents { - let fadeNode = ASDisplayNode() - fadeNode.displaysAsynchronously = false - fadeNode.contents = textContents - fadeNode.frame = strongSelf.textNode.textNode.frame - fadeNode.isLayerBacked = true - strongSelf.containerNode.addSubnode(fadeNode) - fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in - fadeNode?.removeFromSupernode() - }) - strongSelf.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - } - } - } + if strongSelf.appliedExpandedBlockIds != nil && strongSelf.appliedExpandedBlockIds != strongSelf.expandedBlockIds { + itemApply?.setInvertOffsetDirection() } + strongSelf.appliedExpandedBlockIds = strongSelf.expandedBlockIds - let _ = textApply(TextNodeWithEntities.Arguments(context: item.context, cache: item.controllerInteraction.presentationContext.animationCache, renderer: item.controllerInteraction.presentationContext.animationRenderer, placeholderColor: messageTheme.mediaPlaceholderColor, attemptSynchronous: synchronousLoads)) + let _ = textApply(InteractiveTextNodeWithEntities.Arguments( + context: item.context, + cache: item.controllerInteraction.presentationContext.animationCache, + renderer: item.controllerInteraction.presentationContext.animationRenderer, + placeholderColor: messageTheme.mediaPlaceholderColor, + attemptSynchronous: synchronousLoads, + textColor: messageTheme.primaryTextColor, + spoilerEffectColor: messageTheme.secondaryTextColor, + animation: animation + )) animation.animator.updateFrame(layer: strongSelf.textNode.textNode.layer, frame: textFrame, completion: nil) - if let (_, spoilerTextApply) = spoilerTextLayoutAndApply { - let spoilerTextNode = spoilerTextApply(TextNodeWithEntities.Arguments(context: item.context, cache: item.controllerInteraction.presentationContext.animationCache, renderer: item.controllerInteraction.presentationContext.animationRenderer, placeholderColor: messageTheme.mediaPlaceholderColor, attemptSynchronous: synchronousLoads)) - if strongSelf.spoilerTextNode == nil { - spoilerTextNode.textNode.alpha = 0.0 - spoilerTextNode.textNode.isUserInteractionEnabled = false - spoilerTextNode.textNode.contentMode = .topLeft - spoilerTextNode.textNode.contentsScale = UIScreenScale - spoilerTextNode.textNode.displaysAsynchronously = false - strongSelf.containerNode.insertSubnode(spoilerTextNode.textNode, aboveSubnode: strongSelf.textAccessibilityOverlayNode) - - strongSelf.spoilerTextNode = spoilerTextNode - } - - strongSelf.spoilerTextNode?.textNode.frame = textFrame - - let dustNode: InvisibleInkDustNode - if let current = strongSelf.dustNode { - dustNode = current - } else { - dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency && !item.presentationData.isPreview) - strongSelf.dustNode = dustNode - strongSelf.containerNode.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode) - } - dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) - dustNode.update(size: dustNode.frame.size, color: messageTheme.secondaryTextColor, textColor: messageTheme.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) - } else if let spoilerTextNode = strongSelf.spoilerTextNode { - strongSelf.spoilerTextNode = nil - spoilerTextNode.textNode.removeFromSupernode() - - if let dustNode = strongSelf.dustNode { - strongSelf.dustNode = nil - dustNode.removeFromSupernode() - } - } - switch strongSelf.visibility { case .none: strongSelf.textNode.visibilityRect = nil - strongSelf.spoilerTextNode?.visibilityRect = nil case let .visible(_, subRect): var subRect = subRect subRect.origin.x = 0.0 subRect.size.width = 10000.0 strongSelf.textNode.visibilityRect = subRect - strongSelf.spoilerTextNode?.visibilityRect = subRect } if let textSelectionNode = strongSelf.textSelectionNode { @@ -704,7 +708,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } strongSelf.textAccessibilityOverlayNode.frame = textFrame - strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout + //TODO:localize + //strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout strongSelf.updateIsTranslating(isTranslating) @@ -846,8 +851,9 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } let textNodeFrame = self.textNode.textNode.frame - if let (index, attributes) = self.textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(self.dustNode?.isRevealed ?? true) { + let textLocalPoint = CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY) + if let (index, attributes) = self.textNode.textNode.attributesAtPoint(textLocalPoint) { + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !self.displayContentsUnderSpoilers { return ChatMessageBubbleContentTapAction(content: .none) } else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { var concealed = true @@ -856,7 +862,15 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { urlRange = urlRangeValue concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText) } - return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)), activate: { [weak self] in + + var content: ChatMessageBubbleContentTapAction.Content + if url.hasPrefix("tel:") { + content = .phone(url.replacingOccurrences(of: "tel:", with: "")) + } else { + content = .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)) + } + + return ChatMessageBubbleContentTapAction(content: content, activate: { [weak self] in guard let self else { return nil } @@ -930,6 +944,16 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { return ChatMessageBubbleContentTapAction(content: .copy(pre)) } else if let code = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Code)] as? String { return ChatMessageBubbleContentTapAction(content: .copy(code)) + } else if let _ = attributes[NSAttributedString.Key(rawValue: "Attribute__Blockquote")] { + if let _ = self.textNode.textNode.collapsibleBlockAtPoint(textLocalPoint) { + return ChatMessageBubbleContentTapAction(content: .none) + } else { + if let text = self.textNode.textNode.attributeSubstring(name: "Attribute__Blockquote", index: index) { + return ChatMessageBubbleContentTapAction(content: .copy(text.1)) + } else { + return ChatMessageBubbleContentTapAction(content: .none) + } + } } else if let emoji = attributes[NSAttributedString.Key(rawValue: ChatTextInputAttributes.customEmoji.rawValue)] as? ChatTextInputTextCustomEmojiAttribute, let file = emoji.file { return ChatMessageBubbleContentTapAction(content: .customEmoji(file)) } else { @@ -992,6 +1016,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { }) } } + override public func updateTouchesAtPoint(_ point: CGPoint?) { if let item = self.item { var rects: [CGRect]? @@ -1020,8 +1045,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } - if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, let dustNode = self.dustNode, !dustNode.isRevealed { - + if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, !self.displayContentsUnderSpoilers { } else if let rects = rects { let linkHighlightingNode: LinkHighlightingNode if let current = self.linkHighlightingNode { @@ -1053,7 +1077,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { rectsSet = [] } for i in 0 ..< rectsSet.count { - let rects = rectsSet[i] + var rects = rectsSet[i] + if rects.count > 1 { + for i in 0 ..< rects.count - 1 { + let deltaY = rects[i + 1].minY - rects[i].maxY + if deltaY > 0.0 && deltaY <= 2.0 { + rects[i].size.height += deltaY * 0.5 + rects[i + 1].size.height += deltaY * 0.5 + rects[i + 1].origin.y -= deltaY * 0.5 + } + } + } let textHighlightNode: LinkHighlightingNode if i < self.textHighlightingNodes.count { textHighlightNode = self.textHighlightingNodes[i] @@ -1281,11 +1315,13 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { guard let strongSelf = self else { return } - if let dustNode = strongSelf.dustNode, !dustNode.isRevealed, let textLayout = strongSelf.textNode.textNode.cachedLayout, !textLayout.spoilers.isEmpty, let selectionRange = selectionRange { - for (spoilerRange, _) in textLayout.spoilers { - if let intersection = selectionRange.intersection(spoilerRange), intersection.length > 0 { - dustNode.update(revealed: true) - return + if !strongSelf.displayContentsUnderSpoilers, let textLayout = strongSelf.textNode.textNode.cachedLayout, textLayout.segments.contains(where: { !$0.spoilers.isEmpty }), let selectionRange { + for segment in textLayout.segments { + for (spoilerRange, _) in segment.spoilers { + if let intersection = selectionRange.intersection(spoilerRange), intersection.length > 0 { + strongSelf.updateDisplayContentsUnderSpoilers(value: true) + return + } } } } @@ -1339,12 +1375,22 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { }) } - if let dustNode = self.dustNode, dustNode.isRevealed { - dustNode.update(revealed: false) + if self.displayContentsUnderSpoilers { + self.updateDisplayContentsUnderSpoilers(value: false) } } } + private func updateDisplayContentsUnderSpoilers(value: Bool) { + if self.displayContentsUnderSpoilers == value { + return + } + self.displayContentsUnderSpoilers = value + if let item = self.item { + item.controllerInteraction.requestMessageUpdate(item.message.id, false) + } + } + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if let statusNode = self.statusNode, !statusNode.isHidden { return statusNode.reactionView(value: value) @@ -1352,6 +1398,13 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { return nil } + override public func messageEffectTargetView() -> UIView? { + if let statusNode = self.statusNode, !statusNode.isHidden { + return statusNode.messageEffectTargetView() + } + return nil + } + override public func getStatusNode() -> ASDisplayNode? { return self.statusNode } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD index a870c334131..a81c32141d4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", @@ -29,6 +29,7 @@ swift_library( "//submodules/TelegramUI/Components/EmojiStatusComponent", "//submodules/WallpaperBackgroundNode", "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/AvatarNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift index f8f4523fd81..eb2b0893f64 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift @@ -19,6 +19,7 @@ import ComponentFlow import EmojiStatusComponent import WallpaperBackgroundNode import ChatControllerInteraction +import AvatarNode private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat) -> (CGPoint, UIImage?) { enum CornerType { @@ -184,7 +185,8 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { public let context: AccountContext public let controllerInteraction: ChatControllerInteraction public let type: ChatMessageThreadInfoType - public let threadId: Int64 + public let peer: EnginePeer? + public let threadId: Int64? public let parentMessage: Message public let constrainedSize: CGSize public let animationCache: AnimationCache? @@ -196,7 +198,8 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { context: AccountContext, controllerInteraction: ChatControllerInteraction, type: ChatMessageThreadInfoType, - threadId: Int64, + peer: EnginePeer?, + threadId: Int64?, parentMessage: Message, constrainedSize: CGSize, animationCache: AnimationCache?, @@ -207,6 +210,7 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { self.context = context self.controllerInteraction = controllerInteraction self.type = type + self.peer = peer self.threadId = threadId self.parentMessage = parentMessage self.constrainedSize = constrainedSize @@ -239,6 +243,7 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { private let contentBackgroundNode: ASImageNode private var textNode: TextNodeWithEntities? private let arrowNode: ASImageNode + private var avatarNode: AvatarNode? private var titleTopicIconView: ComponentHostView? private var titleTopicIconComponent: EmojiStatusComponent? @@ -322,6 +327,8 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { topicTitle = threadInfo.title topicIconId = threadInfo.icon topicIconColor = threadInfo.iconColor + } else if let peer = arguments.peer { + topicTitle = peer.displayTitle(strings: arguments.presentationData.strings, displayOrder: arguments.presentationData.nameDisplayOrder) } let backgroundColor: UIColor @@ -362,7 +369,10 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { let fillInset: CGFloat = 5.0 let iconSize = CGSize(width: 22.0, height: 22.0) let insets = UIEdgeInsets(top: 2.0, left: 4.0, bottom: 2.0, right: 4.0) - let spacing: CGFloat = 4.0 + var spacing: CGFloat = 4.0 + if arguments.peer != nil { + spacing += 3.0 + } let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: arguments.constrainedSize.width - insets.left - insets.right - iconSize.width - spacing, height: arguments.constrainedSize.height), alignment: .natural, cutout: nil, insets: .zero)) @@ -393,7 +403,11 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { } node.pressed = { - arguments.controllerInteraction.navigateToThreadMessage(arguments.parentMessage.id.peerId, arguments.threadId, arguments.parentMessage.id) + if let _ = arguments.peer { + arguments.controllerInteraction.navigateToMessage(arguments.parentMessage.id, arguments.parentMessage.id, NavigateToMessageParams(timestamp: nil, quote: nil, forceNew: true)) + } else if let threadId = arguments.threadId { + arguments.controllerInteraction.navigateToThreadMessage(arguments.parentMessage.id.peerId, threadId, arguments.parentMessage.id) + } } if node.lineRects != lineRects { @@ -471,57 +485,74 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { node.contentNode.addSubnode(textNode.textNode) } - let titleTopicIconView: ComponentHostView - if let current = node.titleTopicIconView { - titleTopicIconView = current - } else { - titleTopicIconView = ComponentHostView() - node.titleTopicIconView = titleTopicIconView - node.contentNode.view.addSubview(titleTopicIconView) - } - - let titleTopicIconContent: EmojiStatusComponent.Content - var containerSize: CGSize = CGSize(width: 22.0, height: 22.0) - var iconX: CGFloat = 0.0 - if arguments.threadId == 1 { - titleTopicIconContent = .image(image: generalThreadIcon) - containerSize = CGSize(width: 18.0, height: 18.0) - iconX = 3.0 - } else if let fileId = topicIconId, fileId != 0 { - titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: arguments.presentationData.theme.theme.list.mediaPlaceholderColor, themeColor: arguments.presentationData.theme.theme.list.itemAccentColor, loopMode: .count(1)) - } else { - titleTopicIconContent = .topic(title: String(topicTitle.prefix(1)), color: topicIconColor, size: CGSize(width: 22.0, height: 22.0)) - } - - if let animationCache = arguments.animationCache, let animationRenderer = arguments.animationRenderer { - let titleTopicIconComponent = EmojiStatusComponent( - context: arguments.context, - animationCache: animationCache, - animationRenderer: animationRenderer, - content: titleTopicIconContent, - isVisibleForAnimations: node.visibility, - action: nil - ) - node.titleTopicIconComponent = titleTopicIconComponent + if let peer = arguments.peer { + let avatarNode: AvatarNode + if let current = node.avatarNode { + avatarNode = current + } else { + avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0)) + node.contentNode.addSubnode(avatarNode) + } + avatarNode.frame = CGRect(origin: CGPoint(x: -1.0, y: -3.0), size: CGSize(width: 26.0, height: 26.0)) - let iconSize = titleTopicIconView.update( - transition: .immediate, - component: AnyComponent(titleTopicIconComponent), - environment: {}, - containerSize: containerSize - ) + var overrideImage: AvatarNodeImageOverride? + if peer.id.isReplies { + overrideImage = .repliesIcon + } + avatarNode.setPeer(context: arguments.context, theme: arguments.presentationData.theme.theme, peer: peer, overrideImage: overrideImage) + } else { + let titleTopicIconView: ComponentHostView + if let current = node.titleTopicIconView { + titleTopicIconView = current + } else { + titleTopicIconView = ComponentHostView() + node.titleTopicIconView = titleTopicIconView + node.contentNode.view.addSubview(titleTopicIconView) + } - let iconY: CGFloat - if let firstLineMidY = firstLineMidY { - iconY = floorToScreenPixels(firstLineMidY - iconSize.height / 2.0) + let titleTopicIconContent: EmojiStatusComponent.Content + var containerSize: CGSize = CGSize(width: 22.0, height: 22.0) + var iconX: CGFloat = 0.0 + if arguments.threadId == 1 { + titleTopicIconContent = .image(image: generalThreadIcon) + containerSize = CGSize(width: 18.0, height: 18.0) + iconX = 3.0 + } else if let fileId = topicIconId, fileId != 0 { + titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: arguments.presentationData.theme.theme.list.mediaPlaceholderColor, themeColor: arguments.presentationData.theme.theme.list.itemAccentColor, loopMode: .count(1)) } else { - iconY = 0.0 + titleTopicIconContent = .topic(title: String(topicTitle.prefix(1)), color: topicIconColor, size: CGSize(width: 22.0, height: 22.0)) } - titleTopicIconView.frame = CGRect(origin: CGPoint(x: insets.left + iconX, y: insets.top + iconY), size: iconSize) + if let animationCache = arguments.animationCache, let animationRenderer = arguments.animationRenderer { + let titleTopicIconComponent = EmojiStatusComponent( + context: arguments.context, + animationCache: animationCache, + animationRenderer: animationRenderer, + content: titleTopicIconContent, + isVisibleForAnimations: node.visibility, + action: nil + ) + node.titleTopicIconComponent = titleTopicIconComponent + + let iconSize = titleTopicIconView.update( + transition: .immediate, + component: AnyComponent(titleTopicIconComponent), + environment: {}, + containerSize: containerSize + ) + + let iconY: CGFloat + if let firstLineMidY = firstLineMidY { + iconY = floorToScreenPixels(firstLineMidY - iconSize.height / 2.0) + } else { + iconY = 0.0 + } + + titleTopicIconView.frame = CGRect(origin: CGPoint(x: insets.left + iconX, y: insets.top + iconY), size: iconSize) + } } - let textFrame = CGRect(origin: CGPoint(x: iconSize.width + 2.0 + insets.left, y: insets.top), size: textLayout.size) + let textFrame = CGRect(origin: CGPoint(x: iconSize.width + (spacing - 2.0) + insets.left, y: insets.top), size: textLayout.size) textNode.textNode.frame = textFrame if let arrowIcon = arrowIcon, let firstLine = lineRects.first, let lastLine = lineRects.last { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/BUILD index bbe8d60e5f4..b58935deae4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/BUILD @@ -7,11 +7,11 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", - "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/AccountContext", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/Sources/ChatMessageTransitionNode.swift index eef65155476..762d64392b3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/Sources/ChatMessageTransitionNode.swift @@ -1,6 +1,6 @@ import Foundation import UIKit -import ChatMessageItemView +import AccountContext import AsyncDisplayKit public protocol ChatMessageTransitionNodeDecorationItemNode: ASDisplayNode { @@ -10,6 +10,6 @@ public protocol ChatMessageTransitionNodeDecorationItemNode: ASDisplayNode { public protocol ChatMessageTransitionNode: AnyObject { typealias DecorationItemNode = ChatMessageTransitionNodeDecorationItemNode - func add(decorationView: UIView, itemNode: ChatMessageItemView) -> DecorationItemNode + func add(decorationView: UIView, itemNode: ChatMessageItemNodeProtocol) -> DecorationItemNode func remove(decorationNode: DecorationItemNode) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD index 87d31d072a9..3e34751cf80 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD index 98d37bb5828..e73a465c2a1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD index 61ab438674f..23ed0e8ab70 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index e05cc37df0a..0498b223cc3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -140,7 +140,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent self.contentNode.activateAction = { [weak self] in if let strongSelf = self, let item = strongSelf.item { if let _ = item.message.adAttribute { - item.controllerInteraction.activateAdAction(item.message.id) + item.controllerInteraction.activateAdAction(item.message.id, strongSelf.contentNode.makeProgress()) } else { var webPageContent: TelegramMediaWebpageLoadedContent? for media in item.message.media { @@ -749,4 +749,8 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { return self.contentNode.reactionTargetView(value: value) } + + override public func messageEffectTargetView() -> UIView? { + return self.contentNode.messageEffectTargetView() + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatNavigationButton/BUILD b/submodules/TelegramUI/Components/Chat/ChatNavigationButton/BUILD index 7c7d0d30c1f..12111cbbc66 100644 --- a/submodules/TelegramUI/Components/Chat/ChatNavigationButton/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatNavigationButton/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ ], diff --git a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/BUILD b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/BUILD index 6596b91582f..245407c88ef 100644 --- a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/BUILD b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/BUILD index 8ee4eade32e..3ba3678c847 100644 --- a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD index e826f371a24..18a1daddd63 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 8dc6300d157..73f547d8392 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -137,7 +137,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } self.loadingNode = ChatLoadingNode(context: context, theme: self.presentationData.theme, chatWallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners) - self.emptyNode = ChatRecentActionsEmptyNode(theme: self.presentationData.theme, chatWallpaper: self.presentationData.chatWallpaper, chatBubbleCorners: self.presentationData.chatBubbleCorners) + self.emptyNode = ChatRecentActionsEmptyNode(theme: self.presentationData.theme, chatWallpaper: self.presentationData.chatWallpaper, chatBubbleCorners: self.presentationData.chatBubbleCorners, hasIcon: true) self.emptyNode.alpha = 0.0 self.chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners) @@ -304,9 +304,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, navigateToMessageStandalone: { _ in }, navigateToThreadMessage: { [weak self] peerId, threadId, _ in if let context = self?.context, let navigationController = self?.getNavigationController() { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).startStandalone() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone() } - }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false + }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { [weak self] messageId, _, _, _ in guard let self else { return @@ -571,9 +571,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let strongSelf = self { strongSelf.context.sharedContext.applicationBindings.openAppStorePage() } - }, displayMessageTooltip: { _, _, _, _ in + }, displayMessageTooltip: { _, _, _, _, _ in }, seekToTimecode: { _, _, _ in - }, scheduleCurrentMessage: { + }, scheduleCurrentMessage: { _ in }, sendScheduledMessagesNow: { _ in }, editScheduledMessagesTime: { _ in }, performTextSelectionAction: { _, _, _, _ in @@ -604,7 +604,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in }, openWebView: { _, _, _, _ in - }, activateAdAction: { _ in + }, activateAdAction: { _, _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { @@ -613,7 +613,11 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, openPremiumStatusInfo: { _, _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in }, openGroupBoostInfo: { _, _ in - }, openStickerEditor: { + }, openStickerEditor: { + }, openPhoneContextMenu: { _ in + }, openAgeRestrictedMessageMedia: { _, _ in + }, playMessageEffect: { _ in + }, editMessageFactCheck: { _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { @@ -654,14 +658,16 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { let previousDeletedHeaderMessages = Atomic>(value: Set()) let chatThemes = self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager) + let availableReactions: Signal = self.context.availableReactions let historyViewTransition = combineLatest( historyViewUpdate, self.chatPresentationDataPromise.get(), chatThemes, + availableReactions, self.expandedDeletedMessagesPromise.get() ) - |> mapToQueue { [weak self] update, chatPresentationData, chatThemes, expandedDeletedMessages -> Signal in + |> mapToQueue { [weak self] update, chatPresentationData, chatThemes, availableReactions, expandedDeletedMessages -> Signal in var deletedHeaderMessages = previousDeletedHeaderMessages.with { $0 } let processedView = chatRecentActionsEntries(entries: update.0, presentationData: chatPresentationData, expandedDeletedMessages: expandedDeletedMessages, currentDeletedHeaderMessages: &deletedHeaderMessages) @@ -686,7 +692,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { searchResultsState = nil } - return .single(chatRecentActionsHistoryPreparedTransition(from: previous ?? [], to: processedView, type: updateType, canLoadEarlier: update.1, displayingResults: update.3, context: context, peer: peer, controllerInteraction: controllerInteraction, chatThemes: chatThemes, searchResultsState: searchResultsState, toggledDeletedMessageIds: toggledDeletedMessageIds)) + return .single(chatRecentActionsHistoryPreparedTransition(from: previous ?? [], to: processedView, type: updateType, canLoadEarlier: update.1, displayingResults: update.3, context: context, peer: peer, controllerInteraction: controllerInteraction, chatThemes: chatThemes, availableReactions: availableReactions, searchResultsState: searchResultsState, toggledDeletedMessageIds: toggledDeletedMessageIds)) } let appliedTransition = historyViewTransition |> deliverOnMainQueue |> mapToQueue { [weak self] transition -> Signal in @@ -1179,7 +1185,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } case let .replyThread(messageId): if let navigationController = strongSelf.getNavigationController() { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).startStandalone() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone() } case let .stickerPack(name, type): let _ = type @@ -1230,8 +1236,10 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let strongSelf = self { strongSelf.openPeer(peer: peer) } - }, sendFile: nil, + }, + sendFile: nil, sendSticker: nil, + sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { c, a in diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsEmptyNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsEmptyNode.swift index 55b437e6197..096b52733ef 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsEmptyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsEmptyNode.swift @@ -16,6 +16,7 @@ private let textFont = Font.regular(13.0) public final class ChatRecentActionsEmptyNode: ASDisplayNode { private var theme: PresentationTheme private var chatWallpaper: TelegramWallpaper + private var hasIcon: Bool private let backgroundNode: NavigationBackgroundNode private let iconNode: ASImageNode @@ -36,9 +37,10 @@ public final class ChatRecentActionsEmptyNode: ASDisplayNode { private var title: String = "" private var text: String = "" - public init(theme: PresentationTheme, chatWallpaper: TelegramWallpaper, chatBubbleCorners: PresentationChatBubbleCorners) { + public init(theme: PresentationTheme, chatWallpaper: TelegramWallpaper, chatBubbleCorners: PresentationChatBubbleCorners, hasIcon: Bool) { self.theme = theme self.chatWallpaper = chatWallpaper + self.hasIcon = hasIcon self.backgroundNode = NavigationBackgroundNode(color: .clear) @@ -60,7 +62,9 @@ public final class ChatRecentActionsEmptyNode: ASDisplayNode { self.allowsGroupOpacity = true self.addSubnode(self.backgroundNode) - self.addSubnode(self.iconNode) + if hasIcon { + self.addSubnode(self.iconNode) + } self.addSubnode(self.titleNode) self.addSubnode(self.textNode) // MARK: Nicegram Unblock @@ -88,7 +92,7 @@ public final class ChatRecentActionsEmptyNode: ASDisplayNode { self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), transition: .immediate) - let insets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 25.0, right: 16.0) + let insets = self.hasIcon ? UIEdgeInsets(top: 16.0, left: 16.0, bottom: 25.0, right: 16.0) : UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) let maxTextWidth = min(196.0, size.width - insets.left - insets.right - 18.0 * 2.0) @@ -101,12 +105,19 @@ public final class ChatRecentActionsEmptyNode: ASDisplayNode { let spacing: CGFloat = titleLayout.size.height.isZero ? 0.0 : 7.0 let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: self.text, font: textFont, textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) - if themeUpdated || self.iconNode.image == nil { - self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Recent Actions/Placeholder"), color: serviceColor.primaryText) + let contentSize: CGSize + let iconSize: CGSize + if self.hasIcon { + if themeUpdated || self.iconNode.image == nil { + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Recent Actions/Placeholder"), color: serviceColor.primaryText) + } + iconSize = self.iconNode.image?.size ?? .zero + contentSize = CGSize(width: max(titleLayout.size.width, textLayout.size.width) + insets.left + insets.right, height: 5.0 + insets.bottom + iconSize.height - 2.0 + titleLayout.size.height + spacing + textLayout.size.height) + } else { + iconSize = .zero + contentSize = CGSize(width: max(titleLayout.size.width, textLayout.size.width) + insets.left + insets.right, height: insets.top + insets.bottom + titleLayout.size.height + spacing + textLayout.size.height) } - let iconSize = self.iconNode.image?.size ?? .zero - let contentSize = CGSize(width: max(titleLayout.size.width, textLayout.size.width) + insets.left + insets.right, height: 5.0 + insets.bottom + iconSize.height - 2.0 + titleLayout.size.height + spacing + textLayout.size.height) let backgroundFrame = CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) / 2.0), y: floor((size.height - contentSize.height) / 2.0)), size: contentSize) transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(14.0, self.backgroundNode.bounds.height / 2.0), transition: transition) @@ -114,7 +125,7 @@ public final class ChatRecentActionsEmptyNode: ASDisplayNode { let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((contentSize.width - iconSize.width) / 2.0), y: backgroundFrame.minY + 5.0), size: iconSize) transition.updateFrame(node: self.iconNode, frame: iconFrame) - let titleFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((contentSize.width - titleLayout.size.width) / 2.0), y: iconFrame.maxY - 2.0), size: titleLayout.size) + let titleFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((contentSize.width - titleLayout.size.width) / 2.0), y: self.hasIcon ? iconFrame.maxY - 2.0 : backgroundFrame.minY + insets.top), size: titleLayout.size) transition.updateFrame(node: self.titleNode, frame: titleFrame) // MARK: Nicegram Unblock diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift index 29010e6a327..ab70fa9a0da 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift @@ -130,7 +130,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return self.id } - func item(context: AccountContext, peer: Peer, controllerInteraction: ChatControllerInteraction, chatThemes: [TelegramTheme]) -> ListViewItem { + func item(context: AccountContext, peer: Peer, controllerInteraction: ChatControllerInteraction, chatThemes: [TelegramTheme], availableReactions: AvailableReactions?, availableMessageEffects: AvailableMessageEffects?) -> ListViewItem { switch self.entry.event.action { case let .changeTitle(_, new): var peers = SimpleDictionary() @@ -143,7 +143,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.titleUpdated(title: new) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeAbout(prev, new): var peers = SimpleDictionary() var author: Peer? @@ -174,14 +174,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: let peers = SimpleDictionary() let attributes: [MessageAttribute] = [] let prevMessage = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: new, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) } case let .changeUsername(prev, new): var peers = SimpleDictionary() @@ -212,7 +212,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var previousAttributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = [] @@ -231,7 +231,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changeUsernames(prev, new): var peers = SimpleDictionary() @@ -262,7 +262,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var previousAttributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = [] @@ -300,7 +300,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changePhoto(_, new): var peers = SimpleDictionary() @@ -319,7 +319,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.photoUpdated(image: photo) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleInvites(value): var peers = SimpleDictionary() var author: Peer? @@ -346,7 +346,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleSignatures(value): var peers = SimpleDictionary() var author: Peer? @@ -373,7 +373,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .updatePinned(message): switch self.id.contentIndex { case .header: @@ -404,7 +404,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: if let message = message { var peers = SimpleDictionary() @@ -428,7 +428,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { peers[peer.id] = peer } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: message.associatedThreadInfo, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } else { var peers = SimpleDictionary() var author: Peer? @@ -450,7 +450,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } } case let .editMessage(prev, message): @@ -495,7 +495,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -518,7 +518,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { peers[peer.id] = peer } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: message.associatedThreadInfo, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) } case let .deleteMessage(message): switch self.id.contentIndex { @@ -579,7 +579,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -628,7 +628,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: message.id.id), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: message.associatedThreadInfo, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: additionalContent) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: additionalContent) } case .participantJoin, .participantLeave: var peers = SimpleDictionary() @@ -646,7 +646,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { action = TelegramMediaActionType.removedMembers(peerIds: [self.entry.event.peerId]) } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantInvite(participant): var peers = SimpleDictionary() var author: Peer? @@ -663,7 +663,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action: TelegramMediaActionType action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id]) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantToggleBan(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -794,7 +794,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantToggleAdmin(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -1034,7 +1034,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeStickerPack(_, new): var peers = SimpleDictionary() var author: Peer? @@ -1063,7 +1063,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .togglePreHistoryHidden(value): var peers = SimpleDictionary() var author: Peer? @@ -1093,7 +1093,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .updateDefaultBannedRights(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -1152,7 +1152,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .pollStopped(message): switch self.id.contentIndex { case .header: @@ -1180,7 +1180,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -1203,7 +1203,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { peers[peer.id] = peer } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: message.author, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: message.associatedThreadInfo, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: nil) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: nil) } case let .linkedPeerUpdated(previous, updated): var peers = SimpleDictionary() @@ -1259,7 +1259,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeGeoLocation(_, updated): var peers = SimpleDictionary() var author: Peer? @@ -1281,12 +1281,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } else { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } case let .updateSlowmode(_, newValue): var peers = SimpleDictionary() @@ -1317,7 +1317,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .startGroupCall, .endGroupCall: var peers = SimpleDictionary() var author: Peer? @@ -1354,7 +1354,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .groupCallUpdateParticipantMuteStatus(participantId, isMuted): var peers = SimpleDictionary() var author: Peer? @@ -1388,7 +1388,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .updateGroupCallSettings(joinMuted): var peers = SimpleDictionary() var author: Peer? @@ -1417,7 +1417,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .groupCallUpdateParticipantVolume(participantId, volume): var peers = SimpleDictionary() var author: Peer? @@ -1448,7 +1448,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .deleteExportedInvitation(invite): var peers = SimpleDictionary() var author: Peer? @@ -1474,7 +1474,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .revokeExportedInvitation(invite): var peers = SimpleDictionary() var author: Peer? @@ -1500,7 +1500,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .editExportedInvitation(_, updatedInvite): var peers = SimpleDictionary() var author: Peer? @@ -1526,7 +1526,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantJoinedViaInvite(invite, joinedViaFolderLink): var peers = SimpleDictionary() var author: Peer? @@ -1557,7 +1557,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeHistoryTTL(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1588,7 +1588,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeAvailableReactions(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1659,7 +1659,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeTheme(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1690,7 +1690,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantJoinByRequest(invite, approvedBy): var peers = SimpleDictionary() var author: Peer? @@ -1730,7 +1730,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleCopyProtection(value): var peers = SimpleDictionary() var author: Peer? @@ -1757,7 +1757,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .sendMessage(message): switch self.id.contentIndex { case .header: @@ -1782,7 +1782,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -1805,7 +1805,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { peers[peer.id] = peer } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: message.associatedThreadInfo, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } case let .createTopic(info): var peers = SimpleDictionary() @@ -1825,7 +1825,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .deleteTopic(info): var peers = SimpleDictionary() var author: Peer? @@ -1846,7 +1846,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .editTopic(prevInfo, newInfo): var peers = SimpleDictionary() var author: Peer? @@ -1919,7 +1919,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .pinTopic(prevInfo, newInfo): var peers = SimpleDictionary() var author: Peer? @@ -1957,7 +1957,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleForum(isForum): var peers = SimpleDictionary() var author: Peer? @@ -1978,7 +1978,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleAntiSpam(isEnabled): var peers = SimpleDictionary() var author: Peer? @@ -1999,7 +1999,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeNameColor(_, _, updatedColor, updatedIcon): var peers = SimpleDictionary() var author: Peer? @@ -2068,7 +2068,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: TelegramMediaActionType.CustomTextAttributes(attributes: additionalAttributes)) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeProfileColor(_, _, updatedColor, updatedIcon): var peers = SimpleDictionary() var author: Peer? @@ -2147,7 +2147,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: TelegramMediaActionType.CustomTextAttributes(attributes: additionalAttributes)) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeStatus(_, status): var peers = SimpleDictionary() var author: Peer? @@ -2182,7 +2182,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeWallpaper(_, wallpaper): var peers = SimpleDictionary() var author: Peer? @@ -2210,7 +2210,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil, chatThemes: chatThemes), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil, chatThemes: chatThemes), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeEmojiPack(_, new): var peers = SimpleDictionary() var author: Peer? @@ -2239,7 +2239,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } } } @@ -2317,12 +2317,12 @@ struct ChatRecentActionsHistoryTransition { let isEmpty: Bool } -func chatRecentActionsHistoryPreparedTransition(from fromEntries: [ChatRecentActionsEntry], to toEntries: [ChatRecentActionsEntry], type: ChannelAdminEventLogUpdateType, canLoadEarlier: Bool, displayingResults: Bool, context: AccountContext, peer: Peer, controllerInteraction: ChatControllerInteraction, chatThemes: [TelegramTheme], searchResultsState: (String, [MessageIndex])?, toggledDeletedMessageIds: Set) -> ChatRecentActionsHistoryTransition { +func chatRecentActionsHistoryPreparedTransition(from fromEntries: [ChatRecentActionsEntry], to toEntries: [ChatRecentActionsEntry], type: ChannelAdminEventLogUpdateType, canLoadEarlier: Bool, displayingResults: Bool, context: AccountContext, peer: Peer, controllerInteraction: ChatControllerInteraction, chatThemes: [TelegramTheme], availableReactions: AvailableReactions?, searchResultsState: (String, [MessageIndex])?, toggledDeletedMessageIds: Set) -> ChatRecentActionsHistoryTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdatesReversed(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, peer: peer, controllerInteraction: controllerInteraction, chatThemes: chatThemes), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, peer: peer, controllerInteraction: controllerInteraction, chatThemes: chatThemes), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, peer: peer, controllerInteraction: controllerInteraction, chatThemes: chatThemes, availableReactions: availableReactions, availableMessageEffects: nil), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, peer: peer, controllerInteraction: controllerInteraction, chatThemes: chatThemes, availableReactions: availableReactions, availableMessageEffects: nil), directionHint: nil) } return ChatRecentActionsHistoryTransition(filteredEntries: toEntries, type: type, deletions: deletions, insertions: insertions, updates: updates, canLoadEarlier: canLoadEarlier, displayingResults: displayingResults, searchResultsState: searchResultsState, synchronous: !toggledDeletedMessageIds.isEmpty, isEmpty: toEntries.isEmpty) } diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/BUILD b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/BUILD new file mode 100644 index 00000000000..b0c0658e4cc --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/BUILD @@ -0,0 +1,37 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatSendAudioMessageContextPreview", + module_name = "ChatSendAudioMessageContextPreview", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/ChatPresentationInterfaceState", + "//submodules/ChatSendMessageActionUI", + "//submodules/ComponentFlow", + "//submodules/AccountContext", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Display", + "//submodules/WallpaperBackgroundNode", + "//submodules/AudioWaveform", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUIPreferences", + "//submodules/MosaicLayout", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift new file mode 100644 index 00000000000..a0217da0b5d --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -0,0 +1,775 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import TelegramPresentationData +import ChatPresentationInterfaceState +import AccountContext +import ChatSendMessageActionUI +import SwiftSignalKit +import ComponentFlow +import Display +import Postbox +import TelegramCore +import WallpaperBackgroundNode +import AudioWaveform +import ChatMessageItemView +import ChatMessageItemCommon +import ChatMessageBubbleContentNode +import ChatMessageMediaBubbleContentNode +import ChatControllerInteraction +import TelegramUIPreferences +import ChatHistoryEntry +import MosaicLayout + +public final class ChatSendContactMessageContextPreview: UIView, ChatSendMessageContextScreenMediaPreview { + private let context: AccountContext + private let presentationData: PresentationData + private let wallpaperBackgroundNode: WallpaperBackgroundNode? + private let contactPeers: [ContactListPeer] + + private var messageNodes: [ListViewItemNode]? + private let messagesContainer: UIView + + public var isReady: Signal { + return .single(true) + } + + public var view: UIView { + return self + } + + public var globalClippingRect: CGRect? { + return nil + } + + public var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { + return .message + } + + public init(context: AccountContext, presentationData: PresentationData, wallpaperBackgroundNode: WallpaperBackgroundNode?, contactPeers: [ContactListPeer]) { + self.context = context + self.presentationData = presentationData + self.wallpaperBackgroundNode = wallpaperBackgroundNode + self.contactPeers = contactPeers + + self.messagesContainer = UIView() + self.messagesContainer.layer.sublayerTransform = CATransform3DMakeScale(-1.0, -1.0, 1.0) + + super.init(frame: CGRect()) + + self.addSubview(self.messagesContainer) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + public func animateIn(transition: Transition) { + transition.animateAlpha(view: self.messagesContainer, from: 0.0, to: 1.0) + transition.animateScale(view: self.messagesContainer, from: 0.001, to: 1.0) + } + + public func animateOut(transition: Transition) { + transition.setAlpha(view: self.messagesContainer, alpha: 0.0) + transition.setScale(view: self.messagesContainer, scale: 0.001) + } + + public func animateOutOnSend(transition: Transition) { + transition.setAlpha(view: self.messagesContainer, alpha: 0.0) + } + + public func update(containerSize: CGSize, transition: Transition) -> CGSize { + var contactsMedia: [TelegramMediaContact] = [] + for peer in self.contactPeers { + switch peer { + case let .peer(contact, _, _): + guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + continue + } + let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + + let phone = contactData.basicData.phoneNumbers[0].value + contactsMedia.append(TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil)) + case let .deviceContact(_, basicData): + guard !basicData.phoneNumbers.isEmpty else { + continue + } + let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + + let phone = contactData.basicData.phoneNumbers[0].value + contactsMedia.append(TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil)) + } + } + + var items: [ListViewItem] = [] + for contactMedia in contactsMedia { + let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [contactMedia], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + + let item = self.context.sharedContext.makeChatMessagePreviewItem( + context: self.context, + messages: [message], + theme: presentationData.theme, + strings: presentationData.strings, + wallpaper: presentationData.chatWallpaper, + fontSize: presentationData.chatFontSize, + chatBubbleCorners: presentationData.chatBubbleCorners, + dateTimeFormat: presentationData.dateTimeFormat, + nameOrder: presentationData.nameDisplayOrder, + forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: .Local), + tapMessage: nil, + clickThroughMessage: nil, + backgroundNode: self.wallpaperBackgroundNode, + availableReactions: nil, + accountPeer: nil, + isCentered: false, + isPreview: true, + isStandalone: true + ) + items.append(item) + } + + let params = ListViewItemLayoutParams(width: containerSize.width, leftInset: 0.0, rightInset: 0.0, availableHeight: containerSize.height) + if let messageNodes = self.messageNodes { + for i in 0 ..< items.count { + let itemNode = messageNodes[i] + items[i].updateNode(async: { $0() }, node: { + return itemNode + }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in + let nodeFrame = CGRect(origin: CGPoint(x: itemNode.frame.minX, y: itemNode.frame.minY), size: CGSize(width: containerSize.width, height: layout.size.height)) + + itemNode.contentSize = layout.contentSize + itemNode.insets = layout.insets + itemNode.frame = nodeFrame + itemNode.isUserInteractionEnabled = false + + apply(ListViewItemApply(isOnScreen: true)) + }) + } + } else { + var messageNodes: [ListViewItemNode] = [] + for i in 0 ..< items.count { + var itemNode: ListViewItemNode? + items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in + itemNode = node + apply().1(ListViewItemApply(isOnScreen: true)) + }) + itemNode!.isUserInteractionEnabled = false + messageNodes.append(itemNode!) + self.messagesContainer.addSubview(itemNode!.view) + } + self.messageNodes = messageNodes + } + + var contentSize = CGSize() + for messageNode in self.messageNodes ?? [] { + guard let messageNode = messageNode as? ChatMessageItemView else { + continue + } + if !contentSize.height.isZero { + contentSize.height += 2.0 + } + let contentFrame = messageNode.contentFrame() + contentSize.height += contentFrame.height + contentSize.width = max(contentSize.width, contentFrame.width) + } + + var contentOffsetY: CGFloat = 0.0 + for messageNode in self.messageNodes ?? [] { + guard let messageNode = messageNode as? ChatMessageItemView else { + continue + } + if !contentOffsetY.isZero { + contentOffsetY += 2.0 + } + let contentFrame = messageNode.contentFrame() + messageNode.frame = CGRect(origin: CGPoint(x: contentFrame.minX + contentSize.width - contentFrame.width + 6.0, y: 3.0 + contentOffsetY), size: CGSize(width: contentFrame.width, height: contentFrame.height)) + contentOffsetY += contentFrame.height + } + + self.messagesContainer.frame = CGRect(origin: CGPoint(x: 6.0, y: 3.0), size: CGSize(width: contentSize.width, height: contentSize.height)) + + return CGSize(width: contentSize.width - 4.0, height: contentSize.height + 2.0) + } +} + +public final class ChatSendAudioMessageContextPreview: UIView, ChatSendMessageContextScreenMediaPreview { + private let context: AccountContext + private let presentationData: PresentationData + private let wallpaperBackgroundNode: WallpaperBackgroundNode? + private let waveform: AudioWaveform + + private var messageNodes: [ListViewItemNode]? + private let messagesContainer: UIView + + public var isReady: Signal { + return .single(true) + } + + public var view: UIView { + return self + } + + public var globalClippingRect: CGRect? { + return nil + } + + public var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { + return .message + } + + public init(context: AccountContext, presentationData: PresentationData, wallpaperBackgroundNode: WallpaperBackgroundNode?, waveform: AudioWaveform) { + self.context = context + self.presentationData = presentationData + self.wallpaperBackgroundNode = wallpaperBackgroundNode + self.waveform = waveform + + self.messagesContainer = UIView() + self.messagesContainer.layer.sublayerTransform = CATransform3DMakeScale(-1.0, -1.0, 1.0) + + super.init(frame: CGRect()) + + self.addSubview(self.messagesContainer) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + public func animateIn(transition: Transition) { + transition.animateAlpha(view: self.messagesContainer, from: 0.0, to: 1.0) + transition.animateScale(view: self.messagesContainer, from: 0.001, to: 1.0) + } + + public func animateOut(transition: Transition) { + transition.setAlpha(view: self.messagesContainer, alpha: 0.0) + transition.setScale(view: self.messagesContainer, scale: 0.001) + } + + public func animateOutOnSend(transition: Transition) { + transition.setAlpha(view: self.messagesContainer, alpha: 0.0) + } + + public func update(containerSize: CGSize, transition: Transition) -> CGSize { + let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: self.waveform.makeBitstream())] + let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) + + let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [voiceMedia], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + + let item = self.context.sharedContext.makeChatMessagePreviewItem( + context: self.context, + messages: [message], + theme: presentationData.theme, + strings: presentationData.strings, + wallpaper: presentationData.chatWallpaper, + fontSize: presentationData.chatFontSize, + chatBubbleCorners: presentationData.chatBubbleCorners, + dateTimeFormat: presentationData.dateTimeFormat, + nameOrder: presentationData.nameDisplayOrder, + forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: .Local), + tapMessage: nil, + clickThroughMessage: nil, + backgroundNode: self.wallpaperBackgroundNode, + availableReactions: nil, + accountPeer: nil, + isCentered: false, + isPreview: true, + isStandalone: true + ) + let items = [item] + + let params = ListViewItemLayoutParams(width: containerSize.width, leftInset: 0.0, rightInset: 0.0, availableHeight: containerSize.height) + if let messageNodes = self.messageNodes { + for i in 0 ..< items.count { + let itemNode = messageNodes[i] + items[i].updateNode(async: { $0() }, node: { + return itemNode + }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in + let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: containerSize.width, height: layout.size.height)) + + itemNode.contentSize = layout.contentSize + itemNode.insets = layout.insets + itemNode.frame = nodeFrame + itemNode.isUserInteractionEnabled = false + + apply(ListViewItemApply(isOnScreen: true)) + }) + } + } else { + var messageNodes: [ListViewItemNode] = [] + for i in 0 ..< items.count { + var itemNode: ListViewItemNode? + items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in + itemNode = node + apply().1(ListViewItemApply(isOnScreen: true)) + }) + itemNode!.isUserInteractionEnabled = false + messageNodes.append(itemNode!) + self.messagesContainer.addSubview(itemNode!.view) + } + self.messageNodes = messageNodes + } + + guard let messageNode = self.messageNodes?.first as? ChatMessageItemView else { + return CGSize(width: 10.0, height: 10.0) + } + let contentFrame = messageNode.contentFrame() + + self.messagesContainer.frame = CGRect(origin: CGPoint(x: 6.0, y: 3.0), size: CGSize(width: contentFrame.width, height: contentFrame.height)) + + return CGSize(width: contentFrame.width - 4.0, height: contentFrame.height + 2.0) + } +} + +public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMessageContextScreenMediaPreview { + private let context: AccountContext + private let presentationData: PresentationData + private let wallpaperBackgroundNode: WallpaperBackgroundNode? + private let messages: [Message] + + private var chatPresentationData: ChatPresentationData? + + private var messageNodes: [EngineMessage.Id: ChatMessageMediaBubbleContentNode] = [:] + private let messagesContainer: UIView + + public var isReady: Signal { + return .single(true) + } + + public var view: UIView { + return self + } + + public var globalClippingRect: CGRect? { + return nil + } + + public var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { + return .media + } + + public init(context: AccountContext, presentationData: PresentationData, wallpaperBackgroundNode: WallpaperBackgroundNode?, messages: [EngineMessage]) { + self.context = context + self.presentationData = presentationData + self.wallpaperBackgroundNode = wallpaperBackgroundNode + self.messages = messages.map { message in + return message._asMessage().withUpdatedTimestamp(0) + } + + self.messagesContainer = UIView() + + super.init(frame: CGRect()) + + self.addSubview(self.messagesContainer) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + public func animateIn(transition: Transition) { + transition.animateAlpha(view: self.messagesContainer, from: 0.0, to: 1.0) + transition.animateScale(view: self.messagesContainer, from: 0.001, to: 1.0) + } + + public func animateOut(transition: Transition) { + transition.setAlpha(view: self.messagesContainer, alpha: 0.0) + transition.setScale(view: self.messagesContainer, scale: 0.001) + } + + public func animateOutOnSend(transition: Transition) { + transition.setAlpha(view: self.messagesContainer, alpha: 0.0) + } + + public func update(containerSize: CGSize, transition: Transition) -> CGSize { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + + let chatPresentationData: ChatPresentationData + if let current = self.chatPresentationData { + chatPresentationData = current + } else { + chatPresentationData = ChatPresentationData( + theme: ChatPresentationThemeData( + theme: presentationData.theme, + wallpaper: presentationData.chatWallpaper + ), + fontSize: presentationData.chatFontSize, + strings: presentationData.strings, + dateTimeFormat: presentationData.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, + disableAnimations: false, + largeEmoji: false, + chatBubbleCorners: presentationData.chatBubbleCorners + ) + self.chatPresentationData = chatPresentationData + } + + let controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in + return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _, _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in + }, updateMessageReaction: { _, _, _, _ in }, activateMessagePinch: { _ in + }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _, _ in }, navigateToMessageStandalone: { _ in + }, navigateToThreadMessage: { _, _, _ in + }, tapMessage: { _ in + }, clickThroughMessage: { + }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in + return false + }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + }, presentController: { _, _ in + }, presentControllerInCurrent: { _, _ in + }, navigationController: { + return nil + }, chatControllerNode: { + return nil + }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in + }, canSetupReply: { _ in + return .none + }, canSendMessages: { + return false + }, navigateToFirstDateMessage: { _, _ in + }, requestRedeliveryOfFailedMessages: { _ in + }, addContact: { _ in + }, rateCall: { _, _, _ in + }, requestSelectMessagePollOptions: { _, _ in + }, requestOpenMessagePollResults: { _, _ in + }, openAppStorePage: { + }, displayMessageTooltip: { _, _, _, _, _ in + }, seekToTimecode: { _, _, _ in + }, scheduleCurrentMessage: { _ in + }, sendScheduledMessagesNow: { _ in + }, editScheduledMessagesTime: { _ in + }, performTextSelectionAction: { _, _, _, _ in + }, displayImportedMessageTooltip: { _ in + }, displaySwipeToReplyHint: { + }, dismissReplyMarkupMessage: { _ in + }, openMessagePollResults: { _, _ in + }, openPollCreation: { _ in + }, displayPollSolution: { _, _ in + }, displayPsa: { _, _ in + }, displayDiceTooltip: { _ in + }, animateDiceSuccess: { _, _ in + }, displayPremiumStickerTooltip: { _, _ in + }, displayEmojiPackTooltip: { _, _ in + }, openPeerContextMenu: { _, _, _, _, _ in + }, openMessageReplies: { _, _, _ in + }, openReplyThreadOriginalMessage: { _ in + }, openMessageStats: { _ in + }, editMessageMedia: { _, _ in + }, copyText: { _ in + }, displayUndo: { _ in + }, isAnimatingMessage: { _ in + return false + }, getMessageTransitionNode: { + return nil + }, updateChoosingSticker: { _ in + }, commitEmojiInteraction: { _, _, _, _ in + }, openLargeEmojiInfo: { _, _, _ in + }, openJoinLink: { _ in + }, openWebView: { _, _, _, _ in + }, activateAdAction: { _, _ in + }, openRequestedPeerSelection: { _, _, _, _ in + }, saveMediaToFiles: { _ in + }, openNoAdsDemo: { + }, openAdsInfo: { + }, displayGiveawayParticipationStatus: { _ in + }, openPremiumStatusInfo: { _, _, _, _ in + }, openRecommendedChannelContextMenu: { _, _, _ in + }, openGroupBoostInfo: { _, _ in + }, openStickerEditor: { + }, openPhoneContextMenu: { _ in + }, openAgeRestrictedMessageMedia: { _, _ in + }, playMessageEffect: { _ in + }, editMessageFactCheck: { _ in + }, requestMessageUpdate: { _, _ in + }, cancelInteractiveKeyboardGestures: { + }, dismissTextInput: { + }, scrollToMessageId: { _ in + }, navigateToStory: { _, _ in + }, attemptedNavigationToPrivateQuote: { _ in + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, + pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: self.context, backgroundNode: self.wallpaperBackgroundNode)) + + let associatedData = ChatMessageItemAssociatedData( + automaticDownloadPeerType: .channel, + automaticDownloadPeerId: nil, + automaticDownloadNetworkType: .cellular, + isRecentActions: false, + availableReactions: nil, + availableMessageEffects: nil, + savedMessageTags: nil, + defaultReaction: nil, + isPremium: false, + accountPeer: nil + ) + + let entryAttributes = ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil) + + let items = self.messages.map { message -> ChatMessageBubbleContentItem in + return ChatMessageBubbleContentItem( + context: self.context, + controllerInteraction: controllerInteraction, + message: message, + topMessage: message, + read: true, + chatLocation: .peer(id: self.context.account.peerId), + presentationData: chatPresentationData, + associatedData: associatedData, + attributes: entryAttributes, + isItemPinned: false, + isItemEdited: false + ) + } + + let layoutConstants = chatMessageItemLayoutConstants( + (ChatMessageItemLayoutConstants.compact, ChatMessageItemLayoutConstants.regular), + params: ListViewItemLayoutParams( + width: containerSize.width, + leftInset: 0.0, + rightInset: 0.0, + availableHeight: 10000.0 + ), + presentationData: chatPresentationData + ) + + if items.count == 1 { + let messageNode: ChatMessageMediaBubbleContentNode + if let current = self.messageNodes[items[0].message.id] { + messageNode = current + } else { + messageNode = ChatMessageMediaBubbleContentNode() + self.messageNodes[items[0].message.id] = messageNode + self.messagesContainer.addSubview(messageNode.view) + } + + let makeMessageLayout = messageNode.asyncLayoutContent() + + let (_, _, _, continueMessageLayout) = makeMessageLayout( + items[0], + layoutConstants, + ChatMessageBubblePreparePosition.linear( + top: ChatMessageBubbleRelativePosition.None(.None(.None)), + bottom: ChatMessageBubbleRelativePosition.None(.None(.None)) + ), + nil, + CGSize(width: containerSize.width, height: 10000.0), + 0.0 + ) + + let (finalizedWidth, finalizeMessageLayout) = continueMessageLayout( + CGSize(width: containerSize.width, height: 10000.0), + ChatMessageBubbleContentPosition.linear( + top: ChatMessageBubbleRelativePosition.None(.None(.None)), + bottom: ChatMessageBubbleRelativePosition.None(.None(.None)) + ) + ) + let _ = finalizedWidth + + let (finalizedSize, apply) = finalizeMessageLayout(finalizedWidth) + apply(.None, true, nil) + + let contentFrameInset = UIEdgeInsets(top: -2.0, left: -2.0, bottom: -2.0, right: -2.0) + + let contentFrame = CGRect(origin: CGPoint(x: contentFrameInset.left, y: contentFrameInset.top), size: finalizedSize) + messageNode.frame = contentFrame + + let messagesContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentFrame.width + contentFrameInset.left + contentFrameInset.right, height: contentFrame.height + contentFrameInset.top + contentFrameInset.bottom)) + + self.messagesContainer.frame = messagesContainerFrame + return messagesContainerFrame.size + } else { + var contentPropertiesAndLayouts: [( + CGSize?, + ChatMessageBubbleContentProperties, + ChatMessageBubblePreparePosition, + (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)), + ChatMessageMediaBubbleContentNode + )] = [] + + let bottomPosition: ChatMessageBubbleRelativePosition = ChatMessageBubbleRelativePosition.None(.None(.None)) + + var firstNodeTopPosition: ChatMessageBubbleRelativePosition = ChatMessageBubbleRelativePosition.None(.None(.None)) + if "".isEmpty { + firstNodeTopPosition = ChatMessageBubbleRelativePosition.None(.None(.None)) + } + var lastNodeTopPosition = ChatMessageBubbleRelativePosition.None(.None(.None)) + if "".isEmpty { + lastNodeTopPosition = ChatMessageBubbleRelativePosition.None(.None(.None)) + } + + let contentFrameInset = UIEdgeInsets(top: -2.0, left: -2.0, bottom: -2.0, right: -2.0) + + var maximumNodeWidth: CGFloat = containerSize.width + contentFrameInset.left + contentFrameInset.right + let maximumContentWidth = maximumNodeWidth + + for i in 0 ..< items.count { + let messageNode: ChatMessageMediaBubbleContentNode + if let current = self.messageNodes[items[i].message.id] { + messageNode = current + } else { + messageNode = ChatMessageMediaBubbleContentNode() + self.messageNodes[items[i].message.id] = messageNode + self.messagesContainer.addSubview(messageNode.view) + } + + let prepareLayout = messageNode.asyncLayoutContent() + + let prepareContentPosition: ChatMessageBubblePreparePosition = .mosaic(top: .None(.None(.Incoming)), bottom: i == (items.count - 1 - 1) ? bottomPosition : .None(.None(.Incoming))) + + let (properties, unboundSize, maxNodeWidth, nodeLayout) = prepareLayout(items[i], layoutConstants, prepareContentPosition, nil, CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude), 0.0) + maximumNodeWidth = min(maximumNodeWidth, maxNodeWidth) + + contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, nodeLayout, messageNode)) + } + + let maxSize = layoutConstants.image.maxDimensions.fittedToWidthOrSmaller(maximumContentWidth) + let (innerFramesAndPositions, innerSize) = chatMessageBubbleMosaicLayout(maxSize: maxSize, itemSizes: contentPropertiesAndLayouts.map { item in + guard let size = item.0, size.width > 0.0, size.height > 0 else { + return CGSize(width: 256.0, height: 256.0) + } + return size + }) + + let framesAndPositions = innerFramesAndPositions + + let size = CGSize(width: innerSize.width, height: innerSize.height) + + var contentNodePropertiesAndFinalize: [( + ChatMessageBubbleContentProperties, + ChatMessageBubbleContentPosition?, + (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void), + ChatMessageMediaBubbleContentNode + )] = [] + + var maxContentWidth = 0.0 + for i in 0 ..< contentPropertiesAndLayouts.count { + let (_, contentNodeProperties, _, contentNodeLayout, messageNode) = contentPropertiesAndLayouts[i] + + let mosaicIndex = i + + let position = framesAndPositions[mosaicIndex].1 + + let topLeft: ChatMessageBubbleContentMosaicNeighbor + let topRight: ChatMessageBubbleContentMosaicNeighbor + let bottomLeft: ChatMessageBubbleContentMosaicNeighbor + let bottomRight: ChatMessageBubbleContentMosaicNeighbor + + switch firstNodeTopPosition { + case .Neighbour: + topLeft = .merged + topRight = .merged + case .BubbleNeighbour: + topLeft = .mergedBubble + topRight = .mergedBubble + case let .None(status): + if position.contains(.top) && position.contains(.left) { + switch status { + case .Left, .Both: + topLeft = .mergedBubble + case .Right: + topLeft = .none(tail: false) + case .None: + topLeft = .none(tail: false) + } + } else { + topLeft = .merged + } + + if position.contains(.top) && position.contains(.right) { + switch status { + case .Left: + topRight = .none(tail: false) + case .Right, .Both: + topRight = .mergedBubble + case .None: + topRight = .none(tail: false) + } + } else { + topRight = .merged + } + } + + let lastMosaicBottomPosition: ChatMessageBubbleRelativePosition = lastNodeTopPosition + + if position.contains(.bottom), case .Neighbour = lastMosaicBottomPosition { + bottomLeft = .merged + bottomRight = .merged + } else { + let switchValue = lastNodeTopPosition + + switch switchValue { + case .Neighbour: + bottomLeft = .merged + bottomRight = .merged + case .BubbleNeighbour: + bottomLeft = .mergedBubble + bottomRight = .mergedBubble + case let .None(status): + if position.contains(.bottom) && position.contains(.left) { + switch status { + case .Left, .Both: + bottomLeft = .mergedBubble + case .Right: + bottomLeft = .none(tail: false) + case let .None(tailStatus): + if case .Incoming = tailStatus { + bottomLeft = .none(tail: true) + } else { + bottomLeft = .none(tail: false) + } + } + } else { + bottomLeft = .merged + } + + if position.contains(.bottom) && position.contains(.right) { + switch status { + case .Left: + bottomRight = .none(tail: false) + case .Right, .Both: + bottomRight = .mergedBubble + case let .None(tailStatus): + if case .Outgoing = tailStatus { + bottomRight = .none(tail: true) + } else { + bottomRight = .none(tail: false) + } + } + } else { + bottomRight = .merged + } + } + } + + let (_, contentNodeFinalize) = contentNodeLayout(framesAndPositions[mosaicIndex].0.size, .mosaic(position: ChatMessageBubbleContentMosaicPosition(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight), wide: position.isWide)) + + contentNodePropertiesAndFinalize.append((contentNodeProperties, nil, contentNodeFinalize, messageNode)) + + maxContentWidth = max(maxContentWidth, size.width) + } + + for i in 0 ..< contentNodePropertiesAndFinalize.count { + let (_, _, finalize, messageNode) = contentNodePropertiesAndFinalize[i] + + let mosaicIndex = i + + let (_, apply) = finalize(maxContentWidth) + let contentNodeFrame = framesAndPositions[mosaicIndex].0.offsetBy(dx: 0.0, dy: 0.0) + apply(.None, true, nil) + + messageNode.frame = contentNodeFrame + } + + let messagesContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) + + self.messagesContainer.frame = messagesContainerFrame + return messagesContainerFrame.size + } + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/BUILD b/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/BUILD index c70d3954266..6fbc927a287 100644 --- a/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/Sources/ChatShareMessageTagView.swift b/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/Sources/ChatShareMessageTagView.swift index 958a9dad1d7..f2e78e7f465 100644 --- a/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/Sources/ChatShareMessageTagView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/Sources/ChatShareMessageTagView.swift @@ -31,7 +31,7 @@ public final class ChatShareMessageTagView: UIView, UndoOverlayControllerAdditio context: context, animationCache: context.animationCache, presentationData: presentationData, - items: reactionItems.map(ReactionContextItem.reaction), + items: reactionItems.map { ReactionContextItem.reaction(item: $0, icon: .none) }, selectedItems: Set(), title: isSingleMessage ? presentationData.strings.Chat_ForwardToSavedMessageTagSelectionTitle : presentationData.strings.Chat_ForwardToSavedMessagesTagSelectionTitle, reactionsLocked: false, diff --git a/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/BUILD b/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/BUILD index 0627218e50b..6b317b76f19 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ ], diff --git a/submodules/TelegramUI/Components/Chat/EditableTokenListNode/BUILD b/submodules/TelegramUI/Components/Chat/EditableTokenListNode/BUILD index cb19c19e8ed..ec8ae04dce3 100644 --- a/submodules/TelegramUI/Components/Chat/EditableTokenListNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/EditableTokenListNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD new file mode 100644 index 00000000000..15cf263a28f --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD @@ -0,0 +1,30 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "FactCheckAlertController", + module_name = "FactCheckAlertController", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ComponentFlow", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/TextFieldComponent", + "//submodules/TextFormat", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift new file mode 100644 index 00000000000..8591c9993ab --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift @@ -0,0 +1,411 @@ +import Foundation +import UIKit +import SwiftSignalKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import AccountContext +import ComponentFlow +import MultilineTextComponent +import BalancedTextComponent +import TextFieldComponent +import ComponentDisplayAdapters +import TextFormat + +private final class FactCheckAlertContentNode: AlertContentNode { + private let context: AccountContext + private var theme: AlertControllerTheme + private var presentationTheme: PresentationTheme + private let strings: PresentationStrings + private let text: String + private let initialValue: String + + private let titleView = ComponentView() + + private let state = ComponentState() + + private let inputBackgroundNode = ASImageNode() + private let inputField = ComponentView() + private let inputFieldExternalState = TextFieldComponent.ExternalState() + private let inputPlaceholderView = ComponentView() + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private let disposable = MetaDisposable() + + private var validLayout: CGSize? + + private let hapticFeedback = HapticFeedback() + + var present: (ViewController) -> () = { _ in } + + var complete: (() -> Void)? { + didSet { +// self.inputFieldNode.complete = self.complete + } + } + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + init(context: AccountContext, theme: AlertControllerTheme, presentationTheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, value: String, entities: [MessageTextEntity], characterLimit: Int) { + self.context = context + self.theme = theme + self.presentationTheme = presentationTheme + self.strings = strings + self.text = text + self.initialValue = value + + if !value.isEmpty { + self.inputFieldExternalState.initialText = chatInputStateStringWithAppliedEntities(value, entities: entities) + } + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.inputBackgroundNode.displaysAsynchronously = false + self.inputBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: presentationTheme.actionSheet.inputHollowBackgroundColor, strokeColor: presentationTheme.actionSheet.inputBorderColor, strokeWidth: UIScreenPixel) + + self.addSubnode(self.actionNodesSeparator) + self.addSubnode(self.inputBackgroundNode) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + self.actionNodes.last?.actionEnabled = true + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + self.updateTheme(theme) + + self.state._updated = { [weak self] transition, _ in + guard let self, let _ = self.validLayout else { + return + } + self.requestLayout?(transition.containedViewLayoutTransition) + } + } + + deinit { + self.disposable.dispose() + } + + var textAndEntities: (String, [MessageTextEntity]) { + let text = self.inputFieldExternalState.text.string + let entities = generateChatInputTextEntities(self.inputFieldExternalState.text) + return (text, entities) + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.theme = theme + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width, 270.0) + let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) + + let hadValidLayout = self.validLayout != nil + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 16.0) + let spacing: CGFloat = 5.0 + + + let titleSize = self.titleView.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: self.text, font: Font.semibold(17.0), textColor: self.theme.primaryColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0 + )), + environment: {}, + containerSize: CGSize(width: measureSize.width, height: 1000.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) * 0.5), y: origin.y), size: titleSize) + if let titleComponentView = self.titleView.view { + if titleComponentView.superview == nil { + self.view.addSubview(titleComponentView) + } + titleComponentView.frame = titleFrame + } + origin.y += titleSize.height + 17.0 + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = TextAlertContentActionLayout.horizontal + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) + + var contentWidth = max(titleSize.width, minActionsWidth) + contentWidth = max(contentWidth, 234.0) + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + let resultWidth = contentWidth + insets.left + insets.right + + let inputInset: CGFloat = 16.0 + let inputWidth = resultWidth - inputInset * 2.0 + + var characterLimit: Int = 1024 + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["factcheck_length_limit"] as? Double { + characterLimit = Int(value) + } + + let inputFieldSize = self.inputField.update( + transition: .immediate, + component: AnyComponent(TextFieldComponent( + context: self.context, + theme: self.presentationTheme, + strings: self.strings, + externalState: self.inputFieldExternalState, + fontSize: 14.0, + textColor: self.presentationTheme.actionSheet.inputTextColor, + accentColor: self.presentationTheme.actionSheet.controlAccentColor, + insets: UIEdgeInsets(top: 8.0, left: 2.0, bottom: 8.0, right: 2.0), + hideKeyboard: false, + customInputView: nil, + resetText: nil, + isOneLineWhenUnfocused: false, + characterLimit: characterLimit, + emptyLineHandling: .oneConsecutive, + formatMenuAvailability: .available([.bold, .italic, .link]), + returnKeyType: .default, + lockedFormatAction: { + }, + present: { [weak self] c in + self?.present(c) + }, + paste: { _ in + }, + returnKeyAction: nil, + backspaceKeyAction: nil + )), + environment: {}, + containerSize: CGSize(width: inputWidth, height: 270.0) + ) + self.inputField.parentState = self.state + let inputFieldFrame = CGRect(origin: CGPoint(x: inputInset, y: origin.y), size: inputFieldSize) + if let inputFieldView = self.inputField.view as? TextFieldComponent.View { + if inputFieldView.superview == nil { + self.view.addSubview(inputFieldView) + } + transition.updateFrame(view: inputFieldView, frame: inputFieldFrame) + transition.updateFrame(node: self.inputBackgroundNode, frame: inputFieldFrame) + + if !hadValidLayout { + inputFieldView.activateInput() + } + } + + let inputPlaceholderSize = self.inputPlaceholderView.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString( + string: self.strings.FactCheck_Placeholder, + font: Font.regular(14.0), + textColor: self.presentationTheme.actionSheet.inputPlaceholderColor + ))) + ), + environment: {}, + containerSize: CGSize(width: inputWidth, height: 240.0) + ) + let inputPlaceholderFrame = CGRect(origin: CGPoint(x: inputInset + 10.0, y: floorToScreenPixels(inputFieldFrame.midY - inputPlaceholderSize.height / 2.0)), size: inputPlaceholderSize) + if let inputPlaceholderView = self.inputPlaceholderView.view { + if inputPlaceholderView.superview == nil { + inputPlaceholderView.isUserInteractionEnabled = false + self.view.addSubview(inputPlaceholderView) + } + inputPlaceholderView.frame = inputPlaceholderFrame + inputPlaceholderView.isHidden = self.inputFieldExternalState.hasText + } + + if let lastActionNode = self.actionNodes.last { + if self.initialValue.isEmpty { + lastActionNode.actionEnabled = self.inputFieldExternalState.hasText + } else { + if self.inputFieldExternalState.hasText { + lastActionNode.action = TextAlertAction( + type: .defaultAction, + title: self.strings.Common_Done, + action: lastActionNode.action.action + ) + } else { + lastActionNode.action = TextAlertAction( + type: .defaultDestructiveAction, + title: self.strings.FactCheck_Remove, + action: lastActionNode.action.action + ) + } + } + } + + let resultSize = CGSize(width: resultWidth, height: titleSize.height + spacing + inputFieldSize.height + 17.0 + actionsHeight + insets.top + insets.bottom) + + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + return resultSize + } + + func deactivateInput() { + if let inputFieldView = self.inputField.view as? TextFieldComponent.View { + inputFieldView.deactivateInput() + } + } + + func animateError() { + if let inputFieldView = self.inputField.view as? TextFieldComponent.View { + inputFieldView.layer.addShakeAnimation() + } + + self.hapticFeedback.error() + } +} + +public func factCheckAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, value: String, entities: [MessageTextEntity], characterLimit: Int = 1000, apply: @escaping (String, [MessageTextEntity]) -> Void) -> AlertController { + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + + var dismissImpl: ((Bool) -> Void)? + var applyImpl: (() -> Void)? + + let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissImpl?(true) + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: { + dismissImpl?(true) + applyImpl?() + })] + + let contentNode = FactCheckAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), presentationTheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: presentationData.strings.FactCheck_Title, value: value, entities: entities, characterLimit: characterLimit) + contentNode.complete = { + applyImpl?() + } + applyImpl = { [weak contentNode] in + guard let contentNode = contentNode else { + return + } + let (text, entities) = contentNode.textAndEntities + apply(text, entities) + } + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) + let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller] presentationData in + controller?.theme = AlertControllerTheme(presentationData: presentationData) + }) + controller.dismissed = { _ in + presentationDataDisposable.dispose() + } + dismissImpl = { [weak controller] animated in + contentNode.deactivateInput() + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + + contentNode.present = { [weak controller] c in + controller?.present(c, in: .window(.root)) + } + + return controller +} diff --git a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD index 0faa5484421..4271fcb164b 100644 --- a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode/BUILD b/submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode/BUILD index f36b5f7d006..a3f8d2d11db 100644 --- a/submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD index a06f08dcc1c..c9bcd19ce33 100644 --- a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/BUILD b/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/BUILD index 1328ab6234f..8d7ac0ab0c4 100644 --- a/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/MessageHaptics/BUILD b/submodules/TelegramUI/Components/Chat/MessageHaptics/BUILD index 499d48c1ab7..1125c3260f9 100644 --- a/submodules/TelegramUI/Components/Chat/MessageHaptics/BUILD +++ b/submodules/TelegramUI/Components/Chat/MessageHaptics/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/BUILD b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/BUILD index beec5261216..59d3800f195 100644 --- a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/BUILD +++ b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift index 225d40505c8..e1797506dc4 100644 --- a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift +++ b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift @@ -723,7 +723,7 @@ public final class MessageInlineBlockBackgroundView: UIView { displayProgress: params.displayProgress, animation: animation ) - animation.animator.updateFrame(layer: lineView.layer, frame: lineFrame, completion: nil) + animation.animator.updateFrame(layer: self.lineView.layer, frame: lineFrame, completion: nil) if params.pattern != nil { var maxIndex = 0 diff --git a/submodules/TelegramUI/Components/Chat/MessageQuoteComponent/BUILD b/submodules/TelegramUI/Components/Chat/MessageQuoteComponent/BUILD index 0145b88d216..181555e11b6 100644 --- a/submodules/TelegramUI/Components/Chat/MessageQuoteComponent/BUILD +++ b/submodules/TelegramUI/Components/Chat/MessageQuoteComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Chat/PollBubbleTimerNode/BUILD b/submodules/TelegramUI/Components/Chat/PollBubbleTimerNode/BUILD index 1cc5965699e..7925b518f74 100644 --- a/submodules/TelegramUI/Components/Chat/PollBubbleTimerNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/PollBubbleTimerNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/BUILD index d027a20de9a..4a4305e529e 100644 --- a/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD b/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD index 24344ea62dc..f86c72032ac 100644 --- a/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD +++ b/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ShimmeringLinkNode/BUILD b/submodules/TelegramUI/Components/Chat/ShimmeringLinkNode/BUILD index bdff7f82114..c3776176eac 100644 --- a/submodules/TelegramUI/Components/Chat/ShimmeringLinkNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ShimmeringLinkNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD b/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD index 554275c6a93..6e59d99ef68 100644 --- a/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD +++ b/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift b/submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift index 8bd9be9f671..080aeb97a62 100644 --- a/submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift +++ b/submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift @@ -419,3 +419,38 @@ public func topMessageReactions(context: AccountContext, message: Message, subPe return result } } + +public func effectMessageReactions(context: AccountContext) -> Signal<[ReactionItem], NoError> { + return context.engine.stickers.availableMessageEffects() + |> take(1) + |> map { availableMessageEffects -> [ReactionItem] in + guard let availableMessageEffects else { + return [] + } + + var result: [ReactionItem] = [] + var existingIds = Set() + + for messageEffect in availableMessageEffects.messageEffects { + if existingIds.contains(messageEffect.id) { + continue + } + existingIds.insert(messageEffect.id) + + let mainFile: TelegramMediaFile = messageEffect.effectSticker + + result.append(ReactionItem( + reaction: ReactionItem.Reaction(rawValue: .custom(messageEffect.id)), + appearAnimation: mainFile, + stillAnimation: mainFile, + listAnimation: mainFile, + largeListAnimation: mainFile, + applicationAnimation: nil, + largeApplicationAnimation: nil, + isCustom: true + )) + } + + return result + } +} diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD b/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD index 684e3ca458d..b37c7868138 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 2a394ccac8f..66d1984fa35 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -97,11 +97,13 @@ public struct NavigateToMessageParams { public var timestamp: Double? public var quote: Quote? public var progress: Promise? + public var forceNew: Bool - public init(timestamp: Double?, quote: Quote?, progress: Promise? = nil) { + public init(timestamp: Double?, quote: Quote?, progress: Promise? = nil, forceNew: Bool = false) { self.timestamp = timestamp self.quote = quote self.progress = progress + self.forceNew = forceNew } } @@ -115,6 +117,14 @@ public struct OpenMessageParams { } } +public final class ChatSendMessageEffect { + public let id: Int64 + + public init(id: Int64) { + self.id = id + } +} + public final class ChatControllerInteraction: ChatControllerInteractionProtocol { public enum OpenPeerSource { case `default` @@ -144,6 +154,22 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol } } + public struct OpenPhone { + public var number: String + public var message: Message + public var contentNode: ContextExtractedContentContainingNode + public var messageNode: ASDisplayNode + public var progress: Promise? + + public init(number: String, message: Message, contentNode: ContextExtractedContentContainingNode, messageNode: ASDisplayNode, progress: Promise? = nil) { + self.number = number + self.message = message + self.contentNode = contentNode + self.messageNode = messageNode + self.progress = progress + } + } + public let openMessage: (Message, OpenMessageParams) -> Bool public let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void public let openPeerMention: (String, Promise?) -> Void @@ -158,7 +184,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let tapMessage: ((Message) -> Void)? public let clickThroughMessage: () -> Void public let toggleMessagesSelection: ([MessageId], Bool) -> Void - public let sendCurrentMessage: (Bool) -> Void + public let sendCurrentMessage: (Bool, ChatSendMessageEffect?) -> Void public let sendMessage: (String) -> Void public let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool public let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void @@ -197,9 +223,9 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let requestSelectMessagePollOptions: (MessageId, [Data]) -> Void public let requestOpenMessagePollResults: (MessageId, MediaId) -> Void public let openAppStorePage: () -> Void - public let displayMessageTooltip: (MessageId, String, ASDisplayNode?, CGRect?) -> Void + public let displayMessageTooltip: (MessageId, String, Bool, ASDisplayNode?, CGRect?) -> Void public let seekToTimecode: (Message, Double, Bool) -> Void - public let scheduleCurrentMessage: () -> Void + public let scheduleCurrentMessage: (ChatSendMessageActionSheetController.SendParameters?) -> Void public let sendScheduledMessagesNow: ([MessageId]) -> Void public let editScheduledMessagesTime: ([MessageId]) -> Void public let performTextSelectionAction: (Message?, Bool, NSAttributedString, TextSelectionAction) -> Void @@ -228,7 +254,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void public let openJoinLink: (String) -> Void public let openWebView: (String, String, Bool, ChatOpenWebViewSource) -> Void - public let activateAdAction: (EngineMessage.Id) -> Void + public let activateAdAction: (EngineMessage.Id, Promise?) -> Void public let openRequestedPeerSelection: (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32, Int32) -> Void public let saveMediaToFiles: (EngineMessage.Id) -> Void public let openNoAdsDemo: () -> Void @@ -238,6 +264,10 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let openRecommendedChannelContextMenu: (EnginePeer, UIView, ContextGesture?) -> Void public let openGroupBoostInfo: (EnginePeer.Id?, Int) -> Void public let openStickerEditor: () -> Void + public let openPhoneContextMenu: (OpenPhone) -> Void + public let openAgeRestrictedMessageMedia: (Message, @escaping () -> Void) -> Void + public let playMessageEffect: (Message) -> Void + public let editMessageFactCheck: (MessageId) -> Void public let requestMessageUpdate: (MessageId, Bool) -> Void public let cancelInteractiveKeyboardGestures: () -> Void @@ -267,6 +297,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public var recommendedChannelsOpenUp: Bool = false public var enableFullTranslucency: Bool = true public var chatIsRotated: Bool = true + public var canReadHistory: Bool = false public init( // MARK: Nicegram Translate @@ -286,7 +317,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, - sendCurrentMessage: @escaping (Bool) -> Void, + sendCurrentMessage: @escaping (Bool, ChatSendMessageEffect?) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool, sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void, @@ -325,9 +356,9 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, - displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, + displayMessageTooltip: @escaping (MessageId, String, Bool, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, - scheduleCurrentMessage: @escaping () -> Void, + scheduleCurrentMessage: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (Message?, Bool, NSAttributedString, TextSelectionAction) -> Void, @@ -356,7 +387,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void, openJoinLink: @escaping (String) -> Void, openWebView: @escaping (String, String, Bool, ChatOpenWebViewSource) -> Void, - activateAdAction: @escaping (EngineMessage.Id) -> Void, + activateAdAction: @escaping (EngineMessage.Id, Promise?) -> Void, openRequestedPeerSelection: @escaping (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32, Int32) -> Void, saveMediaToFiles: @escaping (EngineMessage.Id) -> Void, openNoAdsDemo: @escaping () -> Void, @@ -366,6 +397,10 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol openRecommendedChannelContextMenu: @escaping (EnginePeer, UIView, ContextGesture?) -> Void, openGroupBoostInfo: @escaping (EnginePeer.Id?, Int) -> Void, openStickerEditor: @escaping () -> Void, + openPhoneContextMenu: @escaping (OpenPhone) -> Void, + openAgeRestrictedMessageMedia: @escaping (Message, @escaping () -> Void) -> Void, + playMessageEffect: @escaping (Message) -> Void, + editMessageFactCheck: @escaping (MessageId) -> Void, requestMessageUpdate: @escaping (MessageId, Bool) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, dismissTextInput: @escaping () -> Void, @@ -474,6 +509,10 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol self.openRecommendedChannelContextMenu = openRecommendedChannelContextMenu self.openGroupBoostInfo = openGroupBoostInfo self.openStickerEditor = openStickerEditor + self.openPhoneContextMenu = openPhoneContextMenu + self.openAgeRestrictedMessageMedia = openAgeRestrictedMessageMedia + self.playMessageEffect = playMessageEffect + self.editMessageFactCheck = editMessageFactCheck self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD index 59374b81620..3cf76d0f3e0 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 18698ed0c8b..2fd1082fb97 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -208,6 +208,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { hasTrending: hasTrending, forceHasPremium: false, hasEdit: hasEdit, + hasAdd: hasEdit, subject: .chatStickers, hideBackground: hideBackground ) @@ -1405,7 +1406,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { parentNavigationController: interaction.getNavigationController(), sendSticker: { [weak interaction] fileReference, sourceView, sourceRect in return interaction?.sendSticker(fileReference, false, false, nil, false, sourceView, sourceRect, nil, []) ?? false - } + }, + actionPerformed: nil ) interaction.presentController(controller, nil) }) @@ -1834,6 +1836,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { switch customChatContents.kind { case .quickReplyMessageInput: break + case .hashTagSearch: + break case .businessLinkSetup: stickerContent = nil gifContent = nil @@ -2891,7 +2895,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { let controller = strongSelf.context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], isEditing: false, expandIfNeeded: false, parentNavigationController: interaction.navigationController(), sendSticker: { file, sourceView, sourceRect in sendSticker(file, false, false, nil, false, sourceView, sourceRect, nil) return true - }) + }, actionPerformed: nil) interaction.navigationController()?.view.window?.endEditing(true) interaction.presentController(controller, nil) diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/BUILD b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/BUILD index 0adf863528e..bb400b55570 100644 --- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/BUILD +++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/ChatInputNode/BUILD b/submodules/TelegramUI/Components/ChatInputNode/BUILD index 1b45fe19a37..3919f1f0923 100644 --- a/submodules/TelegramUI/Components/ChatInputNode/BUILD +++ b/submodules/TelegramUI/Components/ChatInputNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/ChatInputPanelContainer/BUILD b/submodules/TelegramUI/Components/ChatInputPanelContainer/BUILD index 3ae830cb24f..6ff53882853 100644 --- a/submodules/TelegramUI/Components/ChatInputPanelContainer/BUILD +++ b/submodules/TelegramUI/Components/ChatInputPanelContainer/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD b/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD index df40a14bb29..557547eaf72 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD @@ -12,7 +12,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/ChatListTitleView/BUILD b/submodules/TelegramUI/Components/ChatListTitleView/BUILD index e4441abff47..b7b0a51049b 100644 --- a/submodules/TelegramUI/Components/ChatListTitleView/BUILD +++ b/submodules/TelegramUI/Components/ChatListTitleView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD b/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD index f1c9f807dec..365a2111873 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/ChatSendButtonRadialStatusNode/BUILD b/submodules/TelegramUI/Components/ChatSendButtonRadialStatusNode/BUILD index 218ecac737f..a5c2ec5cf18 100644 --- a/submodules/TelegramUI/Components/ChatSendButtonRadialStatusNode/BUILD +++ b/submodules/TelegramUI/Components/ChatSendButtonRadialStatusNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/BUILD b/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/BUILD index 09ef65c475d..219c9149126 100644 --- a/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/BUILD +++ b/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/ChatTimerScreen/BUILD b/submodules/TelegramUI/Components/ChatTimerScreen/BUILD index a0769433332..c33204fdb62 100644 --- a/submodules/TelegramUI/Components/ChatTimerScreen/BUILD +++ b/submodules/TelegramUI/Components/ChatTimerScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/ChatTitleView/BUILD b/submodules/TelegramUI/Components/ChatTitleView/BUILD index 26fb6712c8d..3c65c7f5060 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/BUILD +++ b/submodules/TelegramUI/Components/ChatTitleView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/CompositeTextNode/BUILD b/submodules/TelegramUI/Components/CompositeTextNode/BUILD index cdba78a4dfc..7ca1c3adb79 100644 --- a/submodules/TelegramUI/Components/CompositeTextNode/BUILD +++ b/submodules/TelegramUI/Components/CompositeTextNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/ContextMenuScreen/BUILD b/submodules/TelegramUI/Components/ContextMenuScreen/BUILD index 4987cabf706..400e8bed87c 100644 --- a/submodules/TelegramUI/Components/ContextMenuScreen/BUILD +++ b/submodules/TelegramUI/Components/ContextMenuScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/ContextReferenceButtonComponent/BUILD b/submodules/TelegramUI/Components/ContextReferenceButtonComponent/BUILD index b41112ece64..42eab365eb3 100644 --- a/submodules/TelegramUI/Components/ContextReferenceButtonComponent/BUILD +++ b/submodules/TelegramUI/Components/ContextReferenceButtonComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/DustEffect/BUILD b/submodules/TelegramUI/Components/DustEffect/BUILD index 5444d4eeb17..2dd085f8cce 100644 --- a/submodules/TelegramUI/Components/DustEffect/BUILD +++ b/submodules/TelegramUI/Components/DustEffect/BUILD @@ -48,7 +48,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":DustEffectMetalSourcesBundle", diff --git a/submodules/TelegramUI/Components/DynamicCornerRadiusView/BUILD b/submodules/TelegramUI/Components/DynamicCornerRadiusView/BUILD index a6c83e6f0ee..ebb5bbc02f7 100644 --- a/submodules/TelegramUI/Components/DynamicCornerRadiusView/BUILD +++ b/submodules/TelegramUI/Components/DynamicCornerRadiusView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow" diff --git a/submodules/TelegramUI/Components/EditableChatTextNode/BUILD b/submodules/TelegramUI/Components/EditableChatTextNode/BUILD index 35be430855c..0733cfbc729 100644 --- a/submodules/TelegramUI/Components/EditableChatTextNode/BUILD +++ b/submodules/TelegramUI/Components/EditableChatTextNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/EmojiActionIconComponent/BUILD b/submodules/TelegramUI/Components/EmojiActionIconComponent/BUILD index abd044bb77b..33390a41226 100644 --- a/submodules/TelegramUI/Components/EmojiActionIconComponent/BUILD +++ b/submodules/TelegramUI/Components/EmojiActionIconComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/EmojiStatusComponent/BUILD b/submodules/TelegramUI/Components/EmojiStatusComponent/BUILD index ac13c220d97..c121d573821 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/BUILD +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD index bb7ae0a1d5b..6ad4584979b 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/BUILD b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/BUILD index 14b1bac8efe..1c14361a37d 100644 --- a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/BUILD +++ b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/BUILD b/submodules/TelegramUI/Components/EmojiTextAttachmentView/BUILD index 294e001b6f7..38e98092a86 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/BUILD +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift index 39c7d4f6c04..6613b4752b9 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift @@ -395,6 +395,9 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { self.updateTopicInfo(topicInfo: (id, info)) case let .nameColors(colors): self.updateNameColors(colors: colors) + case .stars: + self.updateStars() + self.updateTintColor() } } else if let file = file { self.updateFile(file: file, attemptSynchronousLoad: attemptSynchronousLoad) @@ -480,6 +483,8 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { if file.isCustomTemplateEmoji { customColor = self.dynamicColor } + } else if let emoji = self.arguments?.emoji, let custom = emoji.custom, case .stars = custom { + customColor = self.dynamicColor } if customColor != nil { @@ -574,6 +579,10 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { self.contents = image?.cgImage } + private func updateStars() { + self.contents = starImage?.cgImage + } + private func updateFile(file: TelegramMediaFile, attemptSynchronousLoad: Bool) { guard let arguments = self.arguments else { return @@ -833,3 +842,13 @@ public final class CustomEmojiContainerView: UIView { } } } + +private let starImage: UIImage? = { + generateImage(CGSize(width: 32.0, height: 32.0), contextGenerator: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + + if let image = generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white), let cgImage = image.cgImage { + context.draw(cgImage, in: CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 4.0), byTiling: false) + } + })?.withRenderingMode(.alwaysTemplate) +}() diff --git a/submodules/TelegramUI/Components/EmptyStateIndicatorComponent/BUILD b/submodules/TelegramUI/Components/EmptyStateIndicatorComponent/BUILD index dc8ed1d04cf..3c890d23856 100644 --- a/submodules/TelegramUI/Components/EmptyStateIndicatorComponent/BUILD +++ b/submodules/TelegramUI/Components/EmptyStateIndicatorComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/EntityKeyboard/BUILD b/submodules/TelegramUI/Components/EntityKeyboard/BUILD index a08c0ef49f8..b4b7a62ecf4 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/BUILD +++ b/submodules/TelegramUI/Components/EntityKeyboard/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", @@ -47,6 +47,7 @@ swift_library( "//submodules/rlottie:RLottieBinding", "//submodules/lottie-ios:Lottie", "//submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage", + "//submodules/TelegramUIPreferences", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift new file mode 100644 index 00000000000..19ee0562aeb --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift @@ -0,0 +1,477 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import MultiAnimationRenderer +import AnimationCache +import SwiftSignalKit +import TelegramCore +import AccountContext +import TelegramPresentationData +import EmojiTextAttachmentView +import EmojiStatusComponent + +final class EmojiKeyboardCloneItemLayer: SimpleLayer { +} + +public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { + public struct Key: Hashable { + var groupId: AnyHashable + var itemId: EmojiPagerContentComponent.ItemContent.Id + + public init( + groupId: AnyHashable, + itemId: EmojiPagerContentComponent.ItemContent.Id + ) { + self.groupId = groupId + self.itemId = itemId + } + } + + enum Badge: Equatable { + case premium + case locked + case featured + case text(String) + case customFile(TelegramMediaFile) + } + + public let item: EmojiPagerContentComponent.Item + private let context: AccountContext + + private var content: EmojiPagerContentComponent.ItemContent + private var theme: PresentationTheme? + + private let placeholderColor: UIColor + let pixelSize: CGSize + let pointSize: CGSize + private let size: CGSize + private var disposable: Disposable? + private var fetchDisposable: Disposable? + private var premiumBadgeView: PremiumBadgeView? + + private var iconLayer: SimpleLayer? + private var tintIconLayer: SimpleLayer? + + private(set) var tintContentLayer: SimpleLayer? + + private var badge: Badge? + private var validSize: CGSize? + + private var isInHierarchyValue: Bool = false + public var isVisibleForAnimations: Bool = false { + didSet { + if self.isVisibleForAnimations != oldValue { + self.updatePlayback() + } + } + } + public private(set) var displayPlaceholder: Bool = false + public let onUpdateDisplayPlaceholder: (Bool, Double) -> Void + + weak var cloneLayer: EmojiKeyboardCloneItemLayer? { + didSet { + if let cloneLayer = self.cloneLayer { + cloneLayer.contents = self.contents + } + } + } + + override public var contents: Any? { + didSet { + self.onContentsUpdate() + if let cloneLayer = self.cloneLayer { + cloneLayer.contents = self.contents + } + } + } + + override public var position: CGPoint { + get { + return super.position + } set(value) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.position = value + } + super.position = value + } + } + + override public var bounds: CGRect { + get { + return super.bounds + } set(value) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.bounds = value + } + super.bounds = value + } + } + + override public func add(_ animation: CAAnimation, forKey key: String?) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.add(animation, forKey: key) + } + + super.add(animation, forKey: key) + } + + override public func removeAllAnimations() { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.removeAllAnimations() + } + + super.removeAllAnimations() + } + + override public func removeAnimation(forKey: String) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.removeAnimation(forKey: forKey) + } + + super.removeAnimation(forKey: forKey) + } + + public var onContentsUpdate: () -> Void = {} + public var onLoop: () -> Void = {} + + public init( + item: EmojiPagerContentComponent.Item, + context: AccountContext, + attemptSynchronousLoad: Bool, + content: EmojiPagerContentComponent.ItemContent, + cache: AnimationCache, + renderer: MultiAnimationRenderer, + placeholderColor: UIColor, + blurredBadgeColor: UIColor, + accentIconColor: UIColor, + pointSize: CGSize, + onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void + ) { + self.item = item + self.context = context + self.content = content + self.placeholderColor = placeholderColor + self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder + + let scale = min(2.0, UIScreenScale) + let pixelSize = CGSize(width: pointSize.width * scale, height: pointSize.height * scale) + self.pixelSize = pixelSize + self.pointSize = pointSize + self.size = CGSize(width: pixelSize.width / scale, height: pixelSize.height / scale) + + super.init() + + switch content { + case let .animation(animationData): + let loadAnimation: () -> Void = { [weak self] in + guard let strongSelf = self else { + return + } + + strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, unique: false, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: pixelSize.width >= 120.0, customColor: animationData.isTemplate ? .white : nil)) + } + + if attemptSynchronousLoad { + if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize) { + self.updateDisplayPlaceholder(displayPlaceholder: true) + + self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true, customColor: animationData.isTemplate ? .white : nil), completion: { [weak self] success, isFinal in + if !isFinal { + if !success { + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + + strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) + } + } + return + } + + Queue.mainQueue().async { + loadAnimation() + + if !success { + guard let strongSelf = self else { + return + } + + strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) + } + } + }) + } else { + loadAnimation() + } + } else { + self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true, customColor: animationData.isTemplate ? .white : nil), completion: { [weak self] success, isFinal in + if !isFinal { + if !success { + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + + strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) + } + } + return + } + + Queue.mainQueue().async { + loadAnimation() + + if !success { + guard let strongSelf = self else { + return + } + + strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) + } + } + }) + } + case let .staticEmoji(staticEmoji): + let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let preScaleFactor: CGFloat = 1.0 + let scaledSize = CGSize(width: floor(size.width * preScaleFactor), height: floor(size.height * preScaleFactor)) + let scaleFactor = scaledSize.width / size.width + + context.scaleBy(x: 1.0 / scaleFactor, y: 1.0 / scaleFactor) + + let string = NSAttributedString(string: staticEmoji, font: Font.regular(floor(32.0 * scaleFactor)), textColor: .black) + let boundingRect = string.boundingRect(with: scaledSize, options: .usesLineFragmentOrigin, context: nil) + UIGraphicsPushContext(context) + string.draw(at: CGPoint(x: floorToScreenPixels((scaledSize.width - boundingRect.width) / 2.0 + boundingRect.minX), y: floorToScreenPixels((scaledSize.height - boundingRect.height) / 2.0 + boundingRect.minY))) + UIGraphicsPopContext() + }) + self.contents = image?.cgImage + case let .icon(icon): + let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + UIGraphicsPushContext(context) + + switch icon { + case .premiumStar: + if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: accentIconColor) { + let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) + image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) + } + case let .topic(title, color): + let colors = topicIconColors(for: color) + if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) { + let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) + image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) + } + case .stop: + if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/NoIcon"), color: .white) { + let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) + image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) + } + case .add: + break + } + + UIGraphicsPopContext() + })?.withRenderingMode(icon == .stop ? .alwaysTemplate : .alwaysOriginal) + self.contents = image?.cgImage + } + + if case .icon(.add) = content { + let tintContentLayer = SimpleLayer() + self.tintContentLayer = tintContentLayer + + let iconLayer = SimpleLayer() + self.iconLayer = iconLayer + self.addSublayer(iconLayer) + + let tintIconLayer = SimpleLayer() + self.tintIconLayer = tintIconLayer + tintContentLayer.addSublayer(tintIconLayer) + } + } + + override public init(layer: Any) { + guard let layer = layer as? EmojiKeyboardItemLayer else { + preconditionFailure() + } + + self.context = layer.context + self.item = layer.item + + self.content = layer.content + self.placeholderColor = layer.placeholderColor + self.size = layer.size + self.pixelSize = layer.pixelSize + self.pointSize = layer.pointSize + + self.onUpdateDisplayPlaceholder = { _, _ in } + + super.init(layer: layer) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.disposable?.dispose() + self.fetchDisposable?.dispose() + } + + public override func action(forKey event: String) -> CAAction? { + if event == kCAOnOrderIn { + self.isInHierarchyValue = true + } else if event == kCAOnOrderOut { + self.isInHierarchyValue = false + } + self.updatePlayback() + return nullAction + } + + func update( + content: EmojiPagerContentComponent.ItemContent, + theme: PresentationTheme + ) { + var themeUpdated = false + if self.theme !== theme { + self.theme = theme + themeUpdated = true + } + var contentUpdated = false + if self.content != content { + self.content = content + contentUpdated = true + } + + if themeUpdated || contentUpdated { + if case let .icon(icon) = content, case let .topic(title, color) = icon { + let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + UIGraphicsPushContext(context) + + let colors = topicIconColors(for: color) + if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) { + let imageSize = image.size + image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) + } + + UIGraphicsPopContext() + }) + self.contents = image?.cgImage + } else if case .icon(.add) = content { + guard let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer else { + return + } + func generateIcon(color: UIColor) -> UIImage? { + return generateImage(self.pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + UIGraphicsPushContext(context) + + context.setFillColor(color.withMultipliedAlpha(0.2).cgColor) + context.fillEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: 8.0, dy: 8.0)) + context.setFillColor(color.cgColor) + + let plusSize = CGSize(width: 4.5, height: 31.5) + context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height), cornerRadius: plusSize.width / 2.0).cgPath) + context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width), cornerRadius: plusSize.width / 2.0).cgPath) + context.fillPath() + + UIGraphicsPopContext() + }) + } + + let needsVibrancy = !theme.overallDarkAppearance + let color = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor + + iconLayer.contents = generateIcon(color: color)?.cgImage + tintIconLayer.contents = generateIcon(color: .white)?.cgImage + + tintIconLayer.isHidden = !needsVibrancy + } + } + } + + func update( + transition: Transition, + size: CGSize, + badge: Badge?, + blurredBadgeColor: UIColor, + blurredBadgeBackgroundColor: UIColor + ) { + if self.badge != badge || self.validSize != size { + self.badge = badge + self.validSize = size + + if let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer { + transition.setFrame(layer: iconLayer, frame: CGRect(origin: .zero, size: size)) + transition.setFrame(layer: tintIconLayer, frame: CGRect(origin: .zero, size: size)) + } + + if let badge = badge { + var badgeTransition = transition + let premiumBadgeView: PremiumBadgeView + if let current = self.premiumBadgeView { + premiumBadgeView = current + } else { + badgeTransition = .immediate + premiumBadgeView = PremiumBadgeView(context: self.context) + self.premiumBadgeView = premiumBadgeView + self.addSublayer(premiumBadgeView.layer) + } + + let badgeDiameter = min(16.0, floor(size.height * 0.5)) + let badgeSize = CGSize(width: badgeDiameter, height: badgeDiameter) + badgeTransition.setFrame(view: premiumBadgeView, frame: CGRect(origin: CGPoint(x: size.width - badgeSize.width, y: size.height - badgeSize.height), size: badgeSize)) + premiumBadgeView.update(transition: badgeTransition, badge: badge, backgroundColor: blurredBadgeColor, size: badgeSize) + + self.blurredRepresentationBackgroundColor = blurredBadgeBackgroundColor + self.blurredRepresentationTarget = premiumBadgeView.contentLayer + } else { + if let premiumBadgeView = self.premiumBadgeView { + self.premiumBadgeView = nil + premiumBadgeView.removeFromSuperview() + + self.blurredRepresentationBackgroundColor = nil + self.blurredRepresentationTarget = nil + } + } + } + } + + private func updatePlayback() { + let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations + + self.shouldBeAnimating = shouldBePlaying + } + + public override func updateDisplayPlaceholder(displayPlaceholder: Bool) { + if self.displayPlaceholder == displayPlaceholder { + return + } + + self.displayPlaceholder = displayPlaceholder + self.onUpdateDisplayPlaceholder(displayPlaceholder, 0.0) + } + + public override func transitionToContents(_ contents: AnyObject, didLoop: Bool) { + self.contents = contents + + if self.displayPlaceholder { + self.displayPlaceholder = false + self.onUpdateDisplayPlaceholder(false, 0.2) + self.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) + } + + if didLoop { + self.onLoop() + } + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 30d4ec87a1e..00c9b967c50 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -26,144 +26,6 @@ import EmojiStatusComponent import TelegramNotices import GenerateStickerPlaceholderImage -private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) -private let featuredBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeAdd"), color: .white) -private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) - - -private final class WarpView: UIView { - private final class WarpPartView: UIView { - let cloneView: PortalView - - init?(contentView: PortalSourceView) { - guard let cloneView = PortalView(matchPosition: false) else { - return nil - } - self.cloneView = cloneView - - super.init(frame: CGRect()) - - self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0) - - self.clipsToBounds = true - self.addSubview(cloneView.view) - contentView.addPortal(view: cloneView) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(containerSize: CGSize, rect: CGRect, transition: Transition) { - transition.setFrame(view: self.cloneView.view, frame: CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: CGSize(width: containerSize.width, height: containerSize.height))) - } - } - - let contentView: PortalSourceView - - private let clippingView: UIView - - private var warpViews: [WarpPartView] = [] - private let warpMaskContainer: UIView - private let warpMaskGradientLayer: SimpleGradientLayer - - override init(frame: CGRect) { - self.contentView = PortalSourceView() - self.clippingView = UIView() - - self.warpMaskContainer = UIView() - self.warpMaskGradientLayer = SimpleGradientLayer() - self.warpMaskContainer.layer.mask = self.warpMaskGradientLayer - - super.init(frame: frame) - - self.clippingView.addSubview(self.contentView) - - self.clippingView.clipsToBounds = true - self.addSubview(self.clippingView) - self.addSubview(self.warpMaskContainer) - - for _ in 0 ..< 8 { - if let warpView = WarpPartView(contentView: self.contentView) { - self.warpViews.append(warpView) - self.warpMaskContainer.addSubview(warpView) - } - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(size: CGSize, topInset: CGFloat, warpHeight: CGFloat, theme: PresentationTheme, transition: Transition) { - transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size)) - - let allItemsHeight = warpHeight * 0.5 - for i in 0 ..< self.warpViews.count { - let itemHeight = warpHeight / CGFloat(self.warpViews.count) - let itemFraction = CGFloat(i + 1) / CGFloat(self.warpViews.count) - let _ = itemHeight - - let da = CGFloat.pi * 0.5 / CGFloat(self.warpViews.count) - let alpha = CGFloat.pi * 0.5 - itemFraction * CGFloat.pi * 0.5 - let endPoint = CGPoint(x: cos(alpha), y: sin(alpha)) - let prevAngle = alpha + da - let prevPt = CGPoint(x: cos(prevAngle), y: sin(prevAngle)) - var angle: CGFloat - angle = -atan2(endPoint.y - prevPt.y, endPoint.x - prevPt.x) - - let itemLengthVector = CGPoint(x: endPoint.x - prevPt.x, y: endPoint.y - prevPt.y) - let itemLength = sqrt(itemLengthVector.x * itemLengthVector.x + itemLengthVector.y * itemLengthVector.y) * warpHeight * 0.5 - let _ = itemLength - - var transform: CATransform3D - transform = CATransform3DIdentity - transform.m34 = 1.0 / 240.0 - - transform = CATransform3DTranslate(transform, 0.0, prevPt.x * allItemsHeight, (1.0 - prevPt.y) * allItemsHeight) - transform = CATransform3DRotate(transform, angle, 1.0, 0.0, 0.0) - - let positionY = size.height - allItemsHeight + 4.0 + CGFloat(i) * itemLength - let rect = CGRect(origin: CGPoint(x: 0.0, y: positionY), size: CGSize(width: size.width, height: itemLength)) - transition.setPosition(view: self.warpViews[i], position: CGPoint(x: rect.midX, y: 4.0)) - transition.setBounds(view: self.warpViews[i], bounds: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: itemLength))) - transition.setTransform(view: self.warpViews[i], transform: transform) - self.warpViews[i].update(containerSize: size, rect: rect, transition: transition) - } - - let clippingTopInset: CGFloat = topInset - let frame = CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: CGSize(width: size.width, height: -clippingTopInset + size.height - 21.0)) - transition.setPosition(view: self.clippingView, position: frame.center) - transition.setBounds(view: self.clippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: frame.size)) - self.clippingView.clipsToBounds = true - - transition.setFrame(view: self.warpMaskContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - allItemsHeight), size: CGSize(width: size.width, height: allItemsHeight))) - - var locations: [NSNumber] = [] - var colors: [CGColor] = [] - let numStops = 6 - for i in 0 ..< numStops { - let step = CGFloat(i) / CGFloat(numStops - 1) - locations.append(step as NSNumber) - colors.append(UIColor.black.withAlphaComponent(1.0 - step * step).cgColor) - } - - let gradientHeight: CGFloat = 6.0 - self.warpMaskGradientLayer.startPoint = CGPoint(x: 0.0, y: (allItemsHeight - gradientHeight) / allItemsHeight) - self.warpMaskGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0) - - self.warpMaskGradientLayer.locations = locations - self.warpMaskGradientLayer.colors = colors - self.warpMaskGradientLayer.type = .axial - - transition.setFrame(layer: self.warpMaskGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: allItemsHeight))) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - return self.contentView.hitTest(point, with: event) - } -} - public struct EmojiComponentReactionItem: Equatable { public var reaction: MessageReaction.Reaction public var file: TelegramMediaFile @@ -207,2161 +69,56 @@ public final class EntityKeyboardAnimationData: Equatable { public init(id: Id, type: ItemType, resource: MediaResourceReference, dimensions: CGSize, immediateThumbnailData: Data?, isReaction: Bool, isTemplate: Bool) { self.id = id - self.type = type - self.resource = resource - self.dimensions = dimensions - self.immediateThumbnailData = immediateThumbnailData - self.isReaction = isReaction - self.isTemplate = isTemplate - } - - public convenience init(file: TelegramMediaFile, isReaction: Bool = false, partialReference: PartialMediaReference? = nil) { - let type: ItemType - if file.isVideoSticker || file.isVideoEmoji { - type = .video - } else if file.isAnimatedSticker { - type = .lottie - } else { - type = .still - } - let isTemplate = file.isCustomTemplateEmoji - - let resourceReference: MediaResourceReference - if let partialReference { - resourceReference = partialReference.mediaReference(file).resourceReference(file.resource) - } else { - resourceReference = .standalone(resource: file.resource) - } - self.init(id: .file(file.fileId), type: type, resource: resourceReference, dimensions: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), immediateThumbnailData: file.immediateThumbnailData, isReaction: isReaction, isTemplate: isTemplate) - } - - public static func ==(lhs: EntityKeyboardAnimationData, rhs: EntityKeyboardAnimationData) -> Bool { - if lhs === rhs { - return true - } - - if lhs.resource.resource.id != rhs.resource.resource.id { - return false - } - if lhs.dimensions != rhs.dimensions { - return false - } - if lhs.type != rhs.type { - return false - } - if lhs.immediateThumbnailData != rhs.immediateThumbnailData { - return false - } - if lhs.isReaction != rhs.isReaction { - return false - } - - return true - } -} - -public class PassthroughLayer: CALayer { - public var mirrorLayer: CALayer? - - override init() { - super.init() - } - - override init(layer: Any) { - super.init(layer: layer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public var position: CGPoint { - get { - return super.position - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.position = value - } - super.position = value - } - } - - override public var bounds: CGRect { - get { - return super.bounds - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.bounds = value - } - super.bounds = value - } - } - - override public var opacity: Float { - get { - return super.opacity - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.opacity = value - } - super.opacity = value - } - } - - override public var sublayerTransform: CATransform3D { - get { - return super.sublayerTransform - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.sublayerTransform = value - } - super.sublayerTransform = value - } - } - - override public var transform: CATransform3D { - get { - return super.transform - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.transform = value - } - super.transform = value - } - } - - override public func add(_ animation: CAAnimation, forKey key: String?) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.add(animation, forKey: key) - } - - super.add(animation, forKey: key) - } - - override public func removeAllAnimations() { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.removeAllAnimations() - } - - super.removeAllAnimations() - } - - override public func removeAnimation(forKey: String) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.removeAnimation(forKey: forKey) - } - - super.removeAnimation(forKey: forKey) - } -} - -open class PassthroughView: UIView { - override public static var layerClass: AnyClass { - return PassthroughLayer.self - } - - public let passthroughView: UIView - - override public init(frame: CGRect) { - self.passthroughView = UIView() - - super.init(frame: frame) - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.passthroughView.layer - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -private class PassthroughShapeLayer: CAShapeLayer { - var mirrorLayer: CAShapeLayer? - - override init() { - super.init() - } - - override init(layer: Any) { - super.init(layer: layer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override var position: CGPoint { - get { - return super.position - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.position = value - } - super.position = value - } - } - - override var bounds: CGRect { - get { - return super.bounds - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.bounds = value - } - super.bounds = value - } - } - - override var opacity: Float { - get { - return super.opacity - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.opacity = value - } - super.opacity = value - } - } - - override var sublayerTransform: CATransform3D { - get { - return super.sublayerTransform - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.sublayerTransform = value - } - super.sublayerTransform = value - } - } - - override var transform: CATransform3D { - get { - return super.transform - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.transform = value - } - super.transform = value - } - } - - override var path: CGPath? { - get { - return super.path - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.path = value - } - super.path = value - } - } - - override var fillColor: CGColor? { - get { - return super.fillColor - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.fillColor = value - } - super.fillColor = value - } - } - - override var fillRule: CAShapeLayerFillRule { - get { - return super.fillRule - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.fillRule = value - } - super.fillRule = value - } - } - - override var strokeColor: CGColor? { - get { - return super.strokeColor - } set(value) { - /*if let mirrorLayer = self.mirrorLayer { - mirrorLayer.strokeColor = value - }*/ - super.strokeColor = value - } - } - - override var strokeStart: CGFloat { - get { - return super.strokeStart - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.strokeStart = value - } - super.strokeStart = value - } - } - - override var strokeEnd: CGFloat { - get { - return super.strokeEnd - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.strokeEnd = value - } - super.strokeEnd = value - } - } - - override var lineWidth: CGFloat { - get { - return super.lineWidth - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.lineWidth = value - } - super.lineWidth = value - } - } - - override var miterLimit: CGFloat { - get { - return super.miterLimit - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.miterLimit = value - } - super.miterLimit = value - } - } - - override var lineCap: CAShapeLayerLineCap { - get { - return super.lineCap - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.lineCap = value - } - super.lineCap = value - } - } - - override var lineJoin: CAShapeLayerLineJoin { - get { - return super.lineJoin - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.lineJoin = value - } - super.lineJoin = value - } - } - - override var lineDashPhase: CGFloat { - get { - return super.lineDashPhase - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.lineDashPhase = value - } - super.lineDashPhase = value - } - } - - override var lineDashPattern: [NSNumber]? { - get { - return super.lineDashPattern - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.lineDashPattern = value - } - super.lineDashPattern = value - } - } - - override func add(_ animation: CAAnimation, forKey key: String?) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.add(animation, forKey: key) - } - - super.add(animation, forKey: key) - } - - override func removeAllAnimations() { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.removeAllAnimations() - } - - super.removeAllAnimations() - } - - override func removeAnimation(forKey: String) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.removeAnimation(forKey: forKey) - } - - super.removeAnimation(forKey: forKey) - } -} - -private final class PremiumBadgeView: UIView { - private var badge: EmojiPagerContentComponent.View.ItemLayer.Badge? - - let contentLayer: SimpleLayer - private let overlayColorLayer: SimpleLayer - private let iconLayer: SimpleLayer - - init() { - self.contentLayer = SimpleLayer() - self.contentLayer.contentsGravity = .resize - self.contentLayer.masksToBounds = true - - self.overlayColorLayer = SimpleLayer() - self.overlayColorLayer.masksToBounds = true - - self.iconLayer = SimpleLayer() - - super.init(frame: CGRect()) - - self.layer.addSublayer(self.contentLayer) - self.layer.addSublayer(self.overlayColorLayer) - self.layer.addSublayer(self.iconLayer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(transition: Transition, badge: EmojiPagerContentComponent.View.ItemLayer.Badge, backgroundColor: UIColor, size: CGSize) { - if self.badge != badge { - self.badge = badge - - switch badge { - case .premium: - self.iconLayer.contents = premiumBadgeIcon?.cgImage - case .featured: - self.iconLayer.contents = featuredBadgeIcon?.cgImage - case .locked: - self.iconLayer.contents = lockedBadgeIcon?.cgImage - } - } - - let iconInset: CGFloat - switch badge { - case .premium: - iconInset = 2.0 - case .featured: - iconInset = 0.0 - case .locked: - iconInset = 0.0 - } - - self.overlayColorLayer.backgroundColor = backgroundColor.cgColor - - transition.setFrame(layer: self.contentLayer, frame: CGRect(origin: CGPoint(), size: size)) - transition.setCornerRadius(layer: self.contentLayer, cornerRadius: min(size.width / 2.0, size.height / 2.0)) - - transition.setFrame(layer: self.overlayColorLayer, frame: CGRect(origin: CGPoint(), size: size)) - transition.setCornerRadius(layer: self.overlayColorLayer, cornerRadius: min(size.width / 2.0, size.height / 2.0)) - - transition.setFrame(layer: self.iconLayer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: iconInset, dy: iconInset)) - } -} - -private final class GroupHeaderActionButton: UIButton { - override static var layerClass: AnyClass { - return PassthroughLayer.self - } - - let tintContainerLayer: SimpleLayer - - private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? - private let backgroundLayer: SimpleLayer - private let tintBackgroundLayer: SimpleLayer - private let textLayer: SimpleLayer - private let tintTextLayer: SimpleLayer - private let pressed: () -> Void - - init(pressed: @escaping () -> Void) { - self.pressed = pressed - - self.tintContainerLayer = SimpleLayer() - - self.backgroundLayer = SimpleLayer() - self.backgroundLayer.masksToBounds = true - - self.tintBackgroundLayer = SimpleLayer() - self.tintBackgroundLayer.masksToBounds = true - - self.textLayer = SimpleLayer() - self.tintTextLayer = SimpleLayer() - - super.init(frame: CGRect()) - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer - - self.layer.addSublayer(self.backgroundLayer) - self.layer.addSublayer(self.textLayer) - - self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside) - - self.tintContainerLayer.addSublayer(self.tintBackgroundLayer) - self.tintContainerLayer.addSublayer(self.tintTextLayer) - } - - required init(coder: NSCoder) { - preconditionFailure() - } - - @objc private func onPressed() { - self.pressed() - } - - override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { - self.alpha = 0.6 - - return super.beginTracking(touch, with: event) - } - - override func endTracking(_ touch: UITouch?, with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.endTracking(touch, with: event) - } - - override func cancelTracking(with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.cancelTracking(with: event) - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.touchesCancelled(touches, with: event) - } - - func update(theme: PresentationTheme, title: String, compact: Bool) -> CGSize { - let textConstrainedWidth: CGFloat = 100.0 - - let needsVibrancy = !theme.overallDarkAppearance && compact - - let foregroundColor: UIColor - let backgroundColor: UIColor - - if compact { - foregroundColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor - backgroundColor = foregroundColor.withMultipliedAlpha(0.2) - } else { - foregroundColor = theme.list.itemCheckColors.foregroundColor - backgroundColor = theme.list.itemCheckColors.fillColor - } - - self.backgroundLayer.backgroundColor = backgroundColor.cgColor - self.tintBackgroundLayer.backgroundColor = UIColor.white.withAlphaComponent(0.2).cgColor - - self.tintContainerLayer.isHidden = !needsVibrancy - - let textSize: CGSize - if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == foregroundColor, currentTextLayout.constrainedWidth == textConstrainedWidth { - textSize = currentTextLayout.size - } else { - let font: UIFont = compact ? Font.medium(11.0) : Font.semibold(15.0) - let string = NSAttributedString(string: title.uppercased(), font: font, textColor: foregroundColor) - let tintString = NSAttributedString(string: title.uppercased(), font: font, textColor: .white) - let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) - self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - string.draw(in: stringBounds) - - UIGraphicsPopContext() - })?.cgImage - self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - tintString.draw(in: stringBounds) - - UIGraphicsPopContext() - })?.cgImage - self.currentTextLayout = (title, foregroundColor, textConstrainedWidth, textSize) - } - - let size = CGSize(width: textSize.width + (compact ? 6.0 : 16.0) * 2.0, height: compact ? 16.0 : 28.0) - - let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) - self.textLayer.frame = textFrame - self.tintTextLayer.frame = textFrame - - self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size) - self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0 - - self.tintBackgroundLayer.frame = self.backgroundLayer.frame - self.tintBackgroundLayer.cornerRadius = self.backgroundLayer.cornerRadius - - return size - } -} - -private final class GroupHeaderLayer: UIView { - override static var layerClass: AnyClass { - return PassthroughLayer.self - } - - private let actionPressed: () -> Void - private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void - - private let textLayer: SimpleLayer - private let tintTextLayer: SimpleLayer - - private var subtitleLayer: SimpleLayer? - private var tintSubtitleLayer: SimpleLayer? - private var lockIconLayer: SimpleLayer? - private var tintLockIconLayer: SimpleLayer? - private var badgeLayer: SimpleLayer? - private var tintBadgeLayer: SimpleLayer? - private(set) var clearIconLayer: SimpleLayer? - private var tintClearIconLayer: SimpleLayer? - private var separatorLayer: SimpleLayer? - private var tintSeparatorLayer: SimpleLayer? - private var actionButton: GroupHeaderActionButton? - - private var groupEmbeddedView: GroupEmbeddedView? - - private var theme: PresentationTheme? - - private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? - private var currentSubtitleLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? - - let tintContentLayer: SimpleLayer - - init(actionPressed: @escaping () -> Void, performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) { - self.actionPressed = actionPressed - self.performItemAction = performItemAction - - self.textLayer = SimpleLayer() - self.tintTextLayer = SimpleLayer() - - self.tintContentLayer = SimpleLayer() - - super.init(frame: CGRect()) - - self.layer.addSublayer(self.textLayer) - self.tintContentLayer.addSublayer(self.tintTextLayer) - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContentLayer - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update( - context: AccountContext, - theme: PresentationTheme, - forceNeedsVibrancy: Bool, - layoutType: EmojiPagerContentComponent.ItemLayoutType, - hasTopSeparator: Bool, - actionButtonTitle: String?, - actionButtonIsCompact: Bool, - title: String, - subtitle: String?, - badge: String?, - isPremiumLocked: Bool, - hasClear: Bool, - embeddedItems: [EmojiPagerContentComponent.Item]?, - isStickers: Bool, - constrainedSize: CGSize, - insets: UIEdgeInsets, - cache: AnimationCache, - renderer: MultiAnimationRenderer, - attemptSynchronousLoad: Bool - ) -> (size: CGSize, centralContentWidth: CGFloat) { - var themeUpdated = false - if self.theme !== theme { - self.theme = theme - themeUpdated = true - } - - let needsVibrancy = !theme.overallDarkAppearance || forceNeedsVibrancy - - let textOffsetY: CGFloat - if hasTopSeparator { - textOffsetY = 9.0 - } else { - textOffsetY = 0.0 - } - - let subtitleColor: UIColor - if theme.overallDarkAppearance && forceNeedsVibrancy { - subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.withMultipliedAlpha(0.2) - } else { - subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor - } - - let color: UIColor - let needsTintText: Bool - if subtitle != nil { - color = theme.chat.inputPanel.primaryTextColor - needsTintText = false - } else { - color = subtitleColor - needsTintText = true - } - - let titleHorizontalOffset: CGFloat - if isPremiumLocked { - titleHorizontalOffset = 10.0 + 2.0 - } else { - titleHorizontalOffset = 0.0 - } - - var actionButtonSize: CGSize? - if let actionButtonTitle = actionButtonTitle { - let actionButton: GroupHeaderActionButton - if let current = self.actionButton { - actionButton = current - } else { - actionButton = GroupHeaderActionButton(pressed: self.actionPressed) - self.actionButton = actionButton - self.addSubview(actionButton) - self.tintContentLayer.addSublayer(actionButton.tintContainerLayer) - } - - actionButtonSize = actionButton.update(theme: theme, title: actionButtonTitle, compact: actionButtonIsCompact) - } else { - if let actionButton = self.actionButton { - self.actionButton = nil - actionButton.removeFromSuperview() - } - } - - var clearSize: CGSize = .zero - var clearWidth: CGFloat = 0.0 - if hasClear { - var updateImage = themeUpdated - - let clearIconLayer: SimpleLayer - if let current = self.clearIconLayer { - clearIconLayer = current - } else { - updateImage = true - clearIconLayer = SimpleLayer() - self.clearIconLayer = clearIconLayer - self.layer.addSublayer(clearIconLayer) - } - let tintClearIconLayer: SimpleLayer - if let current = self.tintClearIconLayer { - tintClearIconLayer = current - } else { - updateImage = true - tintClearIconLayer = SimpleLayer() - self.tintClearIconLayer = tintClearIconLayer - self.tintContentLayer.addSublayer(tintClearIconLayer) - } - - tintClearIconLayer.isHidden = !needsVibrancy - - clearSize = clearIconLayer.bounds.size - if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: subtitleColor) { - clearSize = image.size - clearIconLayer.contents = image.cgImage - } - if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: .white) { - tintClearIconLayer.contents = image.cgImage - } - - tintClearIconLayer.frame = clearIconLayer.frame - clearWidth = 4.0 + clearSize.width - } else { - if let clearIconLayer = self.clearIconLayer { - self.clearIconLayer = nil - clearIconLayer.removeFromSuperlayer() - } - if let tintClearIconLayer = self.tintClearIconLayer { - self.tintClearIconLayer = nil - tintClearIconLayer.removeFromSuperlayer() - } - } - - var textConstrainedWidth = constrainedSize.width - titleHorizontalOffset - 10.0 - if let actionButtonSize = actionButtonSize { - if actionButtonIsCompact { - textConstrainedWidth -= actionButtonSize.width * 2.0 + 10.0 - } else { - textConstrainedWidth -= actionButtonSize.width + 10.0 - } - } - if clearWidth > 0.0 { - textConstrainedWidth -= clearWidth + 8.0 - } - - let textSize: CGSize - if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth { - textSize = currentTextLayout.size - } else { - let font: UIFont - let stringValue: String - if subtitle == nil { - font = Font.medium(13.0) - stringValue = title.uppercased() - } else { - font = Font.semibold(16.0) - stringValue = title - } - let string = NSAttributedString(string: stringValue, font: font, textColor: color) - let whiteString = NSAttributedString(string: stringValue, font: font, textColor: .white) - let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) - textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) - self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - //string.draw(in: stringBounds) - string.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) - - UIGraphicsPopContext() - })?.cgImage - self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - //whiteString.draw(in: stringBounds) - whiteString.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) - - UIGraphicsPopContext() - })?.cgImage - self.tintTextLayer.isHidden = !needsVibrancy - self.currentTextLayout = (title, color, textConstrainedWidth, textSize) - } - - var badgeSize: CGSize = .zero - if let badge { - func generateBadgeImage(color: UIColor) -> UIImage? { - let string = NSAttributedString(string: badge, font: Font.semibold(11.0), textColor: .white) - let stringBounds = string.boundingRect(with: CGSize(width: 120, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) - - let badgeSize = CGSize(width: stringBounds.width + 8.0, height: 16.0) - return generateImage(badgeSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(color.cgColor) - context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: badgeSize), cornerRadius: badgeSize.height / 2.0).cgPath) - context.fillPath() - - context.setBlendMode(.clear) - - UIGraphicsPushContext(context) - - string.draw(with: CGRect(origin: CGPoint(x: floorToScreenPixels((badgeSize.width - stringBounds.size.width) / 2.0), y: floorToScreenPixels((badgeSize.height - stringBounds.size.height) / 2.0)), size: stringBounds.size), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) - - UIGraphicsPopContext() - }) - } - - let badgeLayer: SimpleLayer - if let current = self.badgeLayer { - badgeLayer = current - } else { - badgeLayer = SimpleLayer() - self.badgeLayer = badgeLayer - self.layer.addSublayer(badgeLayer) - - if let image = generateBadgeImage(color: color.withMultipliedAlpha(0.66)) { - badgeLayer.contents = image.cgImage - badgeLayer.bounds = CGRect(origin: .zero, size: image.size) - } - } - badgeSize = badgeLayer.bounds.size - - let tintBadgeLayer: SimpleLayer - if let current = self.tintBadgeLayer { - tintBadgeLayer = current - } else { - tintBadgeLayer = SimpleLayer() - self.tintBadgeLayer = tintBadgeLayer - self.tintContentLayer.addSublayer(tintBadgeLayer) - - if let image = generateBadgeImage(color: .white) { - tintBadgeLayer.contents = image.cgImage - } - } - } else { - if let badgeLayer = self.badgeLayer { - self.badgeLayer = nil - badgeLayer.removeFromSuperlayer() - } - if let tintBadgeLayer = self.tintBadgeLayer { - self.tintBadgeLayer = nil - tintBadgeLayer.removeFromSuperlayer() - } - } - - let textFrame: CGRect - if subtitle == nil { - textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset + floor((constrainedSize.width - titleHorizontalOffset - (textSize.width + badgeSize.width)) / 2.0), y: textOffsetY), size: textSize) - } else { - textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset, y: textOffsetY), size: textSize) - } - self.textLayer.frame = textFrame - self.tintTextLayer.frame = textFrame - self.tintTextLayer.isHidden = !needsTintText - - if let badgeLayer = self.badgeLayer, let tintBadgeLayer = self.tintBadgeLayer { - badgeLayer.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 4.0, y: 0.0), size: badgeLayer.frame.size) - tintBadgeLayer.frame = badgeLayer.frame - } - - if isPremiumLocked { - let lockIconLayer: SimpleLayer - if let current = self.lockIconLayer { - lockIconLayer = current - } else { - lockIconLayer = SimpleLayer() - self.lockIconLayer = lockIconLayer - self.layer.addSublayer(lockIconLayer) - } - if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: color) { - let imageSize = image.size - lockIconLayer.contents = image.cgImage - lockIconLayer.frame = CGRect(origin: CGPoint(x: textFrame.minX - imageSize.width - 3.0, y: 2.0 + UIScreenPixel), size: imageSize) - } else { - lockIconLayer.contents = nil - } - - let tintLockIconLayer: SimpleLayer - if let current = self.tintLockIconLayer { - tintLockIconLayer = current - } else { - tintLockIconLayer = SimpleLayer() - self.tintLockIconLayer = tintLockIconLayer - self.tintContentLayer.addSublayer(tintLockIconLayer) - } - if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: .white) { - tintLockIconLayer.contents = image.cgImage - tintLockIconLayer.frame = lockIconLayer.frame - tintLockIconLayer.isHidden = !needsVibrancy - } else { - tintLockIconLayer.contents = nil - } - } else { - if let lockIconLayer = self.lockIconLayer { - self.lockIconLayer = nil - lockIconLayer.removeFromSuperlayer() - } - if let tintLockIconLayer = self.tintLockIconLayer { - self.tintLockIconLayer = nil - tintLockIconLayer.removeFromSuperlayer() - } - } - - let subtitleSize: CGSize - if let subtitle = subtitle { - var updateSubtitleContents: UIImage? - var updateTintSubtitleContents: UIImage? - if let currentSubtitleLayout = self.currentSubtitleLayout, currentSubtitleLayout.string == subtitle, currentSubtitleLayout.color == subtitleColor, currentSubtitleLayout.constrainedWidth == textConstrainedWidth { - subtitleSize = currentSubtitleLayout.size - } else { - let string = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: subtitleColor) - let whiteString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: .white) - let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - subtitleSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) - updateSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - string.draw(in: stringBounds) - - UIGraphicsPopContext() - }) - updateTintSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - whiteString.draw(in: stringBounds) - - UIGraphicsPopContext() - }) - self.currentSubtitleLayout = (subtitle, subtitleColor, textConstrainedWidth, subtitleSize) - } - - let subtitleLayer: SimpleLayer - if let current = self.subtitleLayer { - subtitleLayer = current - } else { - subtitleLayer = SimpleLayer() - self.subtitleLayer = subtitleLayer - self.layer.addSublayer(subtitleLayer) - } - - if let updateSubtitleContents = updateSubtitleContents { - subtitleLayer.contents = updateSubtitleContents.cgImage - } - - let tintSubtitleLayer: SimpleLayer - if let current = self.tintSubtitleLayer { - tintSubtitleLayer = current - } else { - tintSubtitleLayer = SimpleLayer() - self.tintSubtitleLayer = tintSubtitleLayer - self.tintContentLayer.addSublayer(tintSubtitleLayer) - } - tintSubtitleLayer.isHidden = !needsVibrancy - - if let updateTintSubtitleContents = updateTintSubtitleContents { - tintSubtitleLayer.contents = updateTintSubtitleContents.cgImage - } - - let subtitleFrame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.maxY + 1.0), size: subtitleSize) - subtitleLayer.frame = subtitleFrame - tintSubtitleLayer.frame = subtitleFrame - } else { - subtitleSize = CGSize() - if let subtitleLayer = self.subtitleLayer { - self.subtitleLayer = nil - subtitleLayer.removeFromSuperlayer() - } - if let tintSubtitleLayer = self.tintSubtitleLayer { - self.tintSubtitleLayer = nil - tintSubtitleLayer.removeFromSuperlayer() - } - } - - self.clearIconLayer?.frame = CGRect(origin: CGPoint(x: constrainedSize.width - clearSize.width, y: floorToScreenPixels((textSize.height - clearSize.height) / 2.0)), size: clearSize) - - var size: CGSize - size = CGSize(width: constrainedSize.width, height: constrainedSize.height) - - if let embeddedItems = embeddedItems { - let groupEmbeddedView: GroupEmbeddedView - if let current = self.groupEmbeddedView { - groupEmbeddedView = current - } else { - groupEmbeddedView = GroupEmbeddedView(performItemAction: self.performItemAction) - self.groupEmbeddedView = groupEmbeddedView - self.addSubview(groupEmbeddedView) - } - - let groupEmbeddedViewSize = CGSize(width: constrainedSize.width + insets.left + insets.right, height: 36.0) - groupEmbeddedView.frame = CGRect(origin: CGPoint(x: -insets.left, y: size.height - groupEmbeddedViewSize.height), size: groupEmbeddedViewSize) - groupEmbeddedView.update( - context: context, - theme: theme, - insets: insets, - size: groupEmbeddedViewSize, - items: embeddedItems, - isStickers: isStickers, - cache: cache, - renderer: renderer, - attemptSynchronousLoad: attemptSynchronousLoad - ) - } else { - if let groupEmbeddedView = self.groupEmbeddedView { - self.groupEmbeddedView = nil - groupEmbeddedView.removeFromSuperview() - } - } - - if let actionButtonSize = actionButtonSize, let actionButton = self.actionButton { - let actionButtonFrame = CGRect(origin: CGPoint(x: size.width - actionButtonSize.width, y: textFrame.minY + (actionButtonIsCompact ? 0.0 : 3.0)), size: actionButtonSize) - actionButton.bounds = CGRect(origin: CGPoint(), size: actionButtonFrame.size) - actionButton.center = actionButtonFrame.center - } - - if hasTopSeparator { - let separatorLayer: SimpleLayer - if let current = self.separatorLayer { - separatorLayer = current - } else { - separatorLayer = SimpleLayer() - self.separatorLayer = separatorLayer - self.layer.addSublayer(separatorLayer) - } - separatorLayer.backgroundColor = subtitleColor.cgColor - separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) - - let tintSeparatorLayer: SimpleLayer - if let current = self.tintSeparatorLayer { - tintSeparatorLayer = current - } else { - tintSeparatorLayer = SimpleLayer() - self.tintSeparatorLayer = tintSeparatorLayer - self.tintContentLayer.addSublayer(tintSeparatorLayer) - } - tintSeparatorLayer.backgroundColor = UIColor.white.cgColor - tintSeparatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) - - tintSeparatorLayer.isHidden = !needsVibrancy - } else { - if let separatorLayer = self.separatorLayer { - self.separatorLayer = separatorLayer - separatorLayer.removeFromSuperlayer() - } - if let tintSeparatorLayer = self.tintSeparatorLayer { - self.tintSeparatorLayer = tintSeparatorLayer - tintSeparatorLayer.removeFromSuperlayer() - } - } - - return (size, titleHorizontalOffset + textSize.width + clearWidth) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - return super.hitTest(point, with: event) - } - - func tapGesture(point: CGPoint) -> Bool { - if let groupEmbeddedView = self.groupEmbeddedView { - return groupEmbeddedView.tapGesture(point: self.convert(point, to: groupEmbeddedView)) - } else { - return false - } - } -} - -private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, PagerExpandableScrollView { - private struct ItemLayout { - var itemSize: CGFloat - var itemSpacing: CGFloat - var sideInset: CGFloat - var itemCount: Int - var contentSize: CGSize - - init(height: CGFloat, sideInset: CGFloat, itemCount: Int) { - self.itemSize = 30.0 - self.itemSpacing = 20.0 - self.sideInset = sideInset - self.itemCount = itemCount - - self.contentSize = CGSize(width: self.sideInset * 2.0 + CGFloat(self.itemCount) * self.itemSize + CGFloat(self.itemCount - 1) * self.itemSpacing, height: height) - } - - func frame(at index: Int) -> CGRect { - return CGRect(origin: CGPoint(x: sideInset + CGFloat(index) * (self.itemSize + self.itemSpacing), y: floor((self.contentSize.height - self.itemSize) / 2.0)), size: CGSize(width: self.itemSize, height: self.itemSize)) - } - - func visibleItems(for rect: CGRect) -> Range? { - let offsetRect = rect.offsetBy(dx: -self.sideInset, dy: 0.0) - var minVisibleIndex = Int(floor((offsetRect.minX - self.itemSpacing) / (self.itemSize + self.itemSpacing))) - minVisibleIndex = max(0, minVisibleIndex) - var maxVisibleIndex = Int(ceil((offsetRect.maxX - self.itemSpacing) / (self.itemSize + self.itemSpacing))) - maxVisibleIndex = min(maxVisibleIndex, self.itemCount - 1) - - if minVisibleIndex <= maxVisibleIndex { - return minVisibleIndex ..< (maxVisibleIndex + 1) - } else { - return nil - } - } - } - - private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void - - private var visibleItemLayers: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemLayer] = [:] - private var ignoreScrolling: Bool = false - - private var context: AccountContext? - private var theme: PresentationTheme? - private var cache: AnimationCache? - private var renderer: MultiAnimationRenderer? - private var currentInsets: UIEdgeInsets? - private var currentSize: CGSize? - private var items: [EmojiPagerContentComponent.Item]? - private var isStickers: Bool = false - - private var itemLayout: ItemLayout? - - init(performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) { - self.performItemAction = performItemAction - - super.init(frame: CGRect()) - - self.delaysContentTouches = false - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.contentInsetAdjustmentBehavior = .never - } - if #available(iOS 13.0, *) { - self.automaticallyAdjustsScrollIndicatorInsets = false - } - self.showsVerticalScrollIndicator = true - self.showsHorizontalScrollIndicator = false - self.delegate = self - self.clipsToBounds = true - self.scrollsToTop = false - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func tapGesture(point: CGPoint) -> Bool { - guard let itemLayout = self.itemLayout else { - return false - } - - for (_, itemLayer) in self.visibleItemLayers { - if itemLayer.frame.inset(by: UIEdgeInsets(top: -6.0, left: -itemLayout.itemSpacing, bottom: -6.0, right: -itemLayout.itemSpacing)).contains(point) { - self.performItemAction(itemLayer.item, self, itemLayer.frame, itemLayer) - return true - } - } - - return false - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if !self.ignoreScrolling { - self.updateVisibleItems(transition: .immediate, attemptSynchronousLoad: false) - } - } - - private func updateVisibleItems(transition: Transition, attemptSynchronousLoad: Bool) { - guard let context = self.context, let theme = self.theme, let itemLayout = self.itemLayout, let items = self.items, let cache = self.cache, let renderer = self.renderer else { - return - } - - var validIds = Set() - if let itemRange = itemLayout.visibleItems(for: self.bounds) { - for index in itemRange.lowerBound ..< itemRange.upperBound { - let item = items[index] - let itemId = EmojiPagerContentComponent.View.ItemLayer.Key( - groupId: AnyHashable(0), - itemId: item.content.id - ) - validIds.insert(itemId) - - let itemLayer: EmojiPagerContentComponent.View.ItemLayer - if let current = self.visibleItemLayers[itemId] { - itemLayer = current - } else { - itemLayer = EmojiPagerContentComponent.View.ItemLayer( - item: item, - context: context, - attemptSynchronousLoad: attemptSynchronousLoad, - content: item.content, - cache: cache, - renderer: renderer, - placeholderColor: .clear, - blurredBadgeColor: .clear, - accentIconColor: theme.list.itemAccentColor, - pointSize: CGSize(width: 32.0, height: 32.0), - onUpdateDisplayPlaceholder: { _, _ in - } - ) - self.visibleItemLayers[itemId] = itemLayer - self.layer.addSublayer(itemLayer) - } - - switch item.tintMode { - case let .custom(color): - itemLayer.layerTintColor = color.cgColor - case .accent: - itemLayer.layerTintColor = theme.list.itemAccentColor.cgColor - case .primary: - itemLayer.layerTintColor = theme.list.itemPrimaryTextColor.cgColor - case .none: - itemLayer.layerTintColor = nil - } - - let itemFrame = itemLayout.frame(at: index) - itemLayer.frame = itemFrame - - itemLayer.isVisibleForAnimations = self.isStickers ? context.sharedContext.energyUsageSettings.loopStickers : context.sharedContext.energyUsageSettings.loopEmoji - } - } - - var removedIds: [EmojiPagerContentComponent.View.ItemLayer.Key] = [] - for (id, itemLayer) in self.visibleItemLayers { - if !validIds.contains(id) { - removedIds.append(id) - itemLayer.removeFromSuperlayer() - } - } - for id in removedIds { - self.visibleItemLayers.removeValue(forKey: id) - } - } - - func update( - context: AccountContext, - theme: PresentationTheme, - insets: UIEdgeInsets, - size: CGSize, - items: [EmojiPagerContentComponent.Item], - isStickers: Bool, - cache: AnimationCache, - renderer: MultiAnimationRenderer, - attemptSynchronousLoad: Bool - ) { - if self.theme === theme && self.currentInsets == insets && self.currentSize == size && self.items == items { - return - } - - self.context = context - self.theme = theme - self.currentInsets = insets - self.currentSize = size - self.items = items - self.isStickers = isStickers - self.cache = cache - self.renderer = renderer - - let itemLayout = ItemLayout(height: size.height, sideInset: insets.left, itemCount: items.count) - self.itemLayout = itemLayout - - self.ignoreScrolling = true - if itemLayout.contentSize != self.contentSize { - self.contentSize = itemLayout.contentSize - } - self.ignoreScrolling = false - - self.updateVisibleItems(transition: .immediate, attemptSynchronousLoad: attemptSynchronousLoad) - } -} - -private final class GroupExpandActionButton: UIButton { - override static var layerClass: AnyClass { - return PassthroughLayer.self - } - - let tintContainerLayer: SimpleLayer - - private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? - private let backgroundLayer: SimpleLayer - private let tintBackgroundLayer: SimpleLayer - private let textLayer: SimpleLayer - private let pressed: () -> Void - - init(pressed: @escaping () -> Void) { - self.pressed = pressed - - self.tintContainerLayer = SimpleLayer() - - self.backgroundLayer = SimpleLayer() - self.backgroundLayer.masksToBounds = true - - self.tintBackgroundLayer = SimpleLayer() - self.tintBackgroundLayer.masksToBounds = true - - self.textLayer = SimpleLayer() - - super.init(frame: CGRect()) - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer - - self.layer.addSublayer(self.backgroundLayer) - - self.layer.addSublayer(self.textLayer) - - self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside) - } - - required init(coder: NSCoder) { - preconditionFailure() - } - - @objc private func onPressed() { - self.pressed() - } - - override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { - self.alpha = 0.6 - - return super.beginTracking(touch, with: event) - } - - override func endTracking(_ touch: UITouch?, with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.endTracking(touch, with: event) - } - - override func cancelTracking(with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.cancelTracking(with: event) - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.touchesCancelled(touches, with: event) - } - - func update(theme: PresentationTheme, title: String, useOpaqueTheme: Bool) -> CGSize { - let textConstrainedWidth: CGFloat = 100.0 - let color = theme.list.itemCheckColors.foregroundColor - - if useOpaqueTheme { - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor.cgColor - } else { - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor.cgColor - } - self.tintContainerLayer.backgroundColor = UIColor.white.cgColor - - let textSize: CGSize - if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth { - textSize = currentTextLayout.size - } else { - let font: UIFont = Font.semibold(13.0) - let string = NSAttributedString(string: title.uppercased(), font: font, textColor: color) - let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) - self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - string.draw(in: stringBounds) - - UIGraphicsPopContext() - })?.cgImage - self.currentTextLayout = (title, color, textConstrainedWidth, textSize) - } - - var sideInset: CGFloat = 10.0 - if textSize.width > 24.0 { - sideInset = 6.0 - } - let size = CGSize(width: textSize.width + sideInset * 2.0, height: 28.0) - - let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize) - self.textLayer.frame = textFrame - - self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size) - self.tintBackgroundLayer.frame = CGRect(origin: CGPoint(), size: size) - self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0 - self.tintContainerLayer.cornerRadius = min(size.width, size.height) / 2.0 - - return size - } -} - -public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate { - private final class EmojiSearchTextField: UITextField { - override func textRect(forBounds bounds: CGRect) -> CGRect { - return bounds.integral - } - } - - private struct Params: Equatable { - var context: AccountContext - var theme: PresentationTheme - var forceNeedsVibrancy: Bool - var strings: PresentationStrings - var text: String - var useOpaqueTheme: Bool - var isActive: Bool - var hasPresetSearch: Bool - var textInputState: EmojiSearchSearchBarComponent.TextInputState - var searchState: EmojiPagerContentComponent.SearchState - var size: CGSize - var canFocus: Bool - var searchCategories: EmojiSearchCategories? - - static func ==(lhs: Params, rhs: Params) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.theme !== rhs.theme { - return false - } - if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.text != rhs.text { - return false - } - if lhs.useOpaqueTheme != rhs.useOpaqueTheme { - return false - } - if lhs.isActive != rhs.isActive { - return false - } - if lhs.hasPresetSearch != rhs.hasPresetSearch { - return false - } - if lhs.textInputState != rhs.textInputState { - return false - } - if lhs.searchState != rhs.searchState { - return false - } - if lhs.size != rhs.size { - return false - } - if lhs.canFocus != rhs.canFocus { - return false - } - if lhs.searchCategories != rhs.searchCategories { - return false - } - return true - } - } - - override public static var layerClass: AnyClass { - return PassthroughLayer.self - } - - private let activated: (Bool) -> Void - private let deactivated: (Bool) -> Void - private let updateQuery: (EmojiPagerContentComponent.SearchQuery?) -> Void - - let tintContainerView: UIView - - private let backgroundLayer: SimpleLayer - private let tintBackgroundLayer: SimpleLayer - - private let statusIcon = ComponentView() - - private let clearIconView: UIImageView - private let clearIconTintView: UIImageView - private let clearIconButton: HighlightTrackingButton - - private let cancelButtonTintTitle: ComponentView - private let cancelButtonTitle: ComponentView - private let cancelButton: HighlightTrackingButton - - private var placeholderContent = ComponentView() - - private var textFrame: CGRect? - private var textField: EmojiSearchTextField? - - private var tapRecognizer: UITapGestureRecognizer? - private(set) var currentPresetSearchTerm: EmojiSearchCategories.Group? - - private var params: Params? - - public var wantsDisplayBelowKeyboard: Bool { - return self.textField != nil - } - - init(activated: @escaping (Bool) -> Void, deactivated: @escaping (Bool) -> Void, updateQuery: @escaping (EmojiPagerContentComponent.SearchQuery?) -> Void) { - self.activated = activated - self.deactivated = deactivated - self.updateQuery = updateQuery - - self.tintContainerView = UIView() - - self.backgroundLayer = SimpleLayer() - self.tintBackgroundLayer = SimpleLayer() - - self.clearIconView = UIImageView() - self.clearIconTintView = UIImageView() - self.clearIconButton = HighlightableButton() - self.clearIconView.isHidden = true - self.clearIconTintView.isHidden = true - self.clearIconButton.isHidden = true - - self.cancelButtonTintTitle = ComponentView() - self.cancelButtonTitle = ComponentView() - self.cancelButton = HighlightTrackingButton() - - super.init(frame: CGRect()) - - self.layer.addSublayer(self.backgroundLayer) - self.tintContainerView.layer.addSublayer(self.tintBackgroundLayer) - - self.addSubview(self.clearIconView) - self.tintContainerView.addSubview(self.clearIconTintView) - self.addSubview(self.clearIconButton) - - self.addSubview(self.cancelButton) - self.clipsToBounds = true - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer - - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) - self.tapRecognizer = tapRecognizer - self.addGestureRecognizer(tapRecognizer) - - self.cancelButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.layer.removeAnimation(forKey: "opacity") - cancelButtonTitleView.alpha = 0.4 - } - if let cancelButtonTintTitleView = strongSelf.cancelButtonTintTitle.view { - cancelButtonTintTitleView.layer.removeAnimation(forKey: "opacity") - cancelButtonTintTitleView.alpha = 0.4 - } - } else { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.alpha = 1.0 - cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - if let cancelButtonTintTitleView = strongSelf.cancelButtonTintTitle.view { - cancelButtonTintTitleView.alpha = 1.0 - cancelButtonTintTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } - self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside) - - self.clearIconButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.clearIconView.layer.removeAnimation(forKey: "opacity") - strongSelf.clearIconView.alpha = 0.4 - strongSelf.clearIconTintView.layer.removeAnimation(forKey: "opacity") - strongSelf.clearIconTintView.alpha = 0.4 - } else { - strongSelf.clearIconView.alpha = 1.0 - strongSelf.clearIconView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - strongSelf.clearIconTintView.alpha = 1.0 - strongSelf.clearIconTintView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - self.clearIconButton.addTarget(self, action: #selector(self.clearPressed), for: .touchUpInside) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - let location = recognizer.location(in: self) - if let view = self.statusIcon.view, view.frame.contains(location), self.currentPresetSearchTerm != nil { - self.clearCategorySearch() - } else { - self.activateTextInput() - } - } - } - - func clearCategorySearch() { - if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { - placeholderContentView.clearSelection(dispatchEvent : true) - } - } - - private func activateTextInput() { - guard let params = self.params else { - return - } - if self.textField == nil, let textFrame = self.textFrame, params.canFocus == true { - let backgroundFrame = self.backgroundLayer.frame - let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height)) - - let textField = EmojiSearchTextField(frame: textFieldFrame) - textField.keyboardAppearance = params.theme.rootController.keyboardColor.keyboardAppearance - textField.autocorrectionType = .no - textField.returnKeyType = .search - self.textField = textField - self.insertSubview(textField, belowSubview: self.clearIconView) - textField.delegate = self - textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) - } - - if params.canFocus { - self.currentPresetSearchTerm = nil - if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { - placeholderContentView.clearSelection(dispatchEvent: false) - } - } - - self.activated(true) - - self.textField?.becomeFirstResponder() - } - - @objc private func cancelPressed() { - self.currentPresetSearchTerm = nil - self.updateQuery(nil) - - self.clearIconView.isHidden = true - self.clearIconTintView.isHidden = true - self.clearIconButton.isHidden = true - - let textField = self.textField - self.textField = nil - - self.deactivated(textField?.isFirstResponder ?? false) - - if let textField { - textField.resignFirstResponder() - textField.removeFromSuperview() - } - - /*self.tintTextView.view?.isHidden = false - self.textView.view?.isHidden = false*/ - } - - @objc private func clearPressed() { - self.currentPresetSearchTerm = nil - self.updateQuery(nil) - self.textField?.text = "" - - self.clearIconView.isHidden = true - self.clearIconTintView.isHidden = true - self.clearIconButton.isHidden = true - - /*self.tintTextView.view?.isHidden = false - self.textView.view?.isHidden = false*/ - } - - var isActive: Bool { - return self.textField?.isFirstResponder ?? false - } - - func deactivate() { - if let text = self.textField?.text, !text.isEmpty { - self.textField?.endEditing(true) - } else { - self.cancelPressed() - } - } - - public func textFieldDidBeginEditing(_ textField: UITextField) { - } - - public func textFieldDidEndEditing(_ textField: UITextField) { - } - - public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.endEditing(true) - return false - } - - @objc private func textFieldChanged(_ textField: UITextField) { - self.update(transition: .immediate) - - let text = textField.text ?? "" - - var inputLanguage = textField.textInputMode?.primaryLanguage ?? "en" - if let range = inputLanguage.range(of: "-") { - inputLanguage = String(inputLanguage[inputLanguage.startIndex ..< range.lowerBound]) - } - if let range = inputLanguage.range(of: "_") { - inputLanguage = String(inputLanguage[inputLanguage.startIndex ..< range.lowerBound]) - } - - self.clearIconView.isHidden = text.isEmpty - self.clearIconTintView.isHidden = text.isEmpty - self.clearIconButton.isHidden = text.isEmpty - - self.currentPresetSearchTerm = nil - self.updateQuery(.text(value: text, language: inputLanguage)) - } - - private func update(transition: Transition) { - guard let params = self.params else { - return - } - self.params = nil - self.update(context: params.context, theme: params.theme, forceNeedsVibrancy: params.forceNeedsVibrancy, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, searchState: params.searchState, transition: transition) - } - - public func update(context: AccountContext, theme: PresentationTheme, forceNeedsVibrancy: Bool, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, searchState: EmojiPagerContentComponent.SearchState, transition: Transition) { - let textInputState: EmojiSearchSearchBarComponent.TextInputState - if let textField = self.textField { - textInputState = .active(hasText: !(textField.text ?? "").isEmpty) - } else { - textInputState = .inactive - } - - let params = Params( - context: context, - theme: theme, - forceNeedsVibrancy: forceNeedsVibrancy, - strings: strings, - text: text, - useOpaqueTheme: useOpaqueTheme, - isActive: isActive, - hasPresetSearch: self.currentPresetSearchTerm == nil, - textInputState: textInputState, - searchState: searchState, - size: size, - canFocus: canFocus, - searchCategories: searchCategories - ) - - if self.params == params { - return - } - - let isActiveWithText = isActive && self.currentPresetSearchTerm == nil - - if self.params?.theme !== theme { - /*self.searchIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.searchIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor - - self.searchIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white) - - self.backIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.backIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor - - self.backIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)*/ - - self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.clearIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor - - self.clearIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white) - } - - self.params = params - - let sideInset: CGFloat = 12.0 - let topInset: CGFloat = 8.0 - let inputHeight: CGFloat = 36.0 - - let sideTextInset: CGFloat = sideInset + 4.0 + 24.0 - - if theme.overallDarkAppearance && forceNeedsVibrancy { - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.withMultipliedAlpha(0.3).cgColor - self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor - } else if useOpaqueTheme { - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor - self.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor - } else { - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.cgColor - self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor - } - - self.backgroundLayer.cornerRadius = inputHeight * 0.5 - self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5 - - let cancelColor: UIColor - if theme.overallDarkAppearance && forceNeedsVibrancy { - cancelColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3) - } else { - cancelColor = useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor - } - - let cancelTextSize = self.cancelButtonTitle.update( - transition: .immediate, - component: AnyComponent(Text( - text: strings.Common_Cancel, - font: Font.regular(17.0), - color: cancelColor - )), - environment: {}, - containerSize: CGSize(width: size.width - 32.0, height: 100.0) - ) - let _ = self.cancelButtonTintTitle.update( - transition: .immediate, - component: AnyComponent(Text( - text: strings.Common_Cancel, - font: Font.regular(17.0), - color: .white - )), - environment: {}, - containerSize: CGSize(width: size.width - 32.0, height: 100.0) - ) - - let cancelButtonSpacing: CGFloat = 8.0 - - var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight)) - if isActiveWithText { - backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing - } - transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame) - transition.setFrame(layer: self.tintBackgroundLayer, frame: backgroundFrame) - - transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height))) - - let textX: CGFloat = backgroundFrame.minX + sideTextInset - let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) - self.textFrame = textFrame - - let statusContent: EmojiSearchStatusComponent.Content - switch searchState { - case .empty: - statusContent = .search - case .searching: - statusContent = .progress - case .active: - statusContent = .results - } - - let statusSize = CGSize(width: 24.0, height: 24.0) - let _ = self.statusIcon.update( - transition: transition, - component: AnyComponent(EmojiSearchStatusComponent( - theme: theme, - forceNeedsVibrancy: forceNeedsVibrancy, - strings: strings, - useOpaqueTheme: useOpaqueTheme, - content: statusContent - )), - environment: {}, - containerSize: statusSize - ) - let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - statusSize.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - statusSize.height) / 2.0)), size: statusSize) - if let statusIconView = self.statusIcon.view as? EmojiSearchStatusComponent.View { - if statusIconView.superview == nil { - self.addSubview(statusIconView) - self.tintContainerView.addSubview(statusIconView.tintContainerView) - } - - transition.setFrame(view: statusIconView, frame: iconFrame) - transition.setFrame(view: statusIconView.tintContainerView, frame: iconFrame) - } - - /*if let image = self.searchIconView.image { - let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - transition.setBounds(view: self.searchIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - transition.setPosition(view: self.searchIconView, position: iconFrame.center) - transition.setBounds(view: self.searchIconTintView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - transition.setPosition(view: self.searchIconTintView, position: iconFrame.center) - transition.setScale(view: self.searchIconView, scale: self.currentPresetSearchTerm == nil ? 1.0 : 0.001) - transition.setAlpha(view: self.searchIconView, alpha: self.currentPresetSearchTerm == nil ? 1.0 : 0.0) - transition.setScale(view: self.searchIconTintView, scale: self.currentPresetSearchTerm == nil ? 1.0 : 0.001) - transition.setAlpha(view: self.searchIconTintView, alpha: self.currentPresetSearchTerm == nil ? 1.0 : 0.0) - } - - if let image = self.backIconView.image { - let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - transition.setBounds(view: self.backIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - transition.setPosition(view: self.backIconView, position: iconFrame.center) - transition.setBounds(view: self.backIconTintView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - transition.setPosition(view: self.backIconTintView, position: iconFrame.center) - transition.setScale(view: self.backIconView, scale: self.currentPresetSearchTerm != nil ? 1.0 : 0.001) - transition.setAlpha(view: self.backIconView, alpha: self.currentPresetSearchTerm != nil ? 1.0 : 0.0) - transition.setScale(view: self.backIconTintView, scale: self.currentPresetSearchTerm != nil ? 1.0 : 0.001) - transition.setAlpha(view: self.backIconTintView, alpha: self.currentPresetSearchTerm != nil ? 1.0 : 0.0) - }*/ - - let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX - 6.0, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - (textFrame.minX - 6.0), height: backgroundFrame.height)) - let _ = self.placeholderContent.update( - transition: transition, - component: AnyComponent(EmojiSearchSearchBarComponent( - context: context, - theme: theme, - forceNeedsVibrancy: forceNeedsVibrancy, - strings: strings, - useOpaqueTheme: useOpaqueTheme, - textInputState: textInputState, - categories: searchCategories, - searchTermUpdated: { [weak self] term in - guard let self else { - return - } - var shouldChangeActivation = false - if (self.currentPresetSearchTerm == nil) != (term == nil) { - shouldChangeActivation = true - } - self.currentPresetSearchTerm = term - - if shouldChangeActivation { - if let term { - self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) - - self.updateQuery(.category(value: term)) - self.activated(false) - } else { - self.deactivated(self.textField?.isFirstResponder ?? false) - self.updateQuery(nil) - } - } else { - if let term { - self.updateQuery(.category(value: term)) - } else { - self.updateQuery(nil) - } - } - }, - activateTextInput: { [weak self] in - guard let self else { - return - } - self.activateTextInput() - } - )), - environment: {}, - containerSize: placeholderContentFrame.size - ) - if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { - if placeholderContentView.superview == nil { - self.addSubview(placeholderContentView) - self.tintContainerView.addSubview(placeholderContentView.tintContainerView) - } - transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame) - transition.setFrame(view: placeholderContentView.tintContainerView, frame: placeholderContentFrame) - } - - /*if let searchCategories { - let suggestedItemsView: ComponentView - var suggestedItemsTransition = transition - if let current = self.suggestedItemsView { - suggestedItemsView = current - } else { - suggestedItemsTransition = .immediate - suggestedItemsView = ComponentView() - self.suggestedItemsView = suggestedItemsView - } - - let itemsX: CGFloat = textFrame.maxX + 8.0 - let suggestedItemsFrame = CGRect(origin: CGPoint(x: itemsX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - itemsX, height: backgroundFrame.height)) - - if let suggestedItemsComponentView = suggestedItemsView.view { - if suggestedItemsComponentView.superview == nil { - self.addSubview(suggestedItemsComponentView) - } - suggestedItemsTransition.setFrame(view: suggestedItemsComponentView, frame: suggestedItemsFrame) - suggestedItemsTransition.setAlpha(view: suggestedItemsComponentView, alpha: isActiveWithText ? 0.0 : 1.0) - } - } else { - if let suggestedItemsView = self.suggestedItemsView { - self.suggestedItemsView = nil - if let suggestedItemsComponentView = suggestedItemsView.view { - transition.setAlpha(view: suggestedItemsComponentView, alpha: 0.0, completion: { [weak suggestedItemsComponentView] _ in - suggestedItemsComponentView?.removeFromSuperview() - }) - } - } - }*/ - - if let image = self.clearIconView.image { - let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - transition.setFrame(view: self.clearIconView, frame: iconFrame) - transition.setFrame(view: self.clearIconTintView, frame: iconFrame) - transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0)) - } - - if let cancelButtonTitleComponentView = self.cancelButtonTitle.view { - if cancelButtonTitleComponentView.superview == nil { - self.addSubview(cancelButtonTitleComponentView) - cancelButtonTitleComponentView.isUserInteractionEnabled = false - } - transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) - transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0) - } - if let cancelButtonTintTitleComponentView = self.cancelButtonTintTitle.view { - if cancelButtonTintTitleComponentView.superview == nil { - self.tintContainerView.addSubview(cancelButtonTintTitleComponentView) - cancelButtonTintTitleComponentView.isUserInteractionEnabled = false - } - transition.setFrame(view: cancelButtonTintTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) - transition.setAlpha(view: cancelButtonTintTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0) - } - - var hasText = false - if let textField = self.textField { - textField.textColor = theme.contextMenu.primaryColor - transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideTextInset, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.width - sideTextInset - 32.0, height: backgroundFrame.height))) - - if let text = textField.text, !text.isEmpty { - hasText = true - } - } - let _ = hasText - - /*self.tintTextView.view?.isHidden = hasText - self.textView.view?.isHidden = hasText*/ - } -} - -private final class EmptySearchResultsView: UIView { - override public static var layerClass: AnyClass { - return PassthroughLayer.self - } - - let tintContainerView: UIView - let titleLabel: ComponentView - let titleTintLabel: ComponentView - let icon: ComponentView - - override init(frame: CGRect) { - self.tintContainerView = UIView() - - self.titleLabel = ComponentView() - self.titleTintLabel = ComponentView() - self.icon = ComponentView() - - super.init(frame: frame) - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + self.type = type + self.resource = resource + self.dimensions = dimensions + self.immediateThumbnailData = immediateThumbnailData + self.isReaction = isReaction + self.isTemplate = isTemplate } - func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, searchInitiallyHidden: Bool, transition: Transition) { - let titleColor: UIColor - if useOpaqueTheme { - titleColor = theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor + public convenience init(file: TelegramMediaFile, isReaction: Bool = false, partialReference: PartialMediaReference? = nil) { + let type: ItemType + if file.isVideoSticker || file.isVideoEmoji { + type = .video + } else if file.isAnimatedSticker { + type = .lottie } else { - titleColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + type = .still } + let isTemplate = file.isCustomTemplateEmoji - let iconSize: CGSize - if let file = file { - iconSize = self.icon.update( - transition: .immediate, - component: AnyComponent(EmojiStatusComponent( - context: context, - animationCache: context.animationCache, - animationRenderer: context.animationRenderer, - content: .animation(content: .file(file: file), size: CGSize(width: 32.0, height: 32.0), placeholderColor: titleColor, themeColor: nil, loopMode: .forever), - isVisibleForAnimations: context.sharedContext.energyUsageSettings.loopEmoji, - action: nil - )), - environment: {}, - containerSize: CGSize(width: 32.0, height: 32.0) - ) + let resourceReference: MediaResourceReference + if let partialReference { + resourceReference = partialReference.mediaReference(file).resourceReference(file.resource) } else { - iconSize = CGSize() + resourceReference = .standalone(resource: file.resource) + } + self.init(id: .file(file.fileId), type: type, resource: resourceReference, dimensions: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), immediateThumbnailData: file.immediateThumbnailData, isReaction: isReaction, isTemplate: isTemplate) + } + + public static func ==(lhs: EntityKeyboardAnimationData, rhs: EntityKeyboardAnimationData) -> Bool { + if lhs === rhs { + return true } - let titleSize = self.titleLabel.update( - transition: .immediate, - component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: titleColor)), - environment: {}, - containerSize: CGSize(width: size.width, height: 100.0) - ) - let _ = self.titleTintLabel.update( - transition: .immediate, - component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: .white)), - environment: {}, - containerSize: CGSize(width: size.width, height: 100.0) - ) - - let spacing: CGFloat = 4.0 - let contentHeight = iconSize.height + spacing + titleSize.height - let contentOriginY = searchInitiallyHidden ? floor((size.height - contentHeight) / 2.0) : 10.0 - let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: contentOriginY), size: iconSize) - let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + spacing), size: titleSize) - - if let iconView = self.icon.view { - if iconView.superview == nil { - self.addSubview(iconView) - } - transition.setFrame(view: iconView, frame: iconFrame) + if lhs.resource.resource.id != rhs.resource.resource.id { + return false } - if let titleLabelView = self.titleLabel.view { - if titleLabelView.superview == nil { - self.addSubview(titleLabelView) - } - transition.setFrame(view: titleLabelView, frame: titleFrame) + if lhs.dimensions != rhs.dimensions { + return false } - if let titleTintLabelView = self.titleTintLabel.view { - if titleTintLabelView.superview == nil { - self.tintContainerView.addSubview(titleTintLabelView) - } - transition.setFrame(view: titleTintLabelView, frame: titleFrame) + if lhs.type != rhs.type { + return false + } + if lhs.immediateThumbnailData != rhs.immediateThumbnailData { + return false + } + if lhs.isReaction != rhs.isReaction { + return false } + + return true } } @@ -2608,6 +365,8 @@ public final class EmojiPagerContentComponent: Component { case none case locked case premium + case text(String) + case customFile(TelegramMediaFile) } public enum TintMode: Equatable { @@ -3431,466 +1190,6 @@ public final class EmojiPagerContentComponent: Component { } } - final class CloneItemLayer: SimpleLayer { - } - - public final class ItemLayer: MultiAnimationRenderTarget { - public struct Key: Hashable { - var groupId: AnyHashable - var itemId: ItemContent.Id - - public init( - groupId: AnyHashable, - itemId: ItemContent.Id - ) { - self.groupId = groupId - self.itemId = itemId - } - } - - enum Badge { - case premium - case locked - case featured - } - - public let item: Item - - private var content: ItemContent - private var theme: PresentationTheme? - - private let placeholderColor: UIColor - let pixelSize: CGSize - let pointSize: CGSize - private let size: CGSize - private var disposable: Disposable? - private var fetchDisposable: Disposable? - private var premiumBadgeView: PremiumBadgeView? - - private var iconLayer: SimpleLayer? - private var tintIconLayer: SimpleLayer? - - private(set) var tintContentLayer: SimpleLayer? - - private var badge: Badge? - private var validSize: CGSize? - - private var isInHierarchyValue: Bool = false - public var isVisibleForAnimations: Bool = false { - didSet { - if self.isVisibleForAnimations != oldValue { - self.updatePlayback() - } - } - } - public private(set) var displayPlaceholder: Bool = false - public let onUpdateDisplayPlaceholder: (Bool, Double) -> Void - - weak var cloneLayer: CloneItemLayer? { - didSet { - if let cloneLayer = self.cloneLayer { - cloneLayer.contents = self.contents - } - } - } - - override public var contents: Any? { - didSet { - self.onContentsUpdate() - if let cloneLayer = self.cloneLayer { - cloneLayer.contents = self.contents - } - } - } - - override public var position: CGPoint { - get { - return super.position - } set(value) { - if let mirrorLayer = self.tintContentLayer { - mirrorLayer.position = value - } - super.position = value - } - } - - override public var bounds: CGRect { - get { - return super.bounds - } set(value) { - if let mirrorLayer = self.tintContentLayer { - mirrorLayer.bounds = value - } - super.bounds = value - } - } - - override public func add(_ animation: CAAnimation, forKey key: String?) { - if let mirrorLayer = self.tintContentLayer { - mirrorLayer.add(animation, forKey: key) - } - - super.add(animation, forKey: key) - } - - override public func removeAllAnimations() { - if let mirrorLayer = self.tintContentLayer { - mirrorLayer.removeAllAnimations() - } - - super.removeAllAnimations() - } - - override public func removeAnimation(forKey: String) { - if let mirrorLayer = self.tintContentLayer { - mirrorLayer.removeAnimation(forKey: forKey) - } - - super.removeAnimation(forKey: forKey) - } - - public var onContentsUpdate: () -> Void = {} - public var onLoop: () -> Void = {} - - public init( - item: Item, - context: AccountContext, - attemptSynchronousLoad: Bool, - content: ItemContent, - cache: AnimationCache, - renderer: MultiAnimationRenderer, - placeholderColor: UIColor, - blurredBadgeColor: UIColor, - accentIconColor: UIColor, - pointSize: CGSize, - onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void - ) { - self.item = item - self.content = content - self.placeholderColor = placeholderColor - self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder - - let scale = min(2.0, UIScreenScale) - let pixelSize = CGSize(width: pointSize.width * scale, height: pointSize.height * scale) - self.pixelSize = pixelSize - self.pointSize = pointSize - self.size = CGSize(width: pixelSize.width / scale, height: pixelSize.height / scale) - - super.init() - - switch content { - case let .animation(animationData): - let loadAnimation: () -> Void = { [weak self] in - guard let strongSelf = self else { - return - } - - strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, unique: false, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: pixelSize.width >= 120.0, customColor: animationData.isTemplate ? .white : nil)) - } - - if attemptSynchronousLoad { - if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize) { - self.updateDisplayPlaceholder(displayPlaceholder: true) - - self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true, customColor: animationData.isTemplate ? .white : nil), completion: { [weak self] success, isFinal in - if !isFinal { - if !success { - Queue.mainQueue().async { - guard let strongSelf = self else { - return - } - - strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) - } - } - return - } - - Queue.mainQueue().async { - loadAnimation() - - if !success { - guard let strongSelf = self else { - return - } - - strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) - } - } - }) - } else { - loadAnimation() - } - } else { - self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true, customColor: animationData.isTemplate ? .white : nil), completion: { [weak self] success, isFinal in - if !isFinal { - if !success { - Queue.mainQueue().async { - guard let strongSelf = self else { - return - } - - strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) - } - } - return - } - - Queue.mainQueue().async { - loadAnimation() - - if !success { - guard let strongSelf = self else { - return - } - - strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) - } - } - }) - } - case let .staticEmoji(staticEmoji): - let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - let preScaleFactor: CGFloat = 1.0 - let scaledSize = CGSize(width: floor(size.width * preScaleFactor), height: floor(size.height * preScaleFactor)) - let scaleFactor = scaledSize.width / size.width - - context.scaleBy(x: 1.0 / scaleFactor, y: 1.0 / scaleFactor) - - let string = NSAttributedString(string: staticEmoji, font: Font.regular(floor(32.0 * scaleFactor)), textColor: .black) - let boundingRect = string.boundingRect(with: scaledSize, options: .usesLineFragmentOrigin, context: nil) - UIGraphicsPushContext(context) - string.draw(at: CGPoint(x: floorToScreenPixels((scaledSize.width - boundingRect.width) / 2.0 + boundingRect.minX), y: floorToScreenPixels((scaledSize.height - boundingRect.height) / 2.0 + boundingRect.minY))) - UIGraphicsPopContext() - }) - self.contents = image?.cgImage - case let .icon(icon): - let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - UIGraphicsPushContext(context) - - switch icon { - case .premiumStar: - if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: accentIconColor) { - let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) - image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) - } - case let .topic(title, color): - let colors = topicIconColors(for: color) - if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) { - let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) - image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) - } - case .stop: - if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/NoIcon"), color: .white) { - let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) - image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) - } - case .add: - break - } - - UIGraphicsPopContext() - })?.withRenderingMode(icon == .stop ? .alwaysTemplate : .alwaysOriginal) - self.contents = image?.cgImage - } - - if case .icon(.add) = content { - let tintContentLayer = SimpleLayer() - self.tintContentLayer = tintContentLayer - - let iconLayer = SimpleLayer() - self.iconLayer = iconLayer - self.addSublayer(iconLayer) - - let tintIconLayer = SimpleLayer() - self.tintIconLayer = tintIconLayer - tintContentLayer.addSublayer(tintIconLayer) - } - } - - override public init(layer: Any) { - guard let layer = layer as? ItemLayer else { - preconditionFailure() - } - - self.item = layer.item - - self.content = layer.content - self.placeholderColor = layer.placeholderColor - self.size = layer.size - self.pixelSize = layer.pixelSize - self.pointSize = layer.pointSize - - self.onUpdateDisplayPlaceholder = { _, _ in } - - super.init(layer: layer) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.disposable?.dispose() - self.fetchDisposable?.dispose() - } - - public override func action(forKey event: String) -> CAAction? { - if event == kCAOnOrderIn { - self.isInHierarchyValue = true - } else if event == kCAOnOrderOut { - self.isInHierarchyValue = false - } - self.updatePlayback() - return nullAction - } - - func update( - content: ItemContent, - theme: PresentationTheme - ) { - var themeUpdated = false - if self.theme !== theme { - self.theme = theme - themeUpdated = true - } - var contentUpdated = false - if self.content != content { - self.content = content - contentUpdated = true - } - - if themeUpdated || contentUpdated { - if case let .icon(icon) = content, case let .topic(title, color) = icon { - let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - UIGraphicsPushContext(context) - - let colors = topicIconColors(for: color) - if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) { - let imageSize = image.size - image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) - } - - UIGraphicsPopContext() - }) - self.contents = image?.cgImage - } else if case .icon(.add) = content { - guard let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer else { - return - } - func generateIcon(color: UIColor) -> UIImage? { - return generateImage(self.pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - UIGraphicsPushContext(context) - - context.setFillColor(color.withMultipliedAlpha(0.2).cgColor) - context.fillEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: 8.0, dy: 8.0)) - context.setFillColor(color.cgColor) - - let plusSize = CGSize(width: 4.5, height: 31.5) - context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height), cornerRadius: plusSize.width / 2.0).cgPath) - context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width), cornerRadius: plusSize.width / 2.0).cgPath) - context.fillPath() - - UIGraphicsPopContext() - }) - } - - let needsVibrancy = !theme.overallDarkAppearance - let color = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor - - iconLayer.contents = generateIcon(color: color)?.cgImage - tintIconLayer.contents = generateIcon(color: .white)?.cgImage - - tintIconLayer.isHidden = !needsVibrancy - } - } - } - - func update( - transition: Transition, - size: CGSize, - badge: Badge?, - blurredBadgeColor: UIColor, - blurredBadgeBackgroundColor: UIColor - ) { - if self.badge != badge || self.validSize != size { - self.badge = badge - self.validSize = size - - if let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer { - transition.setFrame(layer: iconLayer, frame: CGRect(origin: .zero, size: size)) - transition.setFrame(layer: tintIconLayer, frame: CGRect(origin: .zero, size: size)) - } - - if let badge = badge { - var badgeTransition = transition - let premiumBadgeView: PremiumBadgeView - if let current = self.premiumBadgeView { - premiumBadgeView = current - } else { - badgeTransition = .immediate - premiumBadgeView = PremiumBadgeView() - self.premiumBadgeView = premiumBadgeView - self.addSublayer(premiumBadgeView.layer) - } - - let badgeDiameter = min(16.0, floor(size.height * 0.5)) - let badgeSize = CGSize(width: badgeDiameter, height: badgeDiameter) - badgeTransition.setFrame(view: premiumBadgeView, frame: CGRect(origin: CGPoint(x: size.width - badgeSize.width, y: size.height - badgeSize.height), size: badgeSize)) - premiumBadgeView.update(transition: badgeTransition, badge: badge, backgroundColor: blurredBadgeColor, size: badgeSize) - - self.blurredRepresentationBackgroundColor = blurredBadgeBackgroundColor - self.blurredRepresentationTarget = premiumBadgeView.contentLayer - } else { - if let premiumBadgeView = self.premiumBadgeView { - self.premiumBadgeView = nil - premiumBadgeView.removeFromSuperview() - - self.blurredRepresentationBackgroundColor = nil - self.blurredRepresentationTarget = nil - } - } - } - } - - private func updatePlayback() { - let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations - - self.shouldBeAnimating = shouldBePlaying - } - - public override func updateDisplayPlaceholder(displayPlaceholder: Bool) { - if self.displayPlaceholder == displayPlaceholder { - return - } - - self.displayPlaceholder = displayPlaceholder - self.onUpdateDisplayPlaceholder(displayPlaceholder, 0.0) - } - - public override func transitionToContents(_ contents: AnyObject, didLoop: Bool) { - self.contents = contents - - if self.displayPlaceholder { - self.displayPlaceholder = false - self.onUpdateDisplayPlaceholder(false, 0.2) - self.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) - } - - if didLoop { - self.onLoop() - } - } - } - private final class GroupBorderLayer: PassthroughShapeLayer { let tintContainerLayer: CAShapeLayer @@ -4031,7 +1330,7 @@ public final class EmojiPagerContentComponent: Component { } private enum VisualItemKey: Hashable { - case item(id: ItemLayer.Key) + case item(id: EmojiKeyboardItemLayer.Key) case header(groupId: AnyHashable) case groupExpandButton(groupId: AnyHashable) case groupActionButton(groupId: AnyHashable) @@ -4061,10 +1360,10 @@ public final class EmojiPagerContentComponent: Component { private var visibleSearchHeader: EmojiSearchHeaderView? private var visibleEmptySearchResultsView: EmptySearchResultsView? private var visibleCustomContentView: EmojiCustomContentView? - private var visibleItemPlaceholderViews: [ItemLayer.Key: ItemPlaceholderView] = [:] + private var visibleItemPlaceholderViews: [EmojiKeyboardItemLayer.Key: ItemPlaceholderView] = [:] private var visibleFillPlaceholdersViews: [Int: ItemPlaceholderView] = [:] - private var visibleItemSelectionLayers: [ItemLayer.Key: ItemSelectionLayer] = [:] - private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:] + private var visibleItemSelectionLayers: [EmojiKeyboardItemLayer.Key: ItemSelectionLayer] = [:] + private var visibleItemLayers: [EmojiKeyboardItemLayer.Key: EmojiKeyboardItemLayer] = [:] private var visibleGroupHeaders: [AnyHashable: GroupHeaderLayer] = [:] private var visibleGroupBorders: [AnyHashable: GroupBorderLayer] = [:] private var visibleGroupPremiumButtons: [AnyHashable: ComponentView] = [:] @@ -4085,12 +1384,13 @@ public final class EmojiPagerContentComponent: Component { private var component: EmojiPagerContentComponent? private weak var state: EmptyComponentState? + private var isUpdating: Bool = false private var pagerEnvironment: PagerComponentChildEnvironment? private var keyboardChildEnvironment: EntityKeyboardChildEnvironment? private var activeItemUpdated: ActionSlot<(AnyHashable, AnyHashable?, Transition)>? private var itemLayout: ItemLayout? - private var contextFocusItemKey: EmojiPagerContentComponent.View.ItemLayer.Key? + private var contextFocusItemKey: EmojiKeyboardItemLayer.Key? private var contextGesture: ContextGesture? private var tapRecognizer: UITapGestureRecognizer? @@ -4430,7 +1730,7 @@ public final class EmojiPagerContentComponent: Component { } public func layerForItem(groupId: AnyHashable, item: EmojiPagerContentComponent.Item) -> CALayer? { - let itemKey = EmojiPagerContentComponent.View.ItemLayer.Key(groupId: groupId, itemId: item.content.id) + let itemKey = EmojiKeyboardItemLayer.Key(groupId: groupId, itemId: item.content.id) if let itemLayer = self.visibleItemLayers[itemKey] { return itemLayer } else { @@ -4457,15 +1757,15 @@ public final class EmojiPagerContentComponent: Component { let offsetDirectionSign: Double = scrollPosition < self.scrollView.bounds.minY ? -1.0 : 1.0 - var previousVisibleLayers: [ItemLayer.Key: (CALayer, CGRect)] = [:] + var previousVisibleLayers: [EmojiKeyboardItemLayer.Key: (CALayer, CGRect)] = [:] for (id, layer) in self.visibleItemLayers { previousVisibleLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } - var previousVisibleItemSelectionLayers: [ItemLayer.Key: (CALayer, CGRect)] = [:] + var previousVisibleItemSelectionLayers: [EmojiKeyboardItemLayer.Key: (CALayer, CGRect)] = [:] for (id, layer) in self.visibleItemSelectionLayers { previousVisibleItemSelectionLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } - var previousVisiblePlaceholderViews: [ItemLayer.Key: (UIView, CGRect)] = [:] + var previousVisiblePlaceholderViews: [EmojiKeyboardItemLayer.Key: (UIView, CGRect)] = [:] for (id, view) in self.visibleItemPlaceholderViews { previousVisiblePlaceholderViews[id] = (view, view.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } @@ -4951,15 +2251,15 @@ public final class EmojiPagerContentComponent: Component { let offsetDirectionSign: Double = scrollPosition < self.scrollView.bounds.minY ? -1.0 : 1.0 - var previousVisibleLayers: [ItemLayer.Key: (CALayer, CGRect)] = [:] + var previousVisibleLayers: [EmojiKeyboardItemLayer.Key: (CALayer, CGRect)] = [:] for (id, layer) in self.visibleItemLayers { previousVisibleLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } - var previousVisibleItemSelectionLayers: [ItemLayer.Key: (ItemSelectionLayer, CGRect)] = [:] + var previousVisibleItemSelectionLayers: [EmojiKeyboardItemLayer.Key: (ItemSelectionLayer, CGRect)] = [:] for (id, layer) in self.visibleItemSelectionLayers { previousVisibleItemSelectionLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } - var previousVisiblePlaceholderViews: [ItemLayer.Key: (UIView, CGRect)] = [:] + var previousVisiblePlaceholderViews: [EmojiKeyboardItemLayer.Key: (UIView, CGRect)] = [:] for (id, view) in self.visibleItemPlaceholderViews { previousVisiblePlaceholderViews[id] = (view, view.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } @@ -5430,8 +2730,8 @@ public final class EmojiPagerContentComponent: Component { } private let longPressDuration: Double = 0.5 - private var longPressItem: EmojiPagerContentComponent.View.ItemLayer.Key? - private var currentLongPressLayer: CloneItemLayer? + private var longPressItem: EmojiKeyboardItemLayer.Key? + private var currentLongPressLayer: EmojiKeyboardCloneItemLayer? private var hapticFeedback: HapticFeedback? private var continuousHaptic: AnyObject? private var longPressTimer: SwiftSignalKit.Timer? @@ -5470,7 +2770,7 @@ public final class EmojiPagerContentComponent: Component { self.currentLongPressLayer = nil currentLongPressLayer.removeFromSuperlayer() } - let currentLongPressLayer = CloneItemLayer() + let currentLongPressLayer = EmojiKeyboardCloneItemLayer() currentLongPressLayer.position = self.scrollView.layer.convert(itemLayer.position, to: externalExpansionView.layer) currentLongPressLayer.bounds = itemLayer.convert(itemLayer.bounds, to: externalExpansionView.layer) currentLongPressLayer.transform = itemLayer.transform @@ -5568,10 +2868,10 @@ public final class EmojiPagerContentComponent: Component { } } - private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (Item, ItemLayer.Key)? { + private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (Item, EmojiKeyboardItemLayer.Key)? { let localPoint = self.convert(point, to: self.scrollView) - var closestItem: (key: ItemLayer.Key, distance: CGFloat)? + var closestItem: (key: EmojiKeyboardItemLayer.Key, distance: CGFloat)? for (key, itemLayer) in self.visibleItemLayers { if extendedHitRange { @@ -5733,7 +3033,7 @@ public final class EmojiPagerContentComponent: Component { var topVisibleGroupId: AnyHashable? var topVisibleSubgroupId: AnyHashable? - var validIds = Set() + var validIds = Set() var validGroupHeaderIds = Set() var validGroupBorderIds = Set() var validGroupPremiumButtonIds = Set() @@ -6063,7 +3363,7 @@ public final class EmojiPagerContentComponent: Component { } } - let itemId = ItemLayer.Key( + let itemId = EmojiKeyboardItemLayer.Key( groupId: itemGroup.groupId, itemId: item.content.id ) @@ -6078,7 +3378,7 @@ public final class EmojiPagerContentComponent: Component { var animateItemIn = false var updateItemLayerPlaceholder = false var itemTransition = transition - let itemLayer: ItemLayer + let itemLayer: EmojiKeyboardItemLayer if let current = self.visibleItemLayers[itemId] { itemLayer = current } else { @@ -6094,7 +3394,7 @@ public final class EmojiPagerContentComponent: Component { } let placeholderColor = keyboardChildEnvironment.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1) - itemLayer = ItemLayer( + itemLayer = EmojiKeyboardItemLayer( item: item, context: component.context, attemptSynchronousLoad: attemptSynchronousLoads, @@ -6191,7 +3491,7 @@ public final class EmojiPagerContentComponent: Component { let itemPosition = CGPoint(x: itemFrame.midX, y: itemFrame.midY) itemTransition.setPosition(layer: itemLayer, position: itemPosition) - var badge: ItemLayer.Badge? + var badge: EmojiKeyboardItemLayer.Badge? if itemGroup.displayPremiumBadges, let file = item.itemFile, file.isPremiumSticker { badge = .premium } else { @@ -6202,6 +3502,10 @@ public final class EmojiPagerContentComponent: Component { badge = .locked case .premium: badge = .premium + case let .text(value): + badge = .text(value) + case let .customFile(customFile): + badge = .customFile(customFile) } } @@ -6368,7 +3672,7 @@ public final class EmojiPagerContentComponent: Component { } var removedPlaceholerViews = false - var removedIds: [ItemLayer.Key] = [] + var removedIds: [EmojiKeyboardItemLayer.Key] = [] for (id, itemLayer) in self.visibleItemLayers { if !validIds.contains(id) { removedIds.append(id) @@ -6458,7 +3762,7 @@ public final class EmojiPagerContentComponent: Component { removedPlaceholerViews = true } } - var removedItemSelectionLayerIds: [ItemLayer.Key] = [] + var removedItemSelectionLayerIds: [EmojiKeyboardItemLayer.Key] = [] for (id, itemSelectionLayer) in self.visibleItemSelectionLayers { var fileId: MediaId? switch id.itemId { @@ -6752,6 +4056,11 @@ public final class EmojiPagerContentComponent: Component { } func update(component: EmojiPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + let previousComponent = self.component self.component = component @@ -6847,7 +4156,7 @@ public final class EmojiPagerContentComponent: Component { var hintDisappearingGroupFrame: (groupId: AnyHashable, frame: CGRect)? var previousAbsoluteItemPositions: [VisualItemKey: CGPoint] = [:] - var anchorItems: [ItemLayer.Key: CGRect] = [:] + var anchorItems: [EmojiKeyboardItemLayer.Key: CGRect] = [:] if let previousComponent = previousComponent, let previousItemLayout = self.itemLayout, previousComponent.contentItemGroups != component.contentItemGroups, previousComponent.itemContentUniqueId == component.itemContentUniqueId { if !transition.animation.isImmediate { var previousItemPositionsValue: [VisualItemKey: CGPoint] = [:] @@ -6855,8 +4164,8 @@ public final class EmojiPagerContentComponent: Component { let itemGroup = previousComponent.contentItemGroups[groupIndex] for itemIndex in 0 ..< itemGroup.items.count { let item = itemGroup.items[itemIndex] - let itemKey: ItemLayer.Key - itemKey = ItemLayer.Key( + let itemKey: EmojiKeyboardItemLayer.Key + itemKey = EmojiKeyboardItemLayer.Key( groupId: itemGroup.groupId, itemId: item.content.id ) @@ -7123,7 +4432,7 @@ public final class EmojiPagerContentComponent: Component { var animatedScrollOffset: CGFloat = 0.0 if !anchorItems.isEmpty && !keepOffset { - let sortedAnchorItems: [(ItemLayer.Key, CGRect)] = anchorItems.sorted(by: { lhs, rhs in + let sortedAnchorItems: [(EmojiKeyboardItemLayer.Key, CGRect)] = anchorItems.sorted(by: { lhs, rhs in if lhs.value.minY != rhs.value.minY { return lhs.value.minY < rhs.value.minY } else { @@ -7137,8 +4446,8 @@ public final class EmojiPagerContentComponent: Component { continue } for j in 0 ..< component.contentItemGroups[i].items.count { - let itemKey: ItemLayer.Key - itemKey = ItemLayer.Key( + let itemKey: EmojiKeyboardItemLayer.Key + itemKey = EmojiKeyboardItemLayer.Key( groupId: component.contentItemGroups[i].groupId, itemId: component.contentItemGroups[i].items[j].content.id ) @@ -7184,8 +4493,8 @@ public final class EmojiPagerContentComponent: Component { let itemGroupLayout = itemLayout.itemGroupLayouts[groupIndex] for itemIndex in 0 ..< itemGroup.items.count { let item = itemGroup.items[itemIndex] - let itemKey: ItemLayer.Key - itemKey = ItemLayer.Key( + let itemKey: EmojiKeyboardItemLayer.Key + itemKey = EmojiKeyboardItemLayer.Key( groupId: itemGroup.groupId, itemId: item.content.id ) @@ -7278,6 +4587,10 @@ public final class EmojiPagerContentComponent: Component { Transition(animation: .curve(duration: 0.4, curve: .spring))) } } + + if !strongSelf.isUpdating { + strongSelf.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + } } }, updateQuery: { [weak self] query in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index d02f26e64f5..ea06826e997 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -1607,6 +1607,7 @@ public extension EmojiPagerContentComponent { hasTrending: Bool, forceHasPremium: Bool, hasEdit: Bool = false, + hasAdd: Bool = false, searchIsPlaceholderOnly: Bool = true, subject: StickersSubject = .chatStickers, hideBackground: Bool = false @@ -1890,7 +1891,7 @@ public extension EmojiPagerContentComponent { } } - if hasEdit && !addedCreateStickerButton, let groupIndex = itemGroupIndexById[groupId] { + if hasAdd && !addedCreateStickerButton, let groupIndex = itemGroupIndexById[groupId] { let resultItem = EmojiPagerContentComponent.Item( animationData: nil, content: .icon(.add), @@ -2162,4 +2163,156 @@ public extension EmojiPagerContentComponent { ) } } + + static func messageEffectsInputData( + context: AccountContext, + animationCache: AnimationCache, + animationRenderer: MultiAnimationRenderer, + hasSearch: Bool, + hideBackground: Bool = false + ) -> Signal { + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + let isPremiumDisabled = premiumConfiguration.isPremiumDisabled + + let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings + + let searchCategories: Signal = context.engine.stickers.emojiSearchCategories(kind: .emoji) + + return combineLatest( + hasPremium(context: context, chatPeerId: nil, premiumIfSavedMessages: false), + context.engine.stickers.availableMessageEffects(), + searchCategories + ) + |> map { hasPremium, availableMessageEffects, searchCategories -> EmojiPagerContentComponent in + struct ItemGroup { + var supergroupId: AnyHashable + var id: AnyHashable + var title: String? + var subtitle: String? + var actionButtonTitle: String? + var isPremiumLocked: Bool + var isFeatured: Bool + var displayPremiumBadges: Bool + var hasEdit: Bool + var headerItem: EntityKeyboardAnimationData? + var items: [EmojiPagerContentComponent.Item] + } + var itemGroups: [ItemGroup] = [] + var itemGroupIndexById: [AnyHashable: Int] = [:] + + if let availableMessageEffects { + var reactionEffects: [AvailableMessageEffects.MessageEffect] = [] + var stickerEffects: [AvailableMessageEffects.MessageEffect] = [] + for messageEffect in availableMessageEffects.messageEffects { + if messageEffect.effectAnimation != nil { + reactionEffects.append(messageEffect) + } else { + stickerEffects.append(messageEffect) + } + } + + for i in 0 ..< 2 { + let groupId = i == 0 ? "reactions" : "stickers" + for item in i == 0 ? reactionEffects : stickerEffects { + if item.isPremium && isPremiumDisabled { + continue + } + + let itemFile: TelegramMediaFile = item.effectSticker + + var tintMode: Item.TintMode = .none + if itemFile.isCustomTemplateEmoji { + tintMode = .primary + } + + let icon: EmojiPagerContentComponent.Item.Icon + if i == 0 { + if !hasPremium && item.isPremium { + icon = .locked + } else { + icon = .none + } + } else { + if !hasPremium && item.isPremium { + icon = .locked + } else if let staticIcon = item.staticIcon { + icon = .customFile(staticIcon) + } else { + icon = .text(item.emoticon) + } + } + + let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none) + let resultItem = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: itemFile, + subgroupId: nil, + icon: icon, + tintMode: tintMode + ) + + if let groupIndex = itemGroupIndexById[groupId] { + itemGroups[groupIndex].items.append(resultItem) + } else { + itemGroupIndexById[groupId] = itemGroups.count + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: i == 0 ? nil : strings.Chat_MessageEffectMenu_SectionMessageEffects, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem])) + } + } + } + } + + let warpContentsOnEdges: Bool = true + + let allItemGroups = itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in + let hasClear = false + let isEmbedded = false + + return EmojiPagerContentComponent.ItemGroup( + supergroupId: group.supergroupId, + groupId: group.id, + title: group.title, + subtitle: group.subtitle, + badge: nil, + actionButtonTitle: group.actionButtonTitle, + isFeatured: group.isFeatured, + isPremiumLocked: group.isPremiumLocked, + isEmbedded: isEmbedded, + hasClear: hasClear, + hasEdit: group.hasEdit, + collapsedLineCount: nil, + displayPremiumBadges: group.displayPremiumBadges, + headerItem: group.headerItem, + fillWithLoadingPlaceholders: false, + items: group.items + ) + } + + return EmojiPagerContentComponent( + id: "stickers", + context: context, + avatarPeer: nil, + animationCache: animationCache, + animationRenderer: animationRenderer, + inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(), + panelItemGroups: [], + contentItemGroups: allItemGroups, + itemLayoutType: .detailed, + itemContentUniqueId: nil, + searchState: .empty(hasResults: false), + warpContentsOnEdges: warpContentsOnEdges, + hideBackground: hideBackground, + displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil, + searchCategories: searchCategories, + searchInitiallyHidden: true, + searchAlwaysActive: false, + searchIsPlaceholderOnly: false, + searchUnicodeEmojiOnly: false, + emptySearchResults: nil, + enableLongPress: false, + selectedItems: Set(), + customTintColor: nil + ) + } + } } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchHeaderView.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchHeaderView.swift new file mode 100644 index 00000000000..272fb06789a --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchHeaderView.swift @@ -0,0 +1,624 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramCore +import TelegramPresentationData +import AccountContext +import SwiftSignalKit + +public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate { + private final class EmojiSearchTextField: UITextField { + override func textRect(forBounds bounds: CGRect) -> CGRect { + return bounds.integral + } + } + + private struct Params: Equatable { + var context: AccountContext + var theme: PresentationTheme + var forceNeedsVibrancy: Bool + var strings: PresentationStrings + var text: String + var useOpaqueTheme: Bool + var isActive: Bool + var hasPresetSearch: Bool + var textInputState: EmojiSearchSearchBarComponent.TextInputState + var searchState: EmojiPagerContentComponent.SearchState + var size: CGSize + var canFocus: Bool + var searchCategories: EmojiSearchCategories? + + static func ==(lhs: Params, rhs: Params) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.useOpaqueTheme != rhs.useOpaqueTheme { + return false + } + if lhs.isActive != rhs.isActive { + return false + } + if lhs.hasPresetSearch != rhs.hasPresetSearch { + return false + } + if lhs.textInputState != rhs.textInputState { + return false + } + if lhs.searchState != rhs.searchState { + return false + } + if lhs.size != rhs.size { + return false + } + if lhs.canFocus != rhs.canFocus { + return false + } + if lhs.searchCategories != rhs.searchCategories { + return false + } + return true + } + } + + override public static var layerClass: AnyClass { + return PassthroughLayer.self + } + + private let activated: (Bool) -> Void + private let deactivated: (Bool) -> Void + private let updateQuery: (EmojiPagerContentComponent.SearchQuery?) -> Void + + let tintContainerView: UIView + + private let backgroundLayer: SimpleLayer + private let tintBackgroundLayer: SimpleLayer + + private let statusIcon = ComponentView() + + private let clearIconView: UIImageView + private let clearIconTintView: UIImageView + private let clearIconButton: HighlightTrackingButton + + private let cancelButtonTintTitle: ComponentView + private let cancelButtonTitle: ComponentView + private let cancelButton: HighlightTrackingButton + + private var placeholderContent = ComponentView() + + private var textFrame: CGRect? + private var textField: EmojiSearchTextField? + + private var tapRecognizer: UITapGestureRecognizer? + private(set) var currentPresetSearchTerm: EmojiSearchCategories.Group? + + private var params: Params? + + public var wantsDisplayBelowKeyboard: Bool { + return self.textField != nil + } + + init(activated: @escaping (Bool) -> Void, deactivated: @escaping (Bool) -> Void, updateQuery: @escaping (EmojiPagerContentComponent.SearchQuery?) -> Void) { + self.activated = activated + self.deactivated = deactivated + self.updateQuery = updateQuery + + self.tintContainerView = UIView() + + self.backgroundLayer = SimpleLayer() + self.tintBackgroundLayer = SimpleLayer() + + self.clearIconView = UIImageView() + self.clearIconTintView = UIImageView() + self.clearIconButton = HighlightableButton() + self.clearIconView.isHidden = true + self.clearIconTintView.isHidden = true + self.clearIconButton.isHidden = true + + self.cancelButtonTintTitle = ComponentView() + self.cancelButtonTitle = ComponentView() + self.cancelButton = HighlightTrackingButton() + + super.init(frame: CGRect()) + + self.layer.addSublayer(self.backgroundLayer) + self.tintContainerView.layer.addSublayer(self.tintBackgroundLayer) + + self.addSubview(self.clearIconView) + self.tintContainerView.addSubview(self.clearIconTintView) + self.addSubview(self.clearIconButton) + + self.addSubview(self.cancelButton) + self.clipsToBounds = true + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer + + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + self.tapRecognizer = tapRecognizer + self.addGestureRecognizer(tapRecognizer) + + self.cancelButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { + cancelButtonTitleView.layer.removeAnimation(forKey: "opacity") + cancelButtonTitleView.alpha = 0.4 + } + if let cancelButtonTintTitleView = strongSelf.cancelButtonTintTitle.view { + cancelButtonTintTitleView.layer.removeAnimation(forKey: "opacity") + cancelButtonTintTitleView.alpha = 0.4 + } + } else { + if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { + cancelButtonTitleView.alpha = 1.0 + cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + if let cancelButtonTintTitleView = strongSelf.cancelButtonTintTitle.view { + cancelButtonTintTitleView.alpha = 1.0 + cancelButtonTintTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside) + + self.clearIconButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.clearIconView.layer.removeAnimation(forKey: "opacity") + strongSelf.clearIconView.alpha = 0.4 + strongSelf.clearIconTintView.layer.removeAnimation(forKey: "opacity") + strongSelf.clearIconTintView.alpha = 0.4 + } else { + strongSelf.clearIconView.alpha = 1.0 + strongSelf.clearIconView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.clearIconTintView.alpha = 1.0 + strongSelf.clearIconTintView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.clearIconButton.addTarget(self, action: #selector(self.clearPressed), for: .touchUpInside) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + let location = recognizer.location(in: self) + if let view = self.statusIcon.view, view.frame.contains(location), self.currentPresetSearchTerm != nil { + self.clearCategorySearch() + } else { + self.activateTextInput() + } + } + } + + func clearCategorySearch() { + if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { + placeholderContentView.clearSelection(dispatchEvent : true) + } + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let textField = self.textField, let text = textField.text, text.isEmpty { + if self.bounds.contains(point), let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { + let leftTextPosition = placeholderContentView.leftTextPosition() + if point.x >= 0.0 && point.x <= placeholderContentView.frame.minX + leftTextPosition { + if let result = placeholderContentView.hitTest(self.convert(point, to: placeholderContentView), with: event) { + return result + } + } + } + } + return super.hitTest(point, with: event) + } + + private func activateTextInput() { + guard let params = self.params else { + return + } + if self.textField == nil, let textFrame = self.textFrame, params.canFocus == true { + let backgroundFrame = self.backgroundLayer.frame + let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height)) + + let textField = EmojiSearchTextField(frame: textFieldFrame) + textField.keyboardAppearance = params.theme.rootController.keyboardColor.keyboardAppearance + textField.autocorrectionType = .no + textField.returnKeyType = .search + self.textField = textField + if let placeholderContentView = self.placeholderContent.view { + self.insertSubview(textField, belowSubview: placeholderContentView) + } else { + self.insertSubview(textField, belowSubview: self.clearIconView) + } + textField.delegate = self + textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) + } + + if params.canFocus { + self.currentPresetSearchTerm = nil + if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { + placeholderContentView.clearSelection(dispatchEvent: false) + } + } + + self.activated(true) + + self.textField?.becomeFirstResponder() + } + + @objc private func cancelPressed() { + let textField = self.textField + self.textField = nil + + self.currentPresetSearchTerm = nil + self.updateQuery(nil) + + self.clearIconView.isHidden = true + self.clearIconTintView.isHidden = true + self.clearIconButton.isHidden = true + + self.deactivated(textField?.isFirstResponder ?? false) + + if let textField { + textField.resignFirstResponder() + textField.removeFromSuperview() + } + + /*self.tintTextView.view?.isHidden = false + self.textView.view?.isHidden = false*/ + } + + @objc private func clearPressed() { + self.currentPresetSearchTerm = nil + self.updateQuery(nil) + self.textField?.text = "" + + self.clearIconView.isHidden = true + self.clearIconTintView.isHidden = true + self.clearIconButton.isHidden = true + + /*self.tintTextView.view?.isHidden = false + self.textView.view?.isHidden = false*/ + } + + var isActive: Bool { + return self.textField?.isFirstResponder ?? false + } + + func deactivate() { + if let text = self.textField?.text, !text.isEmpty { + self.textField?.endEditing(true) + } else { + self.cancelPressed() + } + } + + public func textFieldDidBeginEditing(_ textField: UITextField) { + } + + public func textFieldDidEndEditing(_ textField: UITextField) { + } + + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.endEditing(true) + return false + } + + @objc private func textFieldChanged(_ textField: UITextField) { + self.update(transition: .immediate) + + let text = textField.text ?? "" + + var inputLanguage = textField.textInputMode?.primaryLanguage ?? "en" + if let range = inputLanguage.range(of: "-") { + inputLanguage = String(inputLanguage[inputLanguage.startIndex ..< range.lowerBound]) + } + if let range = inputLanguage.range(of: "_") { + inputLanguage = String(inputLanguage[inputLanguage.startIndex ..< range.lowerBound]) + } + + self.clearIconView.isHidden = text.isEmpty + self.clearIconTintView.isHidden = text.isEmpty + self.clearIconButton.isHidden = text.isEmpty + + self.currentPresetSearchTerm = nil + self.updateQuery(.text(value: text, language: inputLanguage)) + } + + private func update(transition: Transition) { + guard let params = self.params else { + return + } + self.params = nil + self.update(context: params.context, theme: params.theme, forceNeedsVibrancy: params.forceNeedsVibrancy, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, searchState: params.searchState, transition: transition) + } + + public func update(context: AccountContext, theme: PresentationTheme, forceNeedsVibrancy: Bool, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, searchState: EmojiPagerContentComponent.SearchState, transition: Transition) { + let textInputState: EmojiSearchSearchBarComponent.TextInputState + if let textField = self.textField { + textInputState = .active(hasText: !(textField.text ?? "").isEmpty) + } else { + textInputState = .inactive + } + + let params = Params( + context: context, + theme: theme, + forceNeedsVibrancy: forceNeedsVibrancy, + strings: strings, + text: text, + useOpaqueTheme: useOpaqueTheme, + isActive: isActive, + hasPresetSearch: self.currentPresetSearchTerm == nil, + textInputState: textInputState, + searchState: searchState, + size: size, + canFocus: canFocus, + searchCategories: searchCategories + ) + + if self.params == params { + return + } + + let isActiveWithText = isActive && self.currentPresetSearchTerm == nil + + if self.params?.theme !== theme { + /*self.searchIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate) + self.searchIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + + self.searchIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white) + + self.backIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)?.withRenderingMode(.alwaysTemplate) + self.backIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + + self.backIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)*/ + + self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate) + self.clearIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + + self.clearIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white) + } + + self.params = params + + let sideInset: CGFloat = 12.0 + let topInset: CGFloat = 8.0 + let inputHeight: CGFloat = 36.0 + + let sideTextInset: CGFloat = sideInset + 4.0 + 24.0 + + if theme.overallDarkAppearance && forceNeedsVibrancy { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.withMultipliedAlpha(0.3).cgColor + self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor + } else if useOpaqueTheme { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor + self.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor + } else { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.cgColor + self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor + } + + self.backgroundLayer.cornerRadius = inputHeight * 0.5 + self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5 + + let cancelColor: UIColor + if theme.overallDarkAppearance && forceNeedsVibrancy { + cancelColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3) + } else { + cancelColor = useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + } + + let cancelTextSize = self.cancelButtonTitle.update( + transition: .immediate, + component: AnyComponent(Text( + text: strings.Common_Cancel, + font: Font.regular(17.0), + color: cancelColor + )), + environment: {}, + containerSize: CGSize(width: size.width - 32.0, height: 100.0) + ) + let _ = self.cancelButtonTintTitle.update( + transition: .immediate, + component: AnyComponent(Text( + text: strings.Common_Cancel, + font: Font.regular(17.0), + color: .white + )), + environment: {}, + containerSize: CGSize(width: size.width - 32.0, height: 100.0) + ) + + let cancelButtonSpacing: CGFloat = 8.0 + + var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight)) + if isActiveWithText { + backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing + } + transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame) + transition.setFrame(layer: self.tintBackgroundLayer, frame: backgroundFrame) + + transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height))) + + let textX: CGFloat = backgroundFrame.minX + sideTextInset + let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) + self.textFrame = textFrame + + let statusContent: EmojiSearchStatusComponent.Content + switch searchState { + case .empty: + statusContent = .search + case .searching: + statusContent = .progress + case .active: + statusContent = .results + } + + let statusSize = CGSize(width: 24.0, height: 24.0) + let _ = self.statusIcon.update( + transition: transition, + component: AnyComponent(EmojiSearchStatusComponent( + theme: theme, + forceNeedsVibrancy: forceNeedsVibrancy, + strings: strings, + useOpaqueTheme: useOpaqueTheme, + content: statusContent + )), + environment: {}, + containerSize: statusSize + ) + let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - statusSize.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - statusSize.height) / 2.0)), size: statusSize) + if let statusIconView = self.statusIcon.view as? EmojiSearchStatusComponent.View { + if statusIconView.superview == nil { + self.addSubview(statusIconView) + self.tintContainerView.addSubview(statusIconView.tintContainerView) + } + + transition.setFrame(view: statusIconView, frame: iconFrame) + transition.setFrame(view: statusIconView.tintContainerView, frame: iconFrame) + } + + let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX - 6.0, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - (textFrame.minX - 6.0), height: backgroundFrame.height)) + let _ = self.placeholderContent.update( + transition: transition, + component: AnyComponent(EmojiSearchSearchBarComponent( + context: context, + theme: theme, + forceNeedsVibrancy: forceNeedsVibrancy, + strings: strings, + useOpaqueTheme: useOpaqueTheme, + textInputState: textInputState, + categories: searchCategories, + searchTermUpdated: { [weak self] term in + guard let self else { + return + } + var shouldChangeActivation = false + if (self.currentPresetSearchTerm == nil) != (term == nil) { + shouldChangeActivation = true + } + self.currentPresetSearchTerm = term + + if shouldChangeActivation { + if let term { + self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + + self.updateQuery(.category(value: term)) + self.activated(false) + } else { + self.deactivated(self.textField?.isFirstResponder ?? false) + self.updateQuery(nil) + } + } else { + if let term { + self.updateQuery(.category(value: term)) + } else { + self.updateQuery(nil) + } + } + }, + activateTextInput: { [weak self] in + guard let self else { + return + } + self.activateTextInput() + } + )), + environment: {}, + containerSize: placeholderContentFrame.size + ) + if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { + if placeholderContentView.superview == nil { + self.addSubview(placeholderContentView) + self.tintContainerView.addSubview(placeholderContentView.tintContainerView) + } + transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame) + transition.setFrame(view: placeholderContentView.tintContainerView, frame: placeholderContentFrame) + } + + /*if let searchCategories { + let suggestedItemsView: ComponentView + var suggestedItemsTransition = transition + if let current = self.suggestedItemsView { + suggestedItemsView = current + } else { + suggestedItemsTransition = .immediate + suggestedItemsView = ComponentView() + self.suggestedItemsView = suggestedItemsView + } + + let itemsX: CGFloat = textFrame.maxX + 8.0 + let suggestedItemsFrame = CGRect(origin: CGPoint(x: itemsX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - itemsX, height: backgroundFrame.height)) + + if let suggestedItemsComponentView = suggestedItemsView.view { + if suggestedItemsComponentView.superview == nil { + self.addSubview(suggestedItemsComponentView) + } + suggestedItemsTransition.setFrame(view: suggestedItemsComponentView, frame: suggestedItemsFrame) + suggestedItemsTransition.setAlpha(view: suggestedItemsComponentView, alpha: isActiveWithText ? 0.0 : 1.0) + } + } else { + if let suggestedItemsView = self.suggestedItemsView { + self.suggestedItemsView = nil + if let suggestedItemsComponentView = suggestedItemsView.view { + transition.setAlpha(view: suggestedItemsComponentView, alpha: 0.0, completion: { [weak suggestedItemsComponentView] _ in + suggestedItemsComponentView?.removeFromSuperview() + }) + } + } + }*/ + + if let image = self.clearIconView.image { + let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) + transition.setFrame(view: self.clearIconView, frame: iconFrame) + transition.setFrame(view: self.clearIconTintView, frame: iconFrame) + transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0)) + } + + if let cancelButtonTitleComponentView = self.cancelButtonTitle.view { + if cancelButtonTitleComponentView.superview == nil { + self.addSubview(cancelButtonTitleComponentView) + cancelButtonTitleComponentView.isUserInteractionEnabled = false + } + transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) + transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0) + } + if let cancelButtonTintTitleComponentView = self.cancelButtonTintTitle.view { + if cancelButtonTintTitleComponentView.superview == nil { + self.tintContainerView.addSubview(cancelButtonTintTitleComponentView) + cancelButtonTintTitleComponentView.isUserInteractionEnabled = false + } + transition.setFrame(view: cancelButtonTintTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) + transition.setAlpha(view: cancelButtonTintTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0) + } + + var hasText = false + if let textField = self.textField { + textField.textColor = theme.contextMenu.primaryColor + transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideTextInset, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.width - sideTextInset - 32.0, height: backgroundFrame.height))) + + if let text = textField.text, !text.isEmpty { + hasText = true + } + } + let _ = hasText + + + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift index 93c8f1e1645..9b9b67c1ea1 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift @@ -420,6 +420,24 @@ final class EmojiSearchSearchBarComponent: Component { } } + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let component = self.component else { + return nil + } + let _ = component + + return super.hitTest(point, with: event) + } + + func leftTextPosition() -> CGFloat { + guard let itemLayout = self.itemLayout else { + return 0.0 + } + + let visibleBounds = self.scrollView.bounds + return (itemLayout.itemStartX - itemLayout.textSpacing) + visibleBounds.minX + } + private func updateScrolling(transition: Transition, fromScrolling: Bool) { guard let component = self.component, let itemLayout = self.itemLayout else { return @@ -427,8 +445,12 @@ final class EmojiSearchSearchBarComponent: Component { let itemAlpha: CGFloat switch component.textInputState { - case .active: - itemAlpha = 0.0 + case let .active(hasText): + if hasText { + itemAlpha = 0.0 + } else { + itemAlpha = 1.0 + } case .inactive: itemAlpha = 1.0 } @@ -674,7 +696,7 @@ final class EmojiSearchSearchBarComponent: Component { if self.scrollView.bounds.size != availableSize { transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize)) } - if case .active = component.textInputState { + if case .active(true) = component.textInputState { transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint()) } if self.scrollView.contentSize != itemLayout.contentSize { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmptySearchResultsView.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmptySearchResultsView.swift new file mode 100644 index 00000000000..466c430e8da --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmptySearchResultsView.swift @@ -0,0 +1,101 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramCore +import TelegramPresentationData +import EmojiStatusComponent + +final class EmptySearchResultsView: UIView { + override public static var layerClass: AnyClass { + return PassthroughLayer.self + } + + let tintContainerView: UIView + let titleLabel: ComponentView + let titleTintLabel: ComponentView + let icon: ComponentView + + override init(frame: CGRect) { + self.tintContainerView = UIView() + + self.titleLabel = ComponentView() + self.titleTintLabel = ComponentView() + self.icon = ComponentView() + + super.init(frame: frame) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, searchInitiallyHidden: Bool, transition: Transition) { + let titleColor: UIColor + if useOpaqueTheme { + titleColor = theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor + } else { + titleColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + } + + let iconSize: CGSize + if let file = file { + iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + content: .animation(content: .file(file: file), size: CGSize(width: 32.0, height: 32.0), placeholderColor: titleColor, themeColor: nil, loopMode: .forever), + isVisibleForAnimations: context.sharedContext.energyUsageSettings.loopEmoji, + action: nil + )), + environment: {}, + containerSize: CGSize(width: 32.0, height: 32.0) + ) + } else { + iconSize = CGSize() + } + + let titleSize = self.titleLabel.update( + transition: .immediate, + component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: titleColor)), + environment: {}, + containerSize: CGSize(width: size.width, height: 100.0) + ) + let _ = self.titleTintLabel.update( + transition: .immediate, + component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: .white)), + environment: {}, + containerSize: CGSize(width: size.width, height: 100.0) + ) + + let spacing: CGFloat = 4.0 + let contentHeight = iconSize.height + spacing + titleSize.height + let contentOriginY = searchInitiallyHidden ? floor((size.height - contentHeight) / 2.0) : 10.0 + let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: contentOriginY), size: iconSize) + let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + spacing), size: titleSize) + + if let iconView = self.icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + transition.setFrame(view: iconView, frame: iconFrame) + } + if let titleLabelView = self.titleLabel.view { + if titleLabelView.superview == nil { + self.addSubview(titleLabelView) + } + transition.setFrame(view: titleLabelView, frame: titleFrame) + } + if let titleTintLabelView = self.titleTintLabel.view { + if titleTintLabelView.superview == nil { + self.tintContainerView.addSubview(titleTintLabelView) + } + transition.setFrame(view: titleTintLabelView, frame: titleFrame) + } + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift index eea256000d4..236588fff22 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift @@ -85,7 +85,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component { } final class View: UIView { - var itemLayer: EmojiPagerContentComponent.View.ItemLayer? + var itemLayer: EmojiKeyboardItemLayer? var placeholderView: EmojiPagerContentComponent.View.ItemPlaceholderView? var component: EntityKeyboardAnimationTopPanelComponent? var titleView: ComponentView? @@ -116,7 +116,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component { if self.itemLayer == nil { let tintColor: EmojiPagerContentComponent.Item.TintMode = component.customTintColor.flatMap { .custom($0) } ?? .primary - let itemLayer = EmojiPagerContentComponent.View.ItemLayer( + let itemLayer = EmojiKeyboardItemLayer( item: EmojiPagerContentComponent.Item( animationData: component.item, content: .animation(component.item), @@ -157,7 +157,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component { transition.setPosition(layer: itemLayer, position: CGPoint(x: iconFrame.midX, y: iconFrame.midY)) transition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - var badge: EmojiPagerContentComponent.View.ItemLayer.Badge? + var badge: EmojiKeyboardItemLayer.Badge? if component.isPremiumLocked { badge = .locked } else if component.isFeatured { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupEmbeddedView.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupEmbeddedView.swift new file mode 100644 index 00000000000..4721e8897f2 --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupEmbeddedView.swift @@ -0,0 +1,209 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramPresentationData +import AnimationCache +import MultiAnimationRenderer +import PagerComponent + +final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, PagerExpandableScrollView { + private struct ItemLayout { + var itemSize: CGFloat + var itemSpacing: CGFloat + var sideInset: CGFloat + var itemCount: Int + var contentSize: CGSize + + init(height: CGFloat, sideInset: CGFloat, itemCount: Int) { + self.itemSize = 30.0 + self.itemSpacing = 20.0 + self.sideInset = sideInset + self.itemCount = itemCount + + self.contentSize = CGSize(width: self.sideInset * 2.0 + CGFloat(self.itemCount) * self.itemSize + CGFloat(self.itemCount - 1) * self.itemSpacing, height: height) + } + + func frame(at index: Int) -> CGRect { + return CGRect(origin: CGPoint(x: sideInset + CGFloat(index) * (self.itemSize + self.itemSpacing), y: floor((self.contentSize.height - self.itemSize) / 2.0)), size: CGSize(width: self.itemSize, height: self.itemSize)) + } + + func visibleItems(for rect: CGRect) -> Range? { + let offsetRect = rect.offsetBy(dx: -self.sideInset, dy: 0.0) + var minVisibleIndex = Int(floor((offsetRect.minX - self.itemSpacing) / (self.itemSize + self.itemSpacing))) + minVisibleIndex = max(0, minVisibleIndex) + var maxVisibleIndex = Int(ceil((offsetRect.maxX - self.itemSpacing) / (self.itemSize + self.itemSpacing))) + maxVisibleIndex = min(maxVisibleIndex, self.itemCount - 1) + + if minVisibleIndex <= maxVisibleIndex { + return minVisibleIndex ..< (maxVisibleIndex + 1) + } else { + return nil + } + } + } + + private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void + + private var visibleItemLayers: [EmojiKeyboardItemLayer.Key: EmojiKeyboardItemLayer] = [:] + private var ignoreScrolling: Bool = false + + private var context: AccountContext? + private var theme: PresentationTheme? + private var cache: AnimationCache? + private var renderer: MultiAnimationRenderer? + private var currentInsets: UIEdgeInsets? + private var currentSize: CGSize? + private var items: [EmojiPagerContentComponent.Item]? + private var isStickers: Bool = false + + private var itemLayout: ItemLayout? + + init(performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) { + self.performItemAction = performItemAction + + super.init(frame: CGRect()) + + self.delaysContentTouches = false + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.contentInsetAdjustmentBehavior = .never + } + if #available(iOS 13.0, *) { + self.automaticallyAdjustsScrollIndicatorInsets = false + } + self.showsVerticalScrollIndicator = true + self.showsHorizontalScrollIndicator = false + self.delegate = self + self.clipsToBounds = true + self.scrollsToTop = false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func tapGesture(point: CGPoint) -> Bool { + guard let itemLayout = self.itemLayout else { + return false + } + + for (_, itemLayer) in self.visibleItemLayers { + if itemLayer.frame.inset(by: UIEdgeInsets(top: -6.0, left: -itemLayout.itemSpacing, bottom: -6.0, right: -itemLayout.itemSpacing)).contains(point) { + self.performItemAction(itemLayer.item, self, itemLayer.frame, itemLayer) + return true + } + } + + return false + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateVisibleItems(transition: .immediate, attemptSynchronousLoad: false) + } + } + + private func updateVisibleItems(transition: Transition, attemptSynchronousLoad: Bool) { + guard let context = self.context, let theme = self.theme, let itemLayout = self.itemLayout, let items = self.items, let cache = self.cache, let renderer = self.renderer else { + return + } + + var validIds = Set() + if let itemRange = itemLayout.visibleItems(for: self.bounds) { + for index in itemRange.lowerBound ..< itemRange.upperBound { + let item = items[index] + let itemId = EmojiKeyboardItemLayer.Key( + groupId: AnyHashable(0), + itemId: item.content.id + ) + validIds.insert(itemId) + + let itemLayer: EmojiKeyboardItemLayer + if let current = self.visibleItemLayers[itemId] { + itemLayer = current + } else { + itemLayer = EmojiKeyboardItemLayer( + item: item, + context: context, + attemptSynchronousLoad: attemptSynchronousLoad, + content: item.content, + cache: cache, + renderer: renderer, + placeholderColor: .clear, + blurredBadgeColor: .clear, + accentIconColor: theme.list.itemAccentColor, + pointSize: CGSize(width: 32.0, height: 32.0), + onUpdateDisplayPlaceholder: { _, _ in + } + ) + self.visibleItemLayers[itemId] = itemLayer + self.layer.addSublayer(itemLayer) + } + + switch item.tintMode { + case let .custom(color): + itemLayer.layerTintColor = color.cgColor + case .accent: + itemLayer.layerTintColor = theme.list.itemAccentColor.cgColor + case .primary: + itemLayer.layerTintColor = theme.list.itemPrimaryTextColor.cgColor + case .none: + itemLayer.layerTintColor = nil + } + + let itemFrame = itemLayout.frame(at: index) + itemLayer.frame = itemFrame + + itemLayer.isVisibleForAnimations = self.isStickers ? context.sharedContext.energyUsageSettings.loopStickers : context.sharedContext.energyUsageSettings.loopEmoji + } + } + + var removedIds: [EmojiKeyboardItemLayer.Key] = [] + for (id, itemLayer) in self.visibleItemLayers { + if !validIds.contains(id) { + removedIds.append(id) + itemLayer.removeFromSuperlayer() + } + } + for id in removedIds { + self.visibleItemLayers.removeValue(forKey: id) + } + } + + func update( + context: AccountContext, + theme: PresentationTheme, + insets: UIEdgeInsets, + size: CGSize, + items: [EmojiPagerContentComponent.Item], + isStickers: Bool, + cache: AnimationCache, + renderer: MultiAnimationRenderer, + attemptSynchronousLoad: Bool + ) { + if self.theme === theme && self.currentInsets == insets && self.currentSize == size && self.items == items { + return + } + + self.context = context + self.theme = theme + self.currentInsets = insets + self.currentSize = size + self.items = items + self.isStickers = isStickers + self.cache = cache + self.renderer = renderer + + let itemLayout = ItemLayout(height: size.height, sideInset: insets.left, itemCount: items.count) + self.itemLayout = itemLayout + + self.ignoreScrolling = true + if itemLayout.contentSize != self.contentSize { + self.contentSize = itemLayout.contentSize + } + self.ignoreScrolling = false + + self.updateVisibleItems(transition: .immediate, attemptSynchronousLoad: attemptSynchronousLoad) + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupExpandActionButton.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupExpandActionButton.swift new file mode 100644 index 00000000000..0b3be8405d4 --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupExpandActionButton.swift @@ -0,0 +1,128 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData + +final class GroupExpandActionButton: UIButton { + override static var layerClass: AnyClass { + return PassthroughLayer.self + } + + let tintContainerLayer: SimpleLayer + + private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? + private let backgroundLayer: SimpleLayer + private let tintBackgroundLayer: SimpleLayer + private let textLayer: SimpleLayer + private let pressed: () -> Void + + init(pressed: @escaping () -> Void) { + self.pressed = pressed + + self.tintContainerLayer = SimpleLayer() + + self.backgroundLayer = SimpleLayer() + self.backgroundLayer.masksToBounds = true + + self.tintBackgroundLayer = SimpleLayer() + self.tintBackgroundLayer.masksToBounds = true + + self.textLayer = SimpleLayer() + + super.init(frame: CGRect()) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer + + self.layer.addSublayer(self.backgroundLayer) + + self.layer.addSublayer(self.textLayer) + + self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + @objc private func onPressed() { + self.pressed() + } + + override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + self.alpha = 0.6 + + return super.beginTracking(touch, with: event) + } + + override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.endTracking(touch, with: event) + } + + override func cancelTracking(with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.cancelTracking(with: event) + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.touchesCancelled(touches, with: event) + } + + func update(theme: PresentationTheme, title: String, useOpaqueTheme: Bool) -> CGSize { + let textConstrainedWidth: CGFloat = 100.0 + let color = theme.list.itemCheckColors.foregroundColor + + if useOpaqueTheme { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor.cgColor + } else { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor.cgColor + } + self.tintContainerLayer.backgroundColor = UIColor.white.cgColor + + let textSize: CGSize + if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth { + textSize = currentTextLayout.size + } else { + let font: UIFont = Font.semibold(13.0) + let string = NSAttributedString(string: title.uppercased(), font: font, textColor: color) + let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) + self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + string.draw(in: stringBounds) + + UIGraphicsPopContext() + })?.cgImage + self.currentTextLayout = (title, color, textConstrainedWidth, textSize) + } + + var sideInset: CGFloat = 10.0 + if textSize.width > 24.0 { + sideInset = 6.0 + } + let size = CGSize(width: textSize.width + sideInset * 2.0, height: 28.0) + + let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize) + self.textLayer.frame = textFrame + + self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size) + self.tintBackgroundLayer.frame = CGRect(origin: CGPoint(), size: size) + self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0 + self.tintContainerLayer.cornerRadius = min(size.width, size.height) / 2.0 + + return size + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderActionButton.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderActionButton.swift new file mode 100644 index 00000000000..cbc39084de0 --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderActionButton.swift @@ -0,0 +1,149 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData + +final class GroupHeaderActionButton: UIButton { + override static var layerClass: AnyClass { + return PassthroughLayer.self + } + + let tintContainerLayer: SimpleLayer + + private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? + private let backgroundLayer: SimpleLayer + private let tintBackgroundLayer: SimpleLayer + private let textLayer: SimpleLayer + private let tintTextLayer: SimpleLayer + private let pressed: () -> Void + + init(pressed: @escaping () -> Void) { + self.pressed = pressed + + self.tintContainerLayer = SimpleLayer() + + self.backgroundLayer = SimpleLayer() + self.backgroundLayer.masksToBounds = true + + self.tintBackgroundLayer = SimpleLayer() + self.tintBackgroundLayer.masksToBounds = true + + self.textLayer = SimpleLayer() + self.tintTextLayer = SimpleLayer() + + super.init(frame: CGRect()) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer + + self.layer.addSublayer(self.backgroundLayer) + self.layer.addSublayer(self.textLayer) + + self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside) + + self.tintContainerLayer.addSublayer(self.tintBackgroundLayer) + self.tintContainerLayer.addSublayer(self.tintTextLayer) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + @objc private func onPressed() { + self.pressed() + } + + override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + self.alpha = 0.6 + + return super.beginTracking(touch, with: event) + } + + override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.endTracking(touch, with: event) + } + + override func cancelTracking(with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.cancelTracking(with: event) + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.touchesCancelled(touches, with: event) + } + + func update(theme: PresentationTheme, title: String, compact: Bool) -> CGSize { + let textConstrainedWidth: CGFloat = 100.0 + + let needsVibrancy = !theme.overallDarkAppearance && compact + + let foregroundColor: UIColor + let backgroundColor: UIColor + + if compact { + foregroundColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor + backgroundColor = foregroundColor.withMultipliedAlpha(0.2) + } else { + foregroundColor = theme.list.itemCheckColors.foregroundColor + backgroundColor = theme.list.itemCheckColors.fillColor + } + + self.backgroundLayer.backgroundColor = backgroundColor.cgColor + self.tintBackgroundLayer.backgroundColor = UIColor.white.withAlphaComponent(0.2).cgColor + + self.tintContainerLayer.isHidden = !needsVibrancy + + let textSize: CGSize + if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == foregroundColor, currentTextLayout.constrainedWidth == textConstrainedWidth { + textSize = currentTextLayout.size + } else { + let font: UIFont = compact ? Font.medium(11.0) : Font.semibold(15.0) + let string = NSAttributedString(string: title.uppercased(), font: font, textColor: foregroundColor) + let tintString = NSAttributedString(string: title.uppercased(), font: font, textColor: .white) + let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) + self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + string.draw(in: stringBounds) + + UIGraphicsPopContext() + })?.cgImage + self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + tintString.draw(in: stringBounds) + + UIGraphicsPopContext() + })?.cgImage + self.currentTextLayout = (title, foregroundColor, textConstrainedWidth, textSize) + } + + let size = CGSize(width: textSize.width + (compact ? 6.0 : 16.0) * 2.0, height: compact ? 16.0 : 28.0) + + let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + self.textLayer.frame = textFrame + self.tintTextLayer.frame = textFrame + + self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size) + self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0 + + self.tintBackgroundLayer.frame = self.backgroundLayer.frame + self.tintBackgroundLayer.cornerRadius = self.backgroundLayer.cornerRadius + + return size + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderLayer.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderLayer.swift new file mode 100644 index 00000000000..857c0ca5d19 --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderLayer.swift @@ -0,0 +1,525 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramCore +import TelegramPresentationData +import AnimationCache +import MultiAnimationRenderer + +final class GroupHeaderLayer: UIView { + override static var layerClass: AnyClass { + return PassthroughLayer.self + } + + private let actionPressed: () -> Void + private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void + + private let textLayer: SimpleLayer + private let tintTextLayer: SimpleLayer + + private var subtitleLayer: SimpleLayer? + private var tintSubtitleLayer: SimpleLayer? + private var lockIconLayer: SimpleLayer? + private var tintLockIconLayer: SimpleLayer? + private var badgeLayer: SimpleLayer? + private var tintBadgeLayer: SimpleLayer? + private(set) var clearIconLayer: SimpleLayer? + private var tintClearIconLayer: SimpleLayer? + private var separatorLayer: SimpleLayer? + private var tintSeparatorLayer: SimpleLayer? + private var actionButton: GroupHeaderActionButton? + + private var groupEmbeddedView: GroupEmbeddedView? + + private var theme: PresentationTheme? + + private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? + private var currentSubtitleLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? + + let tintContentLayer: SimpleLayer + + init(actionPressed: @escaping () -> Void, performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) { + self.actionPressed = actionPressed + self.performItemAction = performItemAction + + self.textLayer = SimpleLayer() + self.tintTextLayer = SimpleLayer() + + self.tintContentLayer = SimpleLayer() + + super.init(frame: CGRect()) + + self.layer.addSublayer(self.textLayer) + self.tintContentLayer.addSublayer(self.tintTextLayer) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContentLayer + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update( + context: AccountContext, + theme: PresentationTheme, + forceNeedsVibrancy: Bool, + layoutType: EmojiPagerContentComponent.ItemLayoutType, + hasTopSeparator: Bool, + actionButtonTitle: String?, + actionButtonIsCompact: Bool, + title: String, + subtitle: String?, + badge: String?, + isPremiumLocked: Bool, + hasClear: Bool, + embeddedItems: [EmojiPagerContentComponent.Item]?, + isStickers: Bool, + constrainedSize: CGSize, + insets: UIEdgeInsets, + cache: AnimationCache, + renderer: MultiAnimationRenderer, + attemptSynchronousLoad: Bool + ) -> (size: CGSize, centralContentWidth: CGFloat) { + var themeUpdated = false + if self.theme !== theme { + self.theme = theme + themeUpdated = true + } + + let needsVibrancy = !theme.overallDarkAppearance || forceNeedsVibrancy + + let textOffsetY: CGFloat + if hasTopSeparator { + textOffsetY = 9.0 + } else { + textOffsetY = 0.0 + } + + let subtitleColor: UIColor + if theme.overallDarkAppearance && forceNeedsVibrancy { + subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.withMultipliedAlpha(0.2) + } else { + subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor + } + + let color: UIColor + let needsTintText: Bool + if subtitle != nil { + color = theme.chat.inputPanel.primaryTextColor + needsTintText = false + } else { + color = subtitleColor + needsTintText = true + } + + let titleHorizontalOffset: CGFloat + if isPremiumLocked { + titleHorizontalOffset = 10.0 + 2.0 + } else { + titleHorizontalOffset = 0.0 + } + + var actionButtonSize: CGSize? + if let actionButtonTitle = actionButtonTitle { + let actionButton: GroupHeaderActionButton + if let current = self.actionButton { + actionButton = current + } else { + actionButton = GroupHeaderActionButton(pressed: self.actionPressed) + self.actionButton = actionButton + self.addSubview(actionButton) + self.tintContentLayer.addSublayer(actionButton.tintContainerLayer) + } + + actionButtonSize = actionButton.update(theme: theme, title: actionButtonTitle, compact: actionButtonIsCompact) + } else { + if let actionButton = self.actionButton { + self.actionButton = nil + actionButton.removeFromSuperview() + } + } + + var clearSize: CGSize = .zero + var clearWidth: CGFloat = 0.0 + if hasClear { + var updateImage = themeUpdated + + let clearIconLayer: SimpleLayer + if let current = self.clearIconLayer { + clearIconLayer = current + } else { + updateImage = true + clearIconLayer = SimpleLayer() + self.clearIconLayer = clearIconLayer + self.layer.addSublayer(clearIconLayer) + } + let tintClearIconLayer: SimpleLayer + if let current = self.tintClearIconLayer { + tintClearIconLayer = current + } else { + updateImage = true + tintClearIconLayer = SimpleLayer() + self.tintClearIconLayer = tintClearIconLayer + self.tintContentLayer.addSublayer(tintClearIconLayer) + } + + tintClearIconLayer.isHidden = !needsVibrancy + + clearSize = clearIconLayer.bounds.size + if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: subtitleColor) { + clearSize = image.size + clearIconLayer.contents = image.cgImage + } + if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: .white) { + tintClearIconLayer.contents = image.cgImage + } + + tintClearIconLayer.frame = clearIconLayer.frame + clearWidth = 4.0 + clearSize.width + } else { + if let clearIconLayer = self.clearIconLayer { + self.clearIconLayer = nil + clearIconLayer.removeFromSuperlayer() + } + if let tintClearIconLayer = self.tintClearIconLayer { + self.tintClearIconLayer = nil + tintClearIconLayer.removeFromSuperlayer() + } + } + + var textConstrainedWidth = constrainedSize.width - titleHorizontalOffset - 10.0 + if let actionButtonSize = actionButtonSize { + if actionButtonIsCompact { + textConstrainedWidth -= actionButtonSize.width * 2.0 + 10.0 + } else { + textConstrainedWidth -= actionButtonSize.width + 10.0 + } + } + if clearWidth > 0.0 { + textConstrainedWidth -= clearWidth + 8.0 + } + + let textSize: CGSize + if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth { + textSize = currentTextLayout.size + } else { + let font: UIFont + let stringValue: String + if subtitle == nil { + font = Font.medium(13.0) + stringValue = title.uppercased() + } else { + font = Font.semibold(16.0) + stringValue = title + } + let string = NSAttributedString(string: stringValue, font: font, textColor: color) + let whiteString = NSAttributedString(string: stringValue, font: font, textColor: .white) + let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) + self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + //string.draw(in: stringBounds) + string.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + + UIGraphicsPopContext() + })?.cgImage + self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + //whiteString.draw(in: stringBounds) + whiteString.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + + UIGraphicsPopContext() + })?.cgImage + self.tintTextLayer.isHidden = !needsVibrancy + self.currentTextLayout = (title, color, textConstrainedWidth, textSize) + } + + var badgeSize: CGSize = .zero + if let badge { + func generateBadgeImage(color: UIColor) -> UIImage? { + let string = NSAttributedString(string: badge, font: Font.semibold(11.0), textColor: .white) + let stringBounds = string.boundingRect(with: CGSize(width: 120, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + + let badgeSize = CGSize(width: stringBounds.width + 8.0, height: 16.0) + return generateImage(badgeSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(color.cgColor) + context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: badgeSize), cornerRadius: badgeSize.height / 2.0).cgPath) + context.fillPath() + + context.setBlendMode(.clear) + + UIGraphicsPushContext(context) + + string.draw(with: CGRect(origin: CGPoint(x: floorToScreenPixels((badgeSize.width - stringBounds.size.width) / 2.0), y: floorToScreenPixels((badgeSize.height - stringBounds.size.height) / 2.0)), size: stringBounds.size), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + + UIGraphicsPopContext() + }) + } + + let badgeLayer: SimpleLayer + if let current = self.badgeLayer { + badgeLayer = current + } else { + badgeLayer = SimpleLayer() + self.badgeLayer = badgeLayer + self.layer.addSublayer(badgeLayer) + + if let image = generateBadgeImage(color: color.withMultipliedAlpha(0.66)) { + badgeLayer.contents = image.cgImage + badgeLayer.bounds = CGRect(origin: .zero, size: image.size) + } + } + badgeSize = badgeLayer.bounds.size + + let tintBadgeLayer: SimpleLayer + if let current = self.tintBadgeLayer { + tintBadgeLayer = current + } else { + tintBadgeLayer = SimpleLayer() + self.tintBadgeLayer = tintBadgeLayer + self.tintContentLayer.addSublayer(tintBadgeLayer) + + if let image = generateBadgeImage(color: .white) { + tintBadgeLayer.contents = image.cgImage + } + } + } else { + if let badgeLayer = self.badgeLayer { + self.badgeLayer = nil + badgeLayer.removeFromSuperlayer() + } + if let tintBadgeLayer = self.tintBadgeLayer { + self.tintBadgeLayer = nil + tintBadgeLayer.removeFromSuperlayer() + } + } + + let textFrame: CGRect + if subtitle == nil { + textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset + floor((constrainedSize.width - titleHorizontalOffset - (textSize.width + badgeSize.width)) / 2.0), y: textOffsetY), size: textSize) + } else { + textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset, y: textOffsetY), size: textSize) + } + self.textLayer.frame = textFrame + self.tintTextLayer.frame = textFrame + self.tintTextLayer.isHidden = !needsTintText + + if let badgeLayer = self.badgeLayer, let tintBadgeLayer = self.tintBadgeLayer { + badgeLayer.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 4.0, y: 0.0), size: badgeLayer.frame.size) + tintBadgeLayer.frame = badgeLayer.frame + } + + if isPremiumLocked { + let lockIconLayer: SimpleLayer + if let current = self.lockIconLayer { + lockIconLayer = current + } else { + lockIconLayer = SimpleLayer() + self.lockIconLayer = lockIconLayer + self.layer.addSublayer(lockIconLayer) + } + if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: color) { + let imageSize = image.size + lockIconLayer.contents = image.cgImage + lockIconLayer.frame = CGRect(origin: CGPoint(x: textFrame.minX - imageSize.width - 3.0, y: 2.0 + UIScreenPixel), size: imageSize) + } else { + lockIconLayer.contents = nil + } + + let tintLockIconLayer: SimpleLayer + if let current = self.tintLockIconLayer { + tintLockIconLayer = current + } else { + tintLockIconLayer = SimpleLayer() + self.tintLockIconLayer = tintLockIconLayer + self.tintContentLayer.addSublayer(tintLockIconLayer) + } + if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: .white) { + tintLockIconLayer.contents = image.cgImage + tintLockIconLayer.frame = lockIconLayer.frame + tintLockIconLayer.isHidden = !needsVibrancy + } else { + tintLockIconLayer.contents = nil + } + } else { + if let lockIconLayer = self.lockIconLayer { + self.lockIconLayer = nil + lockIconLayer.removeFromSuperlayer() + } + if let tintLockIconLayer = self.tintLockIconLayer { + self.tintLockIconLayer = nil + tintLockIconLayer.removeFromSuperlayer() + } + } + + let subtitleSize: CGSize + if let subtitle = subtitle { + var updateSubtitleContents: UIImage? + var updateTintSubtitleContents: UIImage? + if let currentSubtitleLayout = self.currentSubtitleLayout, currentSubtitleLayout.string == subtitle, currentSubtitleLayout.color == subtitleColor, currentSubtitleLayout.constrainedWidth == textConstrainedWidth { + subtitleSize = currentSubtitleLayout.size + } else { + let string = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: subtitleColor) + let whiteString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: .white) + let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + subtitleSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) + updateSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + string.draw(in: stringBounds) + + UIGraphicsPopContext() + }) + updateTintSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + whiteString.draw(in: stringBounds) + + UIGraphicsPopContext() + }) + self.currentSubtitleLayout = (subtitle, subtitleColor, textConstrainedWidth, subtitleSize) + } + + let subtitleLayer: SimpleLayer + if let current = self.subtitleLayer { + subtitleLayer = current + } else { + subtitleLayer = SimpleLayer() + self.subtitleLayer = subtitleLayer + self.layer.addSublayer(subtitleLayer) + } + + if let updateSubtitleContents = updateSubtitleContents { + subtitleLayer.contents = updateSubtitleContents.cgImage + } + + let tintSubtitleLayer: SimpleLayer + if let current = self.tintSubtitleLayer { + tintSubtitleLayer = current + } else { + tintSubtitleLayer = SimpleLayer() + self.tintSubtitleLayer = tintSubtitleLayer + self.tintContentLayer.addSublayer(tintSubtitleLayer) + } + tintSubtitleLayer.isHidden = !needsVibrancy + + if let updateTintSubtitleContents = updateTintSubtitleContents { + tintSubtitleLayer.contents = updateTintSubtitleContents.cgImage + } + + let subtitleFrame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.maxY + 1.0), size: subtitleSize) + subtitleLayer.frame = subtitleFrame + tintSubtitleLayer.frame = subtitleFrame + } else { + subtitleSize = CGSize() + if let subtitleLayer = self.subtitleLayer { + self.subtitleLayer = nil + subtitleLayer.removeFromSuperlayer() + } + if let tintSubtitleLayer = self.tintSubtitleLayer { + self.tintSubtitleLayer = nil + tintSubtitleLayer.removeFromSuperlayer() + } + } + + self.clearIconLayer?.frame = CGRect(origin: CGPoint(x: constrainedSize.width - clearSize.width, y: floorToScreenPixels((textSize.height - clearSize.height) / 2.0)), size: clearSize) + + var size: CGSize + size = CGSize(width: constrainedSize.width, height: constrainedSize.height) + + if let embeddedItems = embeddedItems { + let groupEmbeddedView: GroupEmbeddedView + if let current = self.groupEmbeddedView { + groupEmbeddedView = current + } else { + groupEmbeddedView = GroupEmbeddedView(performItemAction: self.performItemAction) + self.groupEmbeddedView = groupEmbeddedView + self.addSubview(groupEmbeddedView) + } + + let groupEmbeddedViewSize = CGSize(width: constrainedSize.width + insets.left + insets.right, height: 36.0) + groupEmbeddedView.frame = CGRect(origin: CGPoint(x: -insets.left, y: size.height - groupEmbeddedViewSize.height), size: groupEmbeddedViewSize) + groupEmbeddedView.update( + context: context, + theme: theme, + insets: insets, + size: groupEmbeddedViewSize, + items: embeddedItems, + isStickers: isStickers, + cache: cache, + renderer: renderer, + attemptSynchronousLoad: attemptSynchronousLoad + ) + } else { + if let groupEmbeddedView = self.groupEmbeddedView { + self.groupEmbeddedView = nil + groupEmbeddedView.removeFromSuperview() + } + } + + if let actionButtonSize = actionButtonSize, let actionButton = self.actionButton { + let actionButtonFrame = CGRect(origin: CGPoint(x: size.width - actionButtonSize.width, y: textFrame.minY + (actionButtonIsCompact ? 0.0 : 3.0)), size: actionButtonSize) + actionButton.bounds = CGRect(origin: CGPoint(), size: actionButtonFrame.size) + actionButton.center = actionButtonFrame.center + } + + if hasTopSeparator { + let separatorLayer: SimpleLayer + if let current = self.separatorLayer { + separatorLayer = current + } else { + separatorLayer = SimpleLayer() + self.separatorLayer = separatorLayer + self.layer.addSublayer(separatorLayer) + } + separatorLayer.backgroundColor = subtitleColor.cgColor + separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) + + let tintSeparatorLayer: SimpleLayer + if let current = self.tintSeparatorLayer { + tintSeparatorLayer = current + } else { + tintSeparatorLayer = SimpleLayer() + self.tintSeparatorLayer = tintSeparatorLayer + self.tintContentLayer.addSublayer(tintSeparatorLayer) + } + tintSeparatorLayer.backgroundColor = UIColor.white.cgColor + tintSeparatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) + + tintSeparatorLayer.isHidden = !needsVibrancy + } else { + if let separatorLayer = self.separatorLayer { + self.separatorLayer = separatorLayer + separatorLayer.removeFromSuperlayer() + } + if let tintSeparatorLayer = self.tintSeparatorLayer { + self.tintSeparatorLayer = tintSeparatorLayer + tintSeparatorLayer.removeFromSuperlayer() + } + } + + return (size, titleHorizontalOffset + textSize.width + clearWidth) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + + func tapGesture(point: CGPoint) -> Bool { + if let groupEmbeddedView = self.groupEmbeddedView { + return groupEmbeddedView.tapGesture(point: self.convert(point, to: groupEmbeddedView)) + } else { + return false + } + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/InlineFileIconLayer.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/InlineFileIconLayer.swift new file mode 100644 index 00000000000..7de681f13f0 --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/InlineFileIconLayer.swift @@ -0,0 +1,375 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData +import TelegramCore +import Postbox +import SwiftSignalKit +import MultiAnimationRenderer +import AnimationCache +import AccountContext +import TelegramUIPreferences +import GenerateStickerPlaceholderImage +import EmojiTextAttachmentView +import LottieAnimationCache + +public final class InlineFileIconLayer: MultiAnimationRenderTarget { + private final class Arguments { + let context: InlineFileIconLayer.Context + let userLocation: MediaResourceUserLocation + let file: TelegramMediaFile + let cache: AnimationCache + let renderer: MultiAnimationRenderer + let unique: Bool + let placeholderColor: UIColor + + let pointSize: CGSize + let pixelSize: CGSize + + init(context: InlineFileIconLayer.Context, userLocation: MediaResourceUserLocation, file: TelegramMediaFile, cache: AnimationCache, renderer: MultiAnimationRenderer, unique: Bool, placeholderColor: UIColor, pointSize: CGSize, pixelSize: CGSize) { + self.context = context + self.userLocation = userLocation + self.file = file + self.cache = cache + self.renderer = renderer + self.unique = unique + self.placeholderColor = placeholderColor + self.pointSize = pointSize + self.pixelSize = pixelSize + } + } + + public enum Context: Equatable { + public final class Custom: Equatable { + public let postbox: Postbox + public let energyUsageSettings: () -> EnergyUsageSettings + public let resolveInlineStickers: ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> + + public init(postbox: Postbox, energyUsageSettings: @escaping () -> EnergyUsageSettings, resolveInlineStickers: @escaping ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError>) { + self.postbox = postbox + self.energyUsageSettings = energyUsageSettings + self.resolveInlineStickers = resolveInlineStickers + } + + public static func ==(lhs: Custom, rhs: Custom) -> Bool { + if lhs.postbox !== rhs.postbox { + return false + } + return true + } + } + + case account(AccountContext) + case custom(Custom) + + var postbox: Postbox { + switch self { + case let .account(account): + return account.account.postbox + case let .custom(custom): + return custom.postbox + } + } + + var energyUsageSettings: EnergyUsageSettings { + switch self { + case let .account(account): + return account.sharedContext.energyUsageSettings + case let .custom(custom): + return custom.energyUsageSettings() + } + } + + func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> { + switch self { + case let .account(account): + return account.engine.stickers.resolveInlineStickers(fileIds: fileIds) + case let .custom(custom): + return custom.resolveInlineStickers(fileIds) + } + } + + public static func ==(lhs: Context, rhs: Context) -> Bool { + switch lhs { + case let .account(lhsContext): + if case let .account(rhsContext) = rhs, lhsContext === rhsContext { + return true + } else { + return false + } + case let .custom(custom): + if case .custom(custom) = rhs { + return true + } else { + return false + } + } + } + } + + public static let queue = Queue() + + public struct Key: Hashable { + public var id: Int64 + public var index: Int + + public init(id: Int64, index: Int) { + self.id = id + self.index = index + } + } + + private let arguments: Arguments? + + private var isDisplayingPlaceholder: Bool = false + private var didProcessTintColor: Bool = false + + public private(set) var file: TelegramMediaFile? + private var infoDisposable: Disposable? + private var disposable: Disposable? + private var fetchDisposable: Disposable? + private var loadDisposable: Disposable? + + private var _contentTintColor: UIColor? + public var contentTintColor: UIColor? { + get { + return self._contentTintColor + } + set(value) { + if self._contentTintColor != value { + self._contentTintColor = value + } + } + } + + private var _dynamicColor: UIColor? + public var dynamicColor: UIColor? { + get { + return self._dynamicColor + } + set(value) { + if self._dynamicColor != value { + self._dynamicColor = value + } + } + } + + private var currentLoopCount: Int = 0 + + private var isInHierarchyValue: Bool = false + + public convenience init( + context: AccountContext, + userLocation: MediaResourceUserLocation, + attemptSynchronousLoad: Bool, + file: TelegramMediaFile, + cache: AnimationCache, + renderer: MultiAnimationRenderer, + unique: Bool = false, + placeholderColor: UIColor, + pointSize: CGSize, + dynamicColor: UIColor? = nil + ) { + self.init( + context: .account(context), + userLocation: userLocation, + attemptSynchronousLoad: attemptSynchronousLoad, + file: file, + cache: cache, + renderer: renderer, + unique: unique, + placeholderColor: placeholderColor, + pointSize: pointSize, + dynamicColor: dynamicColor + ) + } + + public init( + context: InlineFileIconLayer.Context, + userLocation: MediaResourceUserLocation, + attemptSynchronousLoad: Bool, + file: TelegramMediaFile, + cache: AnimationCache, + renderer: MultiAnimationRenderer, + unique: Bool = false, + placeholderColor: UIColor, + pointSize: CGSize, + dynamicColor: UIColor? = nil + ) { + let scale = min(2.0, UIScreenScale) + + self.arguments = Arguments( + context: context, + userLocation: userLocation, + file: file, + cache: cache, + renderer: renderer, + unique: unique, + placeholderColor: placeholderColor, + pointSize: pointSize, + pixelSize: CGSize(width: pointSize.width * scale, height: pointSize.height * scale) + ) + + self._dynamicColor = dynamicColor + + super.init() + + self.updateFile(file: file, attemptSynchronousLoad: attemptSynchronousLoad) + } + + override public init(layer: Any) { + self.arguments = nil + + super.init(layer: layer) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.loadDisposable?.dispose() + self.infoDisposable?.dispose() + self.disposable?.dispose() + self.fetchDisposable?.dispose() + } + + override public func action(forKey event: String) -> CAAction? { + if event == kCAOnOrderIn { + self.isInHierarchyValue = true + } else if event == kCAOnOrderOut { + self.isInHierarchyValue = false + } + return nullAction + } + + private func updateFile(file: TelegramMediaFile, attemptSynchronousLoad: Bool) { + guard let arguments = self.arguments else { + return + } + + if self.file?.fileId == file.fileId { + return + } + + self.file = file + + if attemptSynchronousLoad { + if !arguments.renderer.loadFirstFrameSynchronously(target: self, cache: arguments.cache, itemId: file.resource.id.stringRepresentation, size: arguments.pixelSize) { + if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: arguments.pointSize, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: arguments.placeholderColor) { + self.contents = image.cgImage + self.isDisplayingPlaceholder = true + } + } + + self.loadAnimation() + } else { + let isTemplate = file.isCustomTemplateEmoji + + let pointSize = arguments.pointSize + let placeholderColor = arguments.placeholderColor + let isThumbnailCancelled = Atomic(value: false) + self.loadDisposable = arguments.renderer.loadFirstFrame( + target: self, + cache: arguments.cache, + itemId: file.resource.id.stringRepresentation, + size: arguments.pixelSize, + fetch: animationCacheFetchFile(postbox: arguments.context.postbox, userLocation: arguments.userLocation, userContentType: .sticker, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: true, customColor: isTemplate ? .white : nil), completion: { [weak self] result, isFinal in + if !result { + MultiAnimationRendererImpl.firstFrameQueue.async { + let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: pointSize, scale: min(2.0, UIScreenScale), imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: placeholderColor) + + DispatchQueue.main.async { + guard let strongSelf = self, !isThumbnailCancelled.with({ $0 }) else { + return + } + if let image = image { + strongSelf.contents = image.cgImage + strongSelf.isDisplayingPlaceholder = true + } + + if isFinal { + strongSelf.loadAnimation() + } + } + } + } else { + guard let strongSelf = self else { + return + } + let _ = isThumbnailCancelled.swap(true) + strongSelf.loadAnimation() + } + }) + } + } + + private func loadAnimation() { + /*guard let arguments = self.arguments else { + return + } + + guard let file = self.file else { + return + } + + let isTemplate = file.isCustomTemplateEmoji + + let context = arguments.context + if file.isAnimatedSticker || file.isVideoSticker || file.isVideoEmoji { + let keyframeOnly = arguments.pixelSize.width >= 120.0 + + self.disposable = arguments.renderer.add(target: self, cache: arguments.cache, itemId: file.resource.id.stringRepresentation, unique: arguments.unique, size: arguments.pixelSize, fetch: animationCacheFetchFile(postbox: arguments.context.postbox, userLocation: arguments.userLocation, userContentType: .sticker, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: keyframeOnly, customColor: isTemplate ? .white : nil)) + } else { + self.disposable = arguments.renderer.add(target: self, cache: arguments.cache, itemId: file.resource.id.stringRepresentation, unique: arguments.unique, size: arguments.pixelSize, fetch: { options in + let dataDisposable = context.postbox.mediaBox.resourceData(file.resource).start(next: { result in + guard result.complete else { + return + } + + cacheStillSticker(path: result.path, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, customColor: isTemplate ? .white : nil) + }) + + let fetchDisposable = freeMediaFileResourceInteractiveFetched(postbox: context.postbox, userLocation: arguments.userLocation, fileReference: .customEmoji(media: file), resource: file.resource).start() + + return ActionDisposable { + dataDisposable.dispose() + fetchDisposable.dispose() + } + }) + }*/ + } + + override public func updateDisplayPlaceholder(displayPlaceholder: Bool) { + if self.isDisplayingPlaceholder == displayPlaceholder { + return + } + self.isDisplayingPlaceholder = displayPlaceholder + } + + override public func transitionToContents(_ contents: AnyObject, didLoop: Bool) { + if self.isDisplayingPlaceholder { + self.isDisplayingPlaceholder = false + + if let current = self.contents { + let previousLayer = SimpleLayer() + previousLayer.contents = current + previousLayer.frame = self.frame + self.superlayer?.insertSublayer(previousLayer, below: self) + previousLayer.opacity = 0.0 + previousLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak previousLayer] _ in + previousLayer?.removeFromSuperlayer() + }) + + self.contents = contents + self.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) + } else { + self.contents = contents + self.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } else { + self.contents = contents + } + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/PassthroughComponents.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/PassthroughComponents.swift new file mode 100644 index 00000000000..eb60512e4eb --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/PassthroughComponents.swift @@ -0,0 +1,346 @@ +import Foundation +import UIKit +import Display +import ComponentFlow + +public class PassthroughLayer: CALayer { + public var mirrorLayer: CALayer? + + override init() { + super.init() + } + + override init(layer: Any) { + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public var position: CGPoint { + get { + return super.position + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.position = value + } + super.position = value + } + } + + override public var bounds: CGRect { + get { + return super.bounds + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.bounds = value + } + super.bounds = value + } + } + + override public var opacity: Float { + get { + return super.opacity + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.opacity = value + } + super.opacity = value + } + } + + override public var sublayerTransform: CATransform3D { + get { + return super.sublayerTransform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.sublayerTransform = value + } + super.sublayerTransform = value + } + } + + override public var transform: CATransform3D { + get { + return super.transform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.transform = value + } + super.transform = value + } + } + + override public func add(_ animation: CAAnimation, forKey key: String?) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.add(animation, forKey: key) + } + + super.add(animation, forKey: key) + } + + override public func removeAllAnimations() { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAllAnimations() + } + + super.removeAllAnimations() + } + + override public func removeAnimation(forKey: String) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAnimation(forKey: forKey) + } + + super.removeAnimation(forKey: forKey) + } +} + +open class PassthroughView: UIView { + override public static var layerClass: AnyClass { + return PassthroughLayer.self + } + + public let passthroughView: UIView + + override public init(frame: CGRect) { + self.passthroughView = UIView() + + super.init(frame: frame) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.passthroughView.layer + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class PassthroughShapeLayer: CAShapeLayer { + var mirrorLayer: CAShapeLayer? + + override init() { + super.init() + } + + override init(layer: Any) { + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var position: CGPoint { + get { + return super.position + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.position = value + } + super.position = value + } + } + + override var bounds: CGRect { + get { + return super.bounds + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.bounds = value + } + super.bounds = value + } + } + + override var opacity: Float { + get { + return super.opacity + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.opacity = value + } + super.opacity = value + } + } + + override var sublayerTransform: CATransform3D { + get { + return super.sublayerTransform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.sublayerTransform = value + } + super.sublayerTransform = value + } + } + + override var transform: CATransform3D { + get { + return super.transform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.transform = value + } + super.transform = value + } + } + + override var path: CGPath? { + get { + return super.path + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.path = value + } + super.path = value + } + } + + override var fillColor: CGColor? { + get { + return super.fillColor + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.fillColor = value + } + super.fillColor = value + } + } + + override var fillRule: CAShapeLayerFillRule { + get { + return super.fillRule + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.fillRule = value + } + super.fillRule = value + } + } + + override var strokeColor: CGColor? { + get { + return super.strokeColor + } set(value) { + /*if let mirrorLayer = self.mirrorLayer { + mirrorLayer.strokeColor = value + }*/ + super.strokeColor = value + } + } + + override var strokeStart: CGFloat { + get { + return super.strokeStart + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.strokeStart = value + } + super.strokeStart = value + } + } + + override var strokeEnd: CGFloat { + get { + return super.strokeEnd + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.strokeEnd = value + } + super.strokeEnd = value + } + } + + override var lineWidth: CGFloat { + get { + return super.lineWidth + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineWidth = value + } + super.lineWidth = value + } + } + + override var miterLimit: CGFloat { + get { + return super.miterLimit + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.miterLimit = value + } + super.miterLimit = value + } + } + + override var lineCap: CAShapeLayerLineCap { + get { + return super.lineCap + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineCap = value + } + super.lineCap = value + } + } + + override var lineJoin: CAShapeLayerLineJoin { + get { + return super.lineJoin + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineJoin = value + } + super.lineJoin = value + } + } + + override var lineDashPhase: CGFloat { + get { + return super.lineDashPhase + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineDashPhase = value + } + super.lineDashPhase = value + } + } + + override var lineDashPattern: [NSNumber]? { + get { + return super.lineDashPattern + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineDashPattern = value + } + super.lineDashPattern = value + } + } + + override func add(_ animation: CAAnimation, forKey key: String?) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.add(animation, forKey: key) + } + + super.add(animation, forKey: key) + } + + override func removeAllAnimations() { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAllAnimations() + } + + super.removeAllAnimations() + } + + override func removeAnimation(forKey: String) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAnimation(forKey: forKey) + } + + super.removeAnimation(forKey: forKey) + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/PremiumBadgeView.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/PremiumBadgeView.swift new file mode 100644 index 00000000000..6e0a0497d23 --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/PremiumBadgeView.swift @@ -0,0 +1,140 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramCore + +private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) +private let featuredBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeAdd"), color: .white) +private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) + +private let itemBadgeTextFont: UIFont = { + return Font.regular(10.0) +}() + +final class PremiumBadgeView: UIView { + private let context: AccountContext + + private var badge: EmojiKeyboardItemLayer.Badge? + + let contentLayer: SimpleLayer + private let overlayColorLayer: SimpleLayer + private let iconLayer: SimpleLayer + private var customFileLayer: InlineFileIconLayer? + + init(context: AccountContext) { + self.context = context + + self.contentLayer = SimpleLayer() + self.contentLayer.contentsGravity = .resize + self.contentLayer.masksToBounds = true + + self.overlayColorLayer = SimpleLayer() + self.overlayColorLayer.masksToBounds = true + + self.iconLayer = SimpleLayer() + + super.init(frame: CGRect()) + + self.layer.addSublayer(self.contentLayer) + self.layer.addSublayer(self.overlayColorLayer) + self.layer.addSublayer(self.iconLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(transition: Transition, badge: EmojiKeyboardItemLayer.Badge, backgroundColor: UIColor, size: CGSize) { + if self.badge != badge { + self.badge = badge + + switch badge { + case .premium: + self.iconLayer.contents = premiumBadgeIcon?.cgImage + case .featured: + self.iconLayer.contents = featuredBadgeIcon?.cgImage + case .locked: + self.iconLayer.contents = lockedBadgeIcon?.cgImage + case let .text(text): + let string = NSAttributedString(string: text, font: itemBadgeTextFont) + let size = CGSize(width: 12.0, height: 12.0) + let stringBounds = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + let image = generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + string.draw(at: CGPoint(x: floor((size.width - stringBounds.width) * 0.5), y: floor((size.height - stringBounds.height) * 0.5))) + UIGraphicsPopContext() + }) + self.iconLayer.contents = image?.cgImage + case .customFile: + self.iconLayer.contents = nil + } + + if case let .customFile(customFile) = badge { + let customFileLayer: InlineFileIconLayer + if let current = self.customFileLayer { + customFileLayer = current + } else { + customFileLayer = InlineFileIconLayer( + context: self.context, + userLocation: .other, + attemptSynchronousLoad: false, + file: customFile, + cache: self.context.animationCache, + renderer: self.context.animationRenderer, + unique: false, + placeholderColor: .clear, + pointSize: CGSize(width: 18.0, height: 18.0), + dynamicColor: nil + ) + self.customFileLayer = customFileLayer + self.layer.addSublayer(customFileLayer) + } + let _ = customFileLayer + } else { + if let customFileLayer = self.customFileLayer { + self.customFileLayer = nil + customFileLayer.removeFromSuperlayer() + } + } + } + + let iconInset: CGFloat + switch badge { + case .premium: + iconInset = 2.0 + case .featured: + iconInset = 0.0 + case .locked: + iconInset = 0.0 + case .text, .customFile: + iconInset = 0.0 + } + + switch badge { + case .text, .customFile: + self.contentLayer.isHidden = true + self.overlayColorLayer.isHidden = true + default: + self.contentLayer.isHidden = false + self.overlayColorLayer.isHidden = false + } + + self.overlayColorLayer.backgroundColor = backgroundColor.cgColor + + transition.setFrame(layer: self.contentLayer, frame: CGRect(origin: CGPoint(), size: size)) + transition.setCornerRadius(layer: self.contentLayer, cornerRadius: min(size.width / 2.0, size.height / 2.0)) + + transition.setFrame(layer: self.overlayColorLayer, frame: CGRect(origin: CGPoint(), size: size)) + transition.setCornerRadius(layer: self.overlayColorLayer, cornerRadius: min(size.width / 2.0, size.height / 2.0)) + + transition.setFrame(layer: self.iconLayer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: iconInset, dy: iconInset)) + + if let customFileLayer = self.customFileLayer { + let iconSize = CGSize(width: 18.0, height: 18.0) + transition.setFrame(layer: customFileLayer, frame: CGRect(origin: CGPoint(), size: iconSize)) + } + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/WarpView.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/WarpView.swift new file mode 100644 index 00000000000..7ccea1a793e --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/WarpView.swift @@ -0,0 +1,138 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData + +final class WarpView: UIView { + private final class WarpPartView: UIView { + let cloneView: PortalView + + init?(contentView: PortalSourceView) { + guard let cloneView = PortalView(matchPosition: false) else { + return nil + } + self.cloneView = cloneView + + super.init(frame: CGRect()) + + self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0) + + self.clipsToBounds = true + self.addSubview(cloneView.view) + contentView.addPortal(view: cloneView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(containerSize: CGSize, rect: CGRect, transition: Transition) { + transition.setFrame(view: self.cloneView.view, frame: CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: CGSize(width: containerSize.width, height: containerSize.height))) + } + } + + let contentView: PortalSourceView + + private let clippingView: UIView + + private var warpViews: [WarpPartView] = [] + private let warpMaskContainer: UIView + private let warpMaskGradientLayer: SimpleGradientLayer + + override init(frame: CGRect) { + self.contentView = PortalSourceView() + self.clippingView = UIView() + + self.warpMaskContainer = UIView() + self.warpMaskGradientLayer = SimpleGradientLayer() + self.warpMaskContainer.layer.mask = self.warpMaskGradientLayer + + super.init(frame: frame) + + self.clippingView.addSubview(self.contentView) + + self.clippingView.clipsToBounds = true + self.addSubview(self.clippingView) + self.addSubview(self.warpMaskContainer) + + for _ in 0 ..< 8 { + if let warpView = WarpPartView(contentView: self.contentView) { + self.warpViews.append(warpView) + self.warpMaskContainer.addSubview(warpView) + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(size: CGSize, topInset: CGFloat, warpHeight: CGFloat, theme: PresentationTheme, transition: Transition) { + transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size)) + + let allItemsHeight = warpHeight * 0.5 + for i in 0 ..< self.warpViews.count { + let itemHeight = warpHeight / CGFloat(self.warpViews.count) + let itemFraction = CGFloat(i + 1) / CGFloat(self.warpViews.count) + let _ = itemHeight + + let da = CGFloat.pi * 0.5 / CGFloat(self.warpViews.count) + let alpha = CGFloat.pi * 0.5 - itemFraction * CGFloat.pi * 0.5 + let endPoint = CGPoint(x: cos(alpha), y: sin(alpha)) + let prevAngle = alpha + da + let prevPt = CGPoint(x: cos(prevAngle), y: sin(prevAngle)) + var angle: CGFloat + angle = -atan2(endPoint.y - prevPt.y, endPoint.x - prevPt.x) + + let itemLengthVector = CGPoint(x: endPoint.x - prevPt.x, y: endPoint.y - prevPt.y) + let itemLength = sqrt(itemLengthVector.x * itemLengthVector.x + itemLengthVector.y * itemLengthVector.y) * warpHeight * 0.5 + let _ = itemLength + + var transform: CATransform3D + transform = CATransform3DIdentity + transform.m34 = 1.0 / 240.0 + + transform = CATransform3DTranslate(transform, 0.0, prevPt.x * allItemsHeight, (1.0 - prevPt.y) * allItemsHeight) + transform = CATransform3DRotate(transform, angle, 1.0, 0.0, 0.0) + + let positionY = size.height - allItemsHeight + 4.0 + CGFloat(i) * itemLength + let rect = CGRect(origin: CGPoint(x: 0.0, y: positionY), size: CGSize(width: size.width, height: itemLength)) + transition.setPosition(view: self.warpViews[i], position: CGPoint(x: rect.midX, y: 4.0)) + transition.setBounds(view: self.warpViews[i], bounds: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: itemLength))) + transition.setTransform(view: self.warpViews[i], transform: transform) + self.warpViews[i].update(containerSize: size, rect: rect, transition: transition) + } + + let clippingTopInset: CGFloat = topInset + let frame = CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: CGSize(width: size.width, height: -clippingTopInset + size.height - 21.0)) + transition.setPosition(view: self.clippingView, position: frame.center) + transition.setBounds(view: self.clippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: frame.size)) + self.clippingView.clipsToBounds = true + + transition.setFrame(view: self.warpMaskContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - allItemsHeight), size: CGSize(width: size.width, height: allItemsHeight))) + + var locations: [NSNumber] = [] + var colors: [CGColor] = [] + let numStops = 6 + for i in 0 ..< numStops { + let step = CGFloat(i) / CGFloat(numStops - 1) + locations.append(step as NSNumber) + colors.append(UIColor.black.withAlphaComponent(1.0 - step * step).cgColor) + } + + let gradientHeight: CGFloat = 6.0 + self.warpMaskGradientLayer.startPoint = CGPoint(x: 0.0, y: (allItemsHeight - gradientHeight) / allItemsHeight) + self.warpMaskGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0) + + self.warpMaskGradientLayer.locations = locations + self.warpMaskGradientLayer.colors = colors + self.warpMaskGradientLayer.type = .axial + + transition.setFrame(layer: self.warpMaskGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: allItemsHeight))) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return self.contentView.hitTest(point, with: event) + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboardGifContent/BUILD b/submodules/TelegramUI/Components/EntityKeyboardGifContent/BUILD index cc15d62131a..f5f7bcdd1fe 100644 --- a/submodules/TelegramUI/Components/EntityKeyboardGifContent/BUILD +++ b/submodules/TelegramUI/Components/EntityKeyboardGifContent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/BUILD b/submodules/TelegramUI/Components/ForumCreateTopicScreen/BUILD index bb370549e62..1792d3cb742 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/BUILD +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/FullScreenEffectView/BUILD b/submodules/TelegramUI/Components/FullScreenEffectView/BUILD index 75349b10532..810b2f65027 100644 --- a/submodules/TelegramUI/Components/FullScreenEffectView/BUILD +++ b/submodules/TelegramUI/Components/FullScreenEffectView/BUILD @@ -55,7 +55,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD index 8b9993c9332..cb1d0ad36e9 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/TelegramUI/Components/InteractiveTextComponent/BUILD b/submodules/TelegramUI/Components/InteractiveTextComponent/BUILD new file mode 100644 index 00000000000..86bdcf4b766 --- /dev/null +++ b/submodules/TelegramUI/Components/InteractiveTextComponent/BUILD @@ -0,0 +1,29 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "InteractiveTextComponent", + module_name = "InteractiveTextComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/AppBundle", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/TelegramUI/Components/AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer", + "//submodules/TelegramCore", + "//submodules/TelegramUI/Components/EmojiTextAttachmentView", + "//submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView", + "//submodules/InvisibleInkDustNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift b/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift new file mode 100644 index 00000000000..78b09b15973 --- /dev/null +++ b/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift @@ -0,0 +1,2374 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import CoreText +import AppBundle +import ComponentFlow +import TextFormat +import MessageInlineBlockBackgroundView + +private let defaultFont = UIFont.systemFont(ofSize: 15.0) + +private let quoteIcon: UIImage = { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ReplyQuoteIcon"), color: .white)! +}() + +private let codeIcon: UIImage = { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/TextCodeIcon"), color: .white)! +}() + +private let expandArrowIcon: UIImage = { + return generateTintedImage(image: UIImage(bundleImageName: "Item List/ExpandingItemVerticalRegularArrow"), color: .white)! +}() + +private func generateBlockMaskImage() -> UIImage { + let size = CGSize(width: 36.0 + 20.0, height: 36.0) + return generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + + context.setFillColor(UIColor.black.cgColor) + context.fill(CGRect(origin: .zero, size: size)) + + let colorSpace = CGColorSpaceCreateDeviceRGB() + + var locations: [CGFloat] = [0.0, 0.5, 1.0] + let colors: [CGColor] = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.cgColor] + + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + + context.setBlendMode(.copy) + context.drawRadialGradient(gradient, startCenter: CGPoint(x: size.width - 20.0, y: size.height), startRadius: 0.0, endCenter: CGPoint(x: size.width - 20.0, y: size.height), endRadius: 34.0, options: CGGradientDrawingOptions()) + })!.resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: size.height - 1.0, right: size.width - 1.0), resizingMode: .stretch) +} + +private let expandableBlockMaskImage: UIImage = { + return generateBlockMaskImage() +}() + +private final class InteractiveTextNodeStrikethrough { + let range: NSRange + let frame: CGRect + + init(range: NSRange, frame: CGRect) { + self.range = range + self.frame = frame + } +} + +private final class InteractiveTextNodeSpoiler { + let range: NSRange + let frame: CGRect + + init(range: NSRange, frame: CGRect) { + self.range = range + self.frame = frame + } +} + +private final class InteractiveTextNodeEmbeddedItem { + let range: NSRange + let frame: CGRect + let item: AnyHashable + let isHiddenBySpoiler: Bool + + init(range: NSRange, frame: CGRect, item: AnyHashable, isHiddenBySpoiler: Bool) { + self.range = range + self.frame = frame + self.item = item + self.isHiddenBySpoiler = isHiddenBySpoiler + } +} + +private final class InteractiveTextNodeAttachment { + let range: NSRange + let frame: CGRect + let attachment: UIImage + + init(range: NSRange, frame: CGRect, attachment: UIImage) { + self.range = range + self.frame = frame + self.attachment = attachment + } +} + +private final class InteractiveTextNodeLine { + let line: CTLine + var frame: CGRect + let ascent: CGFloat + let descent: CGFloat + let range: NSRange? + let isRTL: Bool + var strikethroughs: [InteractiveTextNodeStrikethrough] + var spoilers: [InteractiveTextNodeSpoiler] + var spoilerWords: [InteractiveTextNodeSpoiler] + var embeddedItems: [InteractiveTextNodeEmbeddedItem] + var attachments: [InteractiveTextNodeAttachment] + let additionalTrailingLine: (CTLine, Double)? + + init(line: CTLine, frame: CGRect, ascent: CGFloat, descent: CGFloat, range: NSRange?, isRTL: Bool, strikethroughs: [InteractiveTextNodeStrikethrough], spoilers: [InteractiveTextNodeSpoiler], spoilerWords: [InteractiveTextNodeSpoiler], embeddedItems: [InteractiveTextNodeEmbeddedItem], attachments: [InteractiveTextNodeAttachment], additionalTrailingLine: (CTLine, Double)?) { + self.line = line + self.frame = frame + self.ascent = ascent + self.descent = descent + self.range = range + self.isRTL = isRTL + self.strikethroughs = strikethroughs + self.spoilers = spoilers + self.spoilerWords = spoilerWords + self.embeddedItems = embeddedItems + self.attachments = attachments + self.additionalTrailingLine = additionalTrailingLine + } +} + +private final class InteractiveTextNodeBlockQuote { + let id: Int + let frame: CGRect + let data: TextNodeBlockQuoteData + let tintColor: UIColor + let secondaryTintColor: UIColor? + let tertiaryTintColor: UIColor? + let backgroundColor: UIColor + let isCollapsed: Bool? + + init(id: Int, frame: CGRect, data: TextNodeBlockQuoteData, tintColor: UIColor, secondaryTintColor: UIColor?, tertiaryTintColor: UIColor?, backgroundColor: UIColor, isCollapsed: Bool?) { + self.id = id + self.frame = frame + self.data = data + self.tintColor = tintColor + self.secondaryTintColor = secondaryTintColor + self.tertiaryTintColor = tertiaryTintColor + self.backgroundColor = backgroundColor + self.isCollapsed = isCollapsed + } +} + +private func displayLineFrame(frame: CGRect, isRTL: Bool, boundingRect: CGRect, cutout: TextNodeCutout?) -> CGRect { + if frame.width.isEqual(to: boundingRect.width) { + return frame + } + var lineFrame = frame + let intersectionFrame = lineFrame.offsetBy(dx: 0.0, dy: 0.0) + + if isRTL { + lineFrame.origin.x = max(0.0, floor(boundingRect.width - lineFrame.size.width)) + if let topRight = cutout?.topRight { + let topRightRect = CGRect(origin: CGPoint(x: boundingRect.width - topRight.width, y: 0.0), size: topRight) + if intersectionFrame.intersects(topRightRect) { + lineFrame.origin.x -= topRight.width + return lineFrame + } + } + if let bottomRight = cutout?.bottomRight { + let bottomRightRect = CGRect(origin: CGPoint(x: boundingRect.width - bottomRight.width, y: boundingRect.height - bottomRight.height), size: bottomRight) + if intersectionFrame.intersects(bottomRightRect) { + lineFrame.origin.x -= bottomRight.width + return lineFrame + } + } + } + return lineFrame +} + +public final class InteractiveTextNodeSegment { + fileprivate let lines: [InteractiveTextNodeLine] + public let visibleLineCount: Int + fileprivate let tintColor: UIColor? + fileprivate let secondaryTintColor: UIColor? + fileprivate let tertiaryTintColor: UIColor? + fileprivate let blockQuote: InteractiveTextNodeBlockQuote? + public let hasRTL: Bool + public let spoilers: [(NSRange, CGRect)] + public let spoilerWords: [(NSRange, CGRect)] + public let embeddedItems: [InteractiveTextNodeLayout.EmbeddedItem] + + public var hasBlockQuote: Bool { + return self.blockQuote != nil + } + + fileprivate init( + lines: [InteractiveTextNodeLine], + visibleLineCount: Int, + tintColor: UIColor?, + secondaryTintColor: UIColor?, + tertiaryTintColor: UIColor?, + blockQuote: InteractiveTextNodeBlockQuote?, + attributedString: NSAttributedString?, + resolvedAlignment: NSTextAlignment, + layoutSize: CGSize + ) { + self.lines = lines + self.visibleLineCount = visibleLineCount + self.tintColor = tintColor + self.secondaryTintColor = secondaryTintColor + self.tertiaryTintColor = tertiaryTintColor + self.blockQuote = blockQuote + + var hasRTL = false + var spoilers: [(NSRange, CGRect)] = [] + var spoilerWords: [(NSRange, CGRect)] = [] + var embeddedItems: [InteractiveTextNodeLayout.EmbeddedItem] = [] + + for line in self.lines { + if line.isRTL { + hasRTL = true + } + + let lineFrame: CGRect + switch resolvedAlignment { + case .center: + lineFrame = CGRect(origin: CGPoint(x: floor((layoutSize.width - line.frame.size.width) / 2.0), y: line.frame.minY), size: line.frame.size) + case .right: + lineFrame = CGRect(origin: CGPoint(x: layoutSize.width - line.frame.size.width, y: line.frame.minY), size: line.frame.size) + default: + lineFrame = displayLineFrame(frame: line.frame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: layoutSize), cutout: nil) + } + + spoilers.append(contentsOf: line.spoilers.map { ( $0.range, $0.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) }) + spoilerWords.append(contentsOf: line.spoilerWords.map { ( $0.range, $0.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) }) + for embeddedItem in line.embeddedItems { + var textColor: UIColor? + if let attributedString, embeddedItem.range.location < attributedString.length { + if let color = attributedString.attribute(.foregroundColor, at: embeddedItem.range.location, effectiveRange: nil) as? UIColor { + textColor = color + } + if textColor == nil { + if let color = attributedString.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? UIColor { + textColor = color + } + } + } + embeddedItems.append(InteractiveTextNodeLayout.EmbeddedItem(range: embeddedItem.range, rect: embeddedItem.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY), value: embeddedItem.item, textColor: textColor ?? .black, isHiddenBySpoiler: embeddedItem.isHiddenBySpoiler)) + } + } + + self.hasRTL = hasRTL + self.spoilers = spoilers + self.spoilerWords = spoilerWords + self.embeddedItems = embeddedItems + } +} + +public final class InteractiveTextNodeLayoutArguments { + public let attributedString: NSAttributedString? + public let backgroundColor: UIColor? + public let minimumNumberOfLines: Int + public let maximumNumberOfLines: Int + public let truncationType: CTLineTruncationType + public let constrainedSize: CGSize + public let alignment: NSTextAlignment + public let verticalAlignment: TextVerticalAlignment + public let lineSpacing: CGFloat + public let cutout: TextNodeCutout? + public let insets: UIEdgeInsets + public let lineColor: UIColor? + public let textShadowColor: UIColor? + public let textShadowBlur: CGFloat? + public let textStroke: (UIColor, CGFloat)? + public let displayContentsUnderSpoilers: Bool + public let customTruncationToken: NSAttributedString? + public let expandedBlocks: Set + + public init( + attributedString: NSAttributedString?, + backgroundColor: UIColor? = nil, + minimumNumberOfLines: Int = 0, + maximumNumberOfLines: Int, + truncationType: CTLineTruncationType, + constrainedSize: CGSize, + alignment: NSTextAlignment = .natural, + verticalAlignment: TextVerticalAlignment = .top, + lineSpacing: CGFloat = 0.12, + cutout: TextNodeCutout? = nil, + insets: UIEdgeInsets = UIEdgeInsets(), + lineColor: UIColor? = nil, + textShadowColor: UIColor? = nil, + textShadowBlur: CGFloat? = nil, + textStroke: (UIColor, CGFloat)? = nil, + displayContentsUnderSpoilers: Bool = false, + customTruncationToken: NSAttributedString? = nil, + expandedBlocks: Set = Set() + ) { + self.attributedString = attributedString + self.backgroundColor = backgroundColor + self.minimumNumberOfLines = minimumNumberOfLines + self.maximumNumberOfLines = maximumNumberOfLines + self.truncationType = truncationType + self.constrainedSize = constrainedSize + self.alignment = alignment + self.verticalAlignment = verticalAlignment + self.lineSpacing = lineSpacing + self.cutout = cutout + self.insets = insets + self.lineColor = lineColor + self.textShadowColor = textShadowColor + self.textShadowBlur = textShadowBlur + self.textStroke = textStroke + self.displayContentsUnderSpoilers = displayContentsUnderSpoilers + self.customTruncationToken = customTruncationToken + self.expandedBlocks = expandedBlocks + } + + public func withAttributedString(_ attributedString: NSAttributedString?) -> InteractiveTextNodeLayoutArguments { + return InteractiveTextNodeLayoutArguments( + attributedString: attributedString, + backgroundColor: self.backgroundColor, + minimumNumberOfLines: self.minimumNumberOfLines, + maximumNumberOfLines: self.maximumNumberOfLines, + truncationType: self.truncationType, + constrainedSize: self.constrainedSize, + alignment: self.alignment, + verticalAlignment: self.verticalAlignment, + lineSpacing: self.lineSpacing, + cutout: self.cutout, + insets: self.insets, + lineColor: self.lineColor, + textShadowColor: self.textShadowColor, + textShadowBlur: self.textShadowBlur, + textStroke: self.textStroke, + displayContentsUnderSpoilers: self.displayContentsUnderSpoilers, + customTruncationToken: self.customTruncationToken, + expandedBlocks: self.expandedBlocks + ) + } +} + +public final class InteractiveTextNodeLayout: NSObject { + public final class EmbeddedItem: Equatable { + public let range: NSRange + public let rect: CGRect + public let value: AnyHashable + public let textColor: UIColor + public let isHiddenBySpoiler: Bool + + public init(range: NSRange, rect: CGRect, value: AnyHashable, textColor: UIColor, isHiddenBySpoiler: Bool) { + self.range = range + self.rect = rect + self.value = value + self.textColor = textColor + self.isHiddenBySpoiler = isHiddenBySpoiler + } + + public static func ==(lhs: EmbeddedItem, rhs: EmbeddedItem) -> Bool { + if lhs.range != rhs.range { + return false + } + if lhs.rect != rhs.rect { + return false + } + if lhs.value != rhs.value { + return false + } + if lhs.textColor != rhs.textColor { + return false + } + if lhs.isHiddenBySpoiler != rhs.isHiddenBySpoiler { + return false + } + return true + } + } + + public let attributedString: NSAttributedString? + fileprivate let maximumNumberOfLines: Int + fileprivate let truncationType: CTLineTruncationType + fileprivate let backgroundColor: UIColor? + fileprivate let constrainedSize: CGSize + fileprivate let explicitAlignment: NSTextAlignment + fileprivate let resolvedAlignment: NSTextAlignment + fileprivate let verticalAlignment: TextVerticalAlignment + fileprivate let lineSpacing: CGFloat + fileprivate let cutout: TextNodeCutout? + public let insets: UIEdgeInsets + public let size: CGSize + public let rawTextSize: CGSize + public let truncated: Bool + fileprivate let firstLineOffset: CGFloat + public let segments: [InteractiveTextNodeSegment] + fileprivate let lineColor: UIColor? + fileprivate let textShadowColor: UIColor? + fileprivate let textShadowBlur: CGFloat? + fileprivate let textStroke: (UIColor, CGFloat)? + public let displayContentsUnderSpoilers: Bool + fileprivate let expandedBlocks: Set + + fileprivate init( + attributedString: NSAttributedString?, + maximumNumberOfLines: Int, + truncationType: CTLineTruncationType, + constrainedSize: CGSize, + explicitAlignment: NSTextAlignment, + resolvedAlignment: NSTextAlignment, + verticalAlignment: TextVerticalAlignment, + lineSpacing: CGFloat, + cutout: TextNodeCutout?, + insets: UIEdgeInsets, + size: CGSize, + rawTextSize: CGSize, + truncated: Bool, + firstLineOffset: CGFloat, + segments: [InteractiveTextNodeSegment], + backgroundColor: UIColor?, + lineColor: UIColor?, + textShadowColor: UIColor?, + textShadowBlur: CGFloat?, + textStroke: (UIColor, CGFloat)?, + displayContentsUnderSpoilers: Bool, + expandedBlocks: Set + ) { + self.attributedString = attributedString + self.maximumNumberOfLines = maximumNumberOfLines + self.truncationType = truncationType + self.constrainedSize = constrainedSize + self.explicitAlignment = explicitAlignment + self.resolvedAlignment = resolvedAlignment + self.verticalAlignment = verticalAlignment + self.lineSpacing = lineSpacing + self.cutout = cutout + self.insets = insets + self.size = size + self.rawTextSize = rawTextSize + self.truncated = truncated + self.firstLineOffset = firstLineOffset + self.segments = segments + self.backgroundColor = backgroundColor + self.lineColor = lineColor + self.textShadowColor = textShadowColor + self.textShadowBlur = textShadowBlur + self.textStroke = textStroke + self.displayContentsUnderSpoilers = displayContentsUnderSpoilers + self.expandedBlocks = expandedBlocks + } + + func withUpdatedDisplayContentsUnderSpoilers(_ displayContentsUnderSpoilers: Bool) -> InteractiveTextNodeLayout { + return InteractiveTextNodeLayout( + attributedString: self.attributedString, + maximumNumberOfLines: self.maximumNumberOfLines, + truncationType: self.truncationType, + constrainedSize: self.constrainedSize, + explicitAlignment: self.explicitAlignment, + resolvedAlignment: self.resolvedAlignment, + verticalAlignment: self.verticalAlignment, + lineSpacing: self.lineSpacing, + cutout: self.cutout, + insets: self.insets, + size: self.size, + rawTextSize: self.rawTextSize, + truncated: self.truncated, + firstLineOffset: self.firstLineOffset, + segments: self.segments, + backgroundColor: self.backgroundColor, + lineColor: self.lineColor, + textShadowColor: self.textShadowColor, + textShadowBlur: self.textShadowBlur, + textStroke: self.textStroke, + displayContentsUnderSpoilers: displayContentsUnderSpoilers, + expandedBlocks: self.expandedBlocks + ) + } + + public var numberOfLines: Int { + var result = 0 + for segment in self.segments { + result += segment.lines.count + } + return result + } + + public var trailingLineWidth: CGFloat { + if let lastSegment = self.segments.last, let lastLine = lastSegment.lines.last { + var width = lastLine.frame.maxX + + if let blockQuote = lastSegment.blockQuote { + if lastLine.frame.intersects(blockQuote.frame) { + width = max(width, ceil(blockQuote.frame.maxX) + 2.0) + } + } + return width + } else { + return 0.0 + } + } + + public var trailingLineIsRTL: Bool { + if let lastSegment = self.segments.last, let lastLine = lastSegment.lines.last { + return lastLine.isRTL + } else { + return false + } + } + + public func attributesAtPoint(_ point: CGPoint, orNearest: Bool) -> (Int, [NSAttributedString.Key: Any])? { + if let attributedString = self.attributedString { + let transformedPoint = CGPoint(x: point.x - self.insets.left, y: point.y - self.insets.top) + if orNearest { + var segmentIndex = -1 + var closestLine: ((segment: Int, line: Int), CGRect, CGFloat)? + for segment in self.segments { + segmentIndex += 1 + var lineIndex = -1 + for line in segment.lines.prefix(segment.visibleLineCount) { + lineIndex += 1 + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y), size: line.frame.size) + switch self.resolvedAlignment { + case .center: + lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) + case .natural, .left: + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) + case .right: + lineFrame.origin.x = self.size.width - lineFrame.size.width + default: + break + } + + let currentDistance = (lineFrame.center.y - point.y) * (lineFrame.center.y - point.y) + if let current = closestLine { + if current.2 > currentDistance { + closestLine = ((segmentIndex, lineIndex), lineFrame, currentDistance) + } + } else { + closestLine = ((segmentIndex, lineIndex), lineFrame, currentDistance) + } + } + } + + if let (index, lineFrame, _) = closestLine { + let line = self.segments[index.segment].lines[index.line] + + let lineRange = CTLineGetStringRange(line.line) + var index: Int + if transformedPoint.x <= lineFrame.minX { + index = lineRange.location + } else if transformedPoint.x >= lineFrame.maxX { + index = lineRange.location + lineRange.length + } else { + index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: floor(lineFrame.height / 2.0))) + if index != 0 { + var glyphStart: CGFloat = 0.0 + CTLineGetOffsetForStringIndex(line.line, index, &glyphStart) + if transformedPoint.x < glyphStart { + var closestLowerIndex: Int? + let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray + if glyphRuns.count != 0 { + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + for i in 0 ..< glyphCount { + var glyphIndex: CFIndex = 0 + CTRunGetStringIndices(run, CFRangeMake(i, 1), &glyphIndex) + if glyphIndex < index { + if let closestLowerIndexValue = closestLowerIndex { + if closestLowerIndexValue < glyphIndex { + closestLowerIndex = glyphIndex + } + } else { + closestLowerIndex = glyphIndex + } + } + } + } + } + if let closestLowerIndex = closestLowerIndex { + index = closestLowerIndex + } + } + } + } + return (index, [:]) + } + } + var segmentIndex = -1 + for segment in self.segments { + segmentIndex += 1 + var lineIndex = -1 + for line in segment.lines.prefix(segment.visibleLineCount) { + lineIndex += 1 + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y), size: line.frame.size) + switch self.resolvedAlignment { + case .center: + lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) + case .natural, .left: + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) + case .right: + lineFrame.origin.x = self.size.width - lineFrame.size.width + default: + break + } + if lineFrame.contains(transformedPoint) { + var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) + if index == attributedString.length { + var closestLowerIndex: Int? + let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray + if glyphRuns.count != 0 { + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + for i in 0 ..< glyphCount { + var glyphIndex: CFIndex = 0 + CTRunGetStringIndices(run, CFRangeMake(i, 1), &glyphIndex) + if glyphIndex < index { + if let closestLowerIndexValue = closestLowerIndex { + if closestLowerIndexValue < glyphIndex { + closestLowerIndex = glyphIndex + } + } else { + closestLowerIndex = glyphIndex + } + } + } + } + } + if let closestLowerIndex = closestLowerIndex { + index = closestLowerIndex + } + } else if index != 0 { + var glyphStart: CGFloat = 0.0 + CTLineGetOffsetForStringIndex(line.line, index, &glyphStart) + if transformedPoint.x < glyphStart { + var closestLowerIndex: Int? + let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray + if glyphRuns.count != 0 { + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + for i in 0 ..< glyphCount { + var glyphIndex: CFIndex = 0 + CTRunGetStringIndices(run, CFRangeMake(i, 1), &glyphIndex) + if glyphIndex < index { + if let closestLowerIndexValue = closestLowerIndex { + if closestLowerIndexValue < glyphIndex { + closestLowerIndex = glyphIndex + } + } else { + closestLowerIndex = glyphIndex + } + } + } + } + } + if let closestLowerIndex = closestLowerIndex { + index = closestLowerIndex + } + } + } + if index >= 0 && index < attributedString.length { + if let range = line.range, index < range.location + range.length { + return (index, attributedString.attributes(at: index, effectiveRange: nil)) + } + } + break + } + } + } + + segmentIndex = -1 + for segment in self.segments { + segmentIndex += 1 + var lineIndex = -1 + for line in segment.lines.prefix(segment.visibleLineCount) { + lineIndex += 1 + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y), size: line.frame.size) + switch self.resolvedAlignment { + case .center: + lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) + case .natural: + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) + case .right: + lineFrame.origin.x = self.size.width - lineFrame.size.width + default: + break + } + if lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.size.height).insetBy(dx: -3.0, dy: -3.0).contains(transformedPoint) { + var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) + if index == attributedString.length { + var closestLowerIndex: Int? + let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray + if glyphRuns.count != 0 { + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + for i in 0 ..< glyphCount { + var glyphIndex: CFIndex = 0 + CTRunGetStringIndices(run, CFRangeMake(i, 1), &glyphIndex) + if glyphIndex < index { + if let closestLowerIndexValue = closestLowerIndex { + if closestLowerIndexValue < glyphIndex { + closestLowerIndex = glyphIndex + } + } else { + closestLowerIndex = glyphIndex + } + } + } + } + } + if let closestLowerIndex = closestLowerIndex { + index = closestLowerIndex + } + } else if index != 0 { + var glyphStart: CGFloat = 0.0 + CTLineGetOffsetForStringIndex(line.line, index, &glyphStart) + if transformedPoint.x < glyphStart { + var closestLowerIndex: Int? + let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray + if glyphRuns.count != 0 { + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + for i in 0 ..< glyphCount { + var glyphIndex: CFIndex = 0 + CTRunGetStringIndices(run, CFRangeMake(i, 1), &glyphIndex) + if glyphIndex < index { + if let closestLowerIndexValue = closestLowerIndex { + if closestLowerIndexValue < glyphIndex { + closestLowerIndex = glyphIndex + } + } else { + closestLowerIndex = glyphIndex + } + } + } + } + } + if let closestLowerIndex = closestLowerIndex { + index = closestLowerIndex + } + } + } + if index >= 0 && index < attributedString.length { + if let range = line.range, index < range.location + range.length { + return (index, attributedString.attributes(at: index, effectiveRange: nil)) + } + } + break + } + } + } + } + return nil + } + + public func linesRects() -> [CGRect] { + var rects: [CGRect] = [] + for segment in self.segments { + for line in segment.lines.prefix(segment.visibleLineCount) { + rects.append(line.frame) + } + } + return rects + } + + public func textRangesRects(text: String) -> [[CGRect]] { + guard let attributedString = self.attributedString else { + return [] + } + + let (ranges, searchText) = findSubstringRanges(in: attributedString.string, query: text) + + var result: [[CGRect]] = [] + for stringRange in ranges { + var rects: [CGRect] = [] + let range = NSRange(stringRange, in: searchText) + for segment in self.segments { + for line in segment.lines.prefix(segment.visibleLineCount) { + guard let rangeValue = line.range else { + continue + } + let lineRange = NSIntersectionRange(range, rangeValue) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0.0 + if lineRange.location != rangeValue.location { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + var rightOffset: CGFloat = line.frame.width + if lineRange.location + lineRange.length != rangeValue.length { + var secondaryOffset: CGFloat = 0.0 + let rawOffset = CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, &secondaryOffset) + rightOffset = ceil(rawOffset) + if !rawOffset.isEqual(to: secondaryOffset) { + rightOffset = ceil(secondaryOffset) + } + } + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y), size: line.frame.size) + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) + + let width = abs(rightOffset - leftOffset) + rects.append(CGRect(origin: CGPoint(x: lineFrame.minX + min(leftOffset, rightOffset) + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: width, height: lineFrame.size.height))) + } + } + } + if !rects.isEmpty { + result.append(rects) + } + } + return result + } + + public func attributeSubstring(name: String, index: Int) -> (String, String)? { + if let attributedString = self.attributedString { + var range = NSRange() + let _ = attributedString.attribute(NSAttributedString.Key(rawValue: name), at: index, effectiveRange: &range) + if range.length != 0 { + return ((attributedString.string as NSString).substring(with: range), attributedString.string) + } + } + return nil + } + + public func attributeSubstringWithRange(name: String, index: Int) -> (String, String, NSRange)? { + if let attributedString = self.attributedString { + var range = NSRange() + let _ = attributedString.attribute(NSAttributedString.Key(rawValue: name), at: index, effectiveRange: &range) + if range.length != 0 { + return ((attributedString.string as NSString).substring(with: range), attributedString.string, range) + } + } + return nil + } + + public func allAttributeRects(name: String) -> [(Any, CGRect)] { + guard let attributedString = self.attributedString else { + return [] + } + var result: [(Any, CGRect)] = [] + attributedString.enumerateAttribute(NSAttributedString.Key(rawValue: name), in: NSRange(location: 0, length: attributedString.length), options: []) { (value, range, _) in + if let value = value, range.length != 0 { + var coveringRect = CGRect() + for segment in self.segments { + for line in segment.lines.prefix(segment.visibleLineCount) { + guard let rangeValue = line.range else { + continue + } + let lineRange = NSIntersectionRange(range, rangeValue) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0.0 + if lineRange.location != rangeValue.location { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + var rightOffset: CGFloat = line.frame.width + if lineRange.location + lineRange.length != rangeValue.length { + var secondaryOffset: CGFloat = 0.0 + let rawOffset = CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, &secondaryOffset) + rightOffset = ceil(rawOffset) + if !rawOffset.isEqual(to: secondaryOffset) { + rightOffset = ceil(secondaryOffset) + } + } + + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y), size: line.frame.size) + switch self.resolvedAlignment { + case .center: + lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) + case .natural: + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) + case .right: + lineFrame.origin.x = self.size.width - lineFrame.size.width + default: + break + } + + let rect = CGRect(origin: CGPoint(x: lineFrame.minX + min(leftOffset, rightOffset) + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: abs(rightOffset - leftOffset), height: lineFrame.size.height)) + if coveringRect.isEmpty { + coveringRect = rect + } else { + coveringRect = coveringRect.union(rect) + } + } + } + if !coveringRect.isEmpty { + result.append((value, coveringRect)) + } + } + } + } + return result + } + + public func lineAndAttributeRects(name: String, at index: Int) -> [(CGRect, CGRect)]? { + if let attributedString = self.attributedString { + var range = NSRange() + let _ = attributedString.attribute(NSAttributedString.Key(rawValue: name), at: index, effectiveRange: &range) + if range.length != 0 { + var rects: [(CGRect, CGRect)] = [] + for segment in self.segments { + for line in segment.lines.prefix(segment.visibleLineCount) { + guard let rangeValue = line.range else { + continue + } + let lineRange = NSIntersectionRange(range, rangeValue) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0.0 + if lineRange.location != rangeValue.location || line.isRTL { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + var rightOffset: CGFloat = line.frame.width + if lineRange.location + lineRange.length != rangeValue.length || line.isRTL { + var secondaryOffset: CGFloat = 0.0 + let rawOffset = CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, &secondaryOffset) + rightOffset = ceil(rawOffset) + if !rawOffset.isEqual(to: secondaryOffset) { + rightOffset = ceil(secondaryOffset) + } + } + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y), size: line.frame.size) + + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) + + let width = abs(rightOffset - leftOffset) + if width > 1.0 { + rects.append((lineFrame, CGRect(origin: CGPoint(x: lineFrame.minX + min(leftOffset, rightOffset) + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: width, height: lineFrame.size.height)))) + } + } + } + } + if !rects.isEmpty { + return rects + } + } + } + return nil + } + + public func rangeRects(in range: NSRange) -> (rects: [CGRect], start: TextRangeRectEdge, end: TextRangeRectEdge)? { + guard let _ = self.attributedString, range.length != 0 else { + return nil + } + var rects: [(CGRect, CGRect)] = [] + var startEdge: TextRangeRectEdge? + var endEdge: TextRangeRectEdge? + for segment in self.segments { + for line in segment.lines.prefix(segment.visibleLineCount) { + guard let rangeValue = line.range else { + continue + } + let lineRange = NSIntersectionRange(range, rangeValue) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0.0 + if lineRange.location != rangeValue.location || line.isRTL { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + var rightOffset: CGFloat = line.frame.width + if lineRange.location + lineRange.length != rangeValue.upperBound || line.isRTL { + var secondaryOffset: CGFloat = 0.0 + let rawOffset = CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, &secondaryOffset) + rightOffset = ceil(rawOffset) + if !rawOffset.isEqual(to: secondaryOffset) { + rightOffset = ceil(secondaryOffset) + } + } + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y), size: line.frame.size) + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) + + let width = max(0.0, abs(rightOffset - leftOffset)) + + if rangeValue.contains(range.lowerBound) { + let offsetX = floor(CTLineGetOffsetForStringIndex(line.line, range.lowerBound, nil)) + startEdge = TextRangeRectEdge(x: lineFrame.minX + offsetX, y: lineFrame.minY, height: lineFrame.height) + } + if rangeValue.contains(range.upperBound - 1) { + let offsetX: CGFloat + if rangeValue.upperBound == range.upperBound { + offsetX = lineFrame.maxX + } else { + var secondaryOffset: CGFloat = 0.0 + let primaryOffset = floor(CTLineGetOffsetForStringIndex(line.line, range.upperBound - 1, &secondaryOffset)) + secondaryOffset = floor(secondaryOffset) + let nextOffet = floor(CTLineGetOffsetForStringIndex(line.line, range.upperBound, &secondaryOffset)) + + if primaryOffset != secondaryOffset { + offsetX = secondaryOffset + } else { + offsetX = nextOffet + } + } + endEdge = TextRangeRectEdge(x: lineFrame.minX + offsetX, y: lineFrame.minY, height: lineFrame.height) + } + + rects.append((lineFrame, CGRect(origin: CGPoint(x: lineFrame.minX + min(leftOffset, rightOffset) + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: width, height: lineFrame.size.height)))) + } + } + } + if !rects.isEmpty, var startEdge = startEdge, var endEdge = endEdge { + startEdge.x += self.insets.left + startEdge.y += self.insets.top + endEdge.x += self.insets.left + endEdge.y += self.insets.top + return (rects.map { $1 }, startEdge, endEdge) + } + return nil + } +} + +private func addSpoiler(line: InteractiveTextNodeLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int) { + var secondaryLeftOffset: CGFloat = 0.0 + let rawLeftOffset = CTLineGetOffsetForStringIndex(line.line, startIndex, &secondaryLeftOffset) + var leftOffset = floor(rawLeftOffset) + if !rawLeftOffset.isEqual(to: secondaryLeftOffset) { + leftOffset = floor(secondaryLeftOffset) + } + + var secondaryRightOffset: CGFloat = 0.0 + let rawRightOffset = CTLineGetOffsetForStringIndex(line.line, endIndex, &secondaryRightOffset) + var rightOffset = ceil(rawRightOffset) + if !rawRightOffset.isEqual(to: secondaryRightOffset) { + rightOffset = ceil(secondaryRightOffset) + } + + line.spoilers.append(InteractiveTextNodeSpoiler(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: 0.0, width: abs(rightOffset - leftOffset), height: ascent + descent))) +} + +private func addSpoilerWord(line: InteractiveTextNodeLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int, rightInset: CGFloat = 0.0) { + var secondaryLeftOffset: CGFloat = 0.0 + let rawLeftOffset = CTLineGetOffsetForStringIndex(line.line, startIndex, &secondaryLeftOffset) + var leftOffset = floor(rawLeftOffset) + if !rawLeftOffset.isEqual(to: secondaryLeftOffset) { + leftOffset = floor(secondaryLeftOffset) + } + + var secondaryRightOffset: CGFloat = 0.0 + let rawRightOffset = CTLineGetOffsetForStringIndex(line.line, endIndex, &secondaryRightOffset) + var rightOffset = ceil(rawRightOffset) + if !rawRightOffset.isEqual(to: secondaryRightOffset) { + rightOffset = ceil(secondaryRightOffset) + } + + line.spoilerWords.append(InteractiveTextNodeSpoiler(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: 0.0, width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent))) +} + +private func addEmbeddedItem(item: AnyHashable, isHiddenBySpoiler: Bool, line: InteractiveTextNodeLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int, rightInset: CGFloat = 0.0) { + var secondaryLeftOffset: CGFloat = 0.0 + let rawLeftOffset = CTLineGetOffsetForStringIndex(line.line, startIndex, &secondaryLeftOffset) + var leftOffset = floor(rawLeftOffset) + if !rawLeftOffset.isEqual(to: secondaryLeftOffset) { + leftOffset = floor(secondaryLeftOffset) + } + + var secondaryRightOffset: CGFloat = 0.0 + let rawRightOffset = CTLineGetOffsetForStringIndex(line.line, endIndex, &secondaryRightOffset) + var rightOffset = ceil(rawRightOffset) + if !rawRightOffset.isEqual(to: secondaryRightOffset) { + rightOffset = ceil(secondaryRightOffset) + } + + line.embeddedItems.append(InteractiveTextNodeEmbeddedItem(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: 0.0, width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent), item: item, isHiddenBySpoiler: isHiddenBySpoiler)) +} + +private func addAttachment(attachment: UIImage, line: InteractiveTextNodeLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int, rightInset: CGFloat = 0.0) { + var secondaryLeftOffset: CGFloat = 0.0 + let rawLeftOffset = CTLineGetOffsetForStringIndex(line.line, startIndex, &secondaryLeftOffset) + var leftOffset = floor(rawLeftOffset) + if !rawLeftOffset.isEqual(to: secondaryLeftOffset) { + leftOffset = floor(secondaryLeftOffset) + } + + var secondaryRightOffset: CGFloat = 0.0 + let rawRightOffset = CTLineGetOffsetForStringIndex(line.line, endIndex, &secondaryRightOffset) + var rightOffset = ceil(rawRightOffset) + if !rawRightOffset.isEqual(to: secondaryRightOffset) { + rightOffset = ceil(secondaryRightOffset) + } + + line.attachments.append(InteractiveTextNodeAttachment(range: NSMakeRange(startIndex, endIndex - startIndex), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent), attachment: attachment)) +} + +open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecognizerDelegate { + public struct RenderContentTypes: OptionSet { + public var rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let text = RenderContentTypes(rawValue: 1 << 0) + public static let emoji = RenderContentTypes(rawValue: 1 << 1) + + public static let all: RenderContentTypes = [.text, .emoji] + } + + final class DrawingParameters: NSObject { + let cachedLayout: InteractiveTextNodeLayout? + let renderContentTypes: RenderContentTypes + + init(cachedLayout: InteractiveTextNodeLayout?, renderContentTypes: RenderContentTypes) { + self.cachedLayout = cachedLayout + self.renderContentTypes = renderContentTypes + + super.init() + } + } + + public internal(set) var cachedLayout: InteractiveTextNodeLayout? + public var renderContentTypes: RenderContentTypes = .all + private var contentItemLayers: [Int: TextContentItemLayer] = [:] + + private var isDisplayingContentsUnderSpoilers: Bool? + + public var canHandleTapAtPoint: ((CGPoint) -> Bool)? + public var requestToggleBlockCollapsed: ((Int) -> Void)? + public var requestDisplayContentsUnderSpoilers: (() -> Void)? + private var tapRecognizer: UITapGestureRecognizer? + + public var currentText: NSAttributedString? { + return self.cachedLayout?.attributedString + } + + public func textRangeRects(in range: NSRange) -> (rects: [CGRect], start: TextRangeRectEdge, end: TextRangeRectEdge)? { + return self.cachedLayout?.rangeRects(in: range) + } + + override public init() { + super.init() + + self.backgroundColor = UIColor.clear + self.isOpaque = false + self.clipsToBounds = false + } + + override open func didLoad() { + super.didLoad() + } + + public func attributesAtPoint(_ point: CGPoint, orNearest: Bool = false) -> (Int, [NSAttributedString.Key: Any])? { + if let cachedLayout = self.cachedLayout { + return cachedLayout.attributesAtPoint(point, orNearest: orNearest) + } else { + return nil + } + } + + public func collapsibleBlockAtPoint(_ point: CGPoint) -> Int? { + for (_, contentItemLayer) in self.contentItemLayers { + if !contentItemLayer.frame.contains(point) { + continue + } + if !contentItemLayer.renderNode.frame.offsetBy(dx: contentItemLayer.frame.minX, dy: contentItemLayer.frame.minY).contains(point) { + continue + } + + guard let item = contentItemLayer.item else { + continue + } + guard let blockQuote = item.segment.blockQuote else { + continue + } + if blockQuote.isCollapsed == nil { + continue + } + + return blockQuote.id + } + return nil + } + + func segmentLayer(index: Int) -> TextContentItemLayer? { + return self.contentItemLayers[index] + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let canHandleTapAtPoint = self.canHandleTapAtPoint else { + return nil + } + if !canHandleTapAtPoint(point) { + return nil + } + + guard let result = super.hitTest(point, with: event) else { + return nil + } + return result + } + + public func textRangesRects(text: String) -> [[CGRect]] { + return self.cachedLayout?.textRangesRects(text: text) ?? [] + } + + public func attributeSubstring(name: String, index: Int) -> (String, String)? { + return self.cachedLayout?.attributeSubstring(name: name, index: index) + } + + public func attributeSubstringWithRange(name: String, index: Int) -> (String, String, NSRange)? { + return self.cachedLayout?.attributeSubstringWithRange(name: name, index: index) + } + + public func attributeRects(name: String, at index: Int) -> [CGRect]? { + if let cachedLayout = self.cachedLayout { + return cachedLayout.lineAndAttributeRects(name: name, at: index)?.map { $0.1 } + } else { + return nil + } + } + + public func rangeRects(in range: NSRange) -> (rects: [CGRect], start: TextRangeRectEdge, end: TextRangeRectEdge)? { + if let cachedLayout = self.cachedLayout { + return cachedLayout.rangeRects(in: range) + } else { + return nil + } + } + + public func lineAndAttributeRects(name: String, at index: Int) -> [(CGRect, CGRect)]? { + if let cachedLayout = self.cachedLayout { + return cachedLayout.lineAndAttributeRects(name: name, at: index) + } else { + return nil + } + } + + private static func calculateLayoutV2( + attributedString: NSAttributedString, + minimumNumberOfLines: Int, + maximumNumberOfLines: Int, + truncationType: CTLineTruncationType, + backgroundColor: UIColor?, + constrainedSize: CGSize, + alignment: NSTextAlignment, + verticalAlignment: TextVerticalAlignment, + lineSpacingFactor: CGFloat, + cutout: TextNodeCutout?, + insets: UIEdgeInsets, + lineColor: UIColor?, + textShadowColor: UIColor?, + textShadowBlur: CGFloat?, + textStroke: (UIColor, CGFloat)?, + displayContentsUnderSpoilers: Bool, + customTruncationToken: NSAttributedString?, + expandedBlocks: Set + ) -> InteractiveTextNodeLayout { + let blockQuoteLeftInset: CGFloat = 9.0 + let blockQuoteRightInset: CGFloat = 0.0 + let blockQuoteIconInset: CGFloat = 7.0 + + struct StringSegment { + let title: NSAttributedString? + let substring: NSAttributedString + let firstCharacterOffset: Int + let blockQuote: TextNodeBlockQuoteData? + let tintColor: UIColor? + let secondaryTintColor: UIColor? + let tertiaryTintColor: UIColor? + } + var stringSegments: [StringSegment] = [] + + let rawWholeString = attributedString.string as NSString + let wholeStringLength = rawWholeString.length + + var segmentCharacterOffset = 0 + while true { + var found = false + attributedString.enumerateAttribute(NSAttributedString.Key("Attribute__Blockquote"), in: NSRange(location: segmentCharacterOffset, length: wholeStringLength - segmentCharacterOffset), using: { value, effectiveRange, stop in + found = true + stop.pointee = ObjCBool(true) + + if segmentCharacterOffset != effectiveRange.location { + stringSegments.append(StringSegment( + title: nil, + substring: attributedString.attributedSubstring(from: NSRange( + location: segmentCharacterOffset, + length: effectiveRange.location - segmentCharacterOffset + )), + firstCharacterOffset: segmentCharacterOffset, + blockQuote: nil, + tintColor: nil, + secondaryTintColor: nil, + tertiaryTintColor: nil + )) + } + + if let value = value as? TextNodeBlockQuoteData { + if effectiveRange.length != 0 { + stringSegments.append(StringSegment( + title: value.title, + substring: attributedString.attributedSubstring(from: effectiveRange), + firstCharacterOffset: effectiveRange.location, + blockQuote: value, + tintColor: value.color, + secondaryTintColor: value.secondaryColor, + tertiaryTintColor: value.tertiaryColor + )) + } + segmentCharacterOffset = effectiveRange.location + effectiveRange.length + if segmentCharacterOffset < wholeStringLength && rawWholeString.character(at: segmentCharacterOffset) == 0x0a { + segmentCharacterOffset += 1 + } + } else { + stringSegments.append(StringSegment( + title: nil, + substring: attributedString.attributedSubstring(from: effectiveRange), + firstCharacterOffset: effectiveRange.location, + blockQuote: nil, + tintColor: nil, + secondaryTintColor: nil, + tertiaryTintColor: nil + )) + segmentCharacterOffset = effectiveRange.location + effectiveRange.length + } + }) + if !found { + if segmentCharacterOffset != wholeStringLength { + stringSegments.append(StringSegment( + title: nil, + substring: attributedString.attributedSubstring(from: NSRange( + location: segmentCharacterOffset, + length: wholeStringLength - segmentCharacterOffset + )), + firstCharacterOffset: segmentCharacterOffset, + blockQuote: nil, + tintColor: nil, + secondaryTintColor: nil, + tertiaryTintColor: nil + )) + } + + break + } + } + + struct CalculatedSegment { + var titleLine: InteractiveTextNodeLine? + var lines: [InteractiveTextNodeLine] = [] + var tintColor: UIColor? + var secondaryTintColor: UIColor? + var tertiaryTintColor: UIColor? + var blockQuote: TextNodeBlockQuoteData? + var additionalWidth: CGFloat = 0.0 + } + + var calculatedSegments: [CalculatedSegment] = [] + + for segment in stringSegments { + var calculatedSegment = CalculatedSegment() + calculatedSegment.blockQuote = segment.blockQuote + calculatedSegment.tintColor = segment.tintColor + calculatedSegment.secondaryTintColor = segment.secondaryTintColor + calculatedSegment.tertiaryTintColor = segment.tertiaryTintColor + + let rawSubstring = segment.substring.string as NSString + let substringLength = rawSubstring.length + + let segmentTypesetterString = attributedString.attributedSubstring(from: NSRange(location: 0, length: segment.firstCharacterOffset + substringLength)) + let typesetter = CTTypesetterCreateWithAttributedString(segmentTypesetterString as CFAttributedString) + + var currentLineStartIndex = segment.firstCharacterOffset + let segmentEndIndex = segment.firstCharacterOffset + substringLength + + var constrainedSegmentWidth = constrainedSize.width + var additionalOffsetX: CGFloat = 0.0 + if segment.blockQuote != nil { + additionalOffsetX += blockQuoteLeftInset + constrainedSegmentWidth -= additionalOffsetX + blockQuoteLeftInset + blockQuoteRightInset + calculatedSegment.additionalWidth += blockQuoteLeftInset + blockQuoteRightInset + } + + var additionalSegmentRightInset: CGFloat = 0.0 + if let blockQuote = segment.blockQuote { + switch blockQuote.kind { + case .quote: + additionalSegmentRightInset = blockQuoteIconInset + case .code: + if segment.title != nil { + additionalSegmentRightInset = blockQuoteIconInset + } + } + } + + if let title = segment.title { + let rawTitleLine = CTLineCreateWithAttributedString(title) + if let titleLine = CTLineCreateTruncatedLine(rawTitleLine, constrainedSegmentWidth - additionalSegmentRightInset, .end, nil) { + var lineAscent: CGFloat = 0.0 + var lineDescent: CGFloat = 0.0 + let lineWidth = CTLineGetTypographicBounds(titleLine, &lineAscent, &lineDescent, nil) + calculatedSegment.titleLine = InteractiveTextNodeLine( + line: titleLine, + frame: CGRect(origin: CGPoint(x: additionalOffsetX, y: 0.0), size: CGSize(width: lineWidth + additionalSegmentRightInset, height: lineAscent + lineDescent)), + ascent: lineAscent, + descent: lineDescent, + range: nil, + isRTL: false, + strikethroughs: [], + spoilers: [], + spoilerWords: [], + embeddedItems: [], + attachments: [], + additionalTrailingLine: nil + ) + additionalSegmentRightInset = 0.0 + } + } + + while true { + let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, currentLineStartIndex, constrainedSegmentWidth - additionalSegmentRightInset) + + if lineCharacterCount != 0 { + let line = CTTypesetterCreateLine(typesetter, CFRange(location: currentLineStartIndex, length: lineCharacterCount)) + var lineAscent: CGFloat = 0.0 + var lineDescent: CGFloat = 0.0 + var lineWidth = CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, nil) + lineWidth = min(lineWidth, constrainedSegmentWidth - additionalSegmentRightInset) + + var isRTL = false + let glyphRuns = CTLineGetGlyphRuns(line) as NSArray + if glyphRuns.count != 0 { + let run = glyphRuns[0] as! CTRun + if CTRunGetStatus(run).contains(CTRunStatus.rightToLeft) { + isRTL = true + } + } + + calculatedSegment.lines.append(InteractiveTextNodeLine( + line: line, + frame: CGRect(origin: CGPoint(x: additionalOffsetX, y: 0.0), size: CGSize(width: lineWidth + additionalSegmentRightInset, height: lineAscent + lineDescent)), + ascent: lineAscent, + descent: lineDescent, + range: NSRange(location: currentLineStartIndex, length: lineCharacterCount), + isRTL: isRTL && segment.blockQuote == nil, + strikethroughs: [], + spoilers: [], + spoilerWords: [], + embeddedItems: [], + attachments: [], + additionalTrailingLine: nil + )) + } + + additionalSegmentRightInset = 0.0 + + currentLineStartIndex += lineCharacterCount + + if currentLineStartIndex >= segmentEndIndex { + break + } + } + + calculatedSegments.append(calculatedSegment) + } + + var size = CGSize() + let isTruncated = false + + for segment in calculatedSegments { + if let titleLine = segment.titleLine { + size.width = max(size.width, titleLine.frame.origin.x + titleLine.frame.width + segment.additionalWidth) + } + for line in segment.lines { + size.width = max(size.width, line.frame.origin.x + line.frame.width + segment.additionalWidth) + } + } + + var segments: [InteractiveTextNodeSegment] = [] + + var firstLineOffset: CGFloat? + + var nextBlockIndex = 0 + + for i in 0 ..< calculatedSegments.count { + var segmentLines: [InteractiveTextNodeLine] = [] + + let segment = calculatedSegments[i] + if i != 0 { + if segment.blockQuote != nil { + size.height += 6.0 + } + } else { + if segment.blockQuote != nil { + size.height += 7.0 + } + } + + let blockMinY = size.height - insets.bottom + var blockWidth: CGFloat = 0.0 + + if let titleLine = segment.titleLine { + titleLine.frame = CGRect(origin: CGPoint(x: titleLine.frame.origin.x, y: size.height), size: titleLine.frame.size) + titleLine.frame.size.width += max(0.0, segment.additionalWidth - 2.0) + size.height += titleLine.frame.height + titleLine.frame.height * lineSpacingFactor + blockWidth = max(blockWidth, titleLine.frame.origin.x + titleLine.frame.width) + + segmentLines.append(titleLine) + } + + var blockIndex: Int? + var isCollapsed = false + if let blockQuote = segment.blockQuote { + let blockIndexValue = nextBlockIndex + blockIndex = blockIndexValue + nextBlockIndex += 1 + if blockQuote.isCollapsible { + isCollapsed = !expandedBlocks.contains(blockIndexValue) + } + } + + var lineCount = 0 + var visibleLineCount = 0 + var segmentHeight: CGFloat = 0.0 + var effectiveSegmentHeight: CGFloat = 0.0 + for i in 0 ..< segment.lines.count { + let line = segment.lines[i] + lineCount += 1 + + line.frame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: size.height + segmentHeight), size: line.frame.size) + line.frame.size.width += max(0.0, segment.additionalWidth) + + var lineHeightIncrease = line.frame.height + if i != segment.lines.count - 1 { + lineHeightIncrease += line.frame.height * lineSpacingFactor + } + + segmentHeight += lineHeightIncrease + if isCollapsed && lineCount > 3 { + } else { + effectiveSegmentHeight += lineHeightIncrease + visibleLineCount = i + 1 + } + blockWidth = max(blockWidth, line.frame.origin.x + line.frame.width) + + if let range = line.range { + attributedString.enumerateAttributes(in: range, options: []) { attributes, range, _ in + if attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil { + var ascent: CGFloat = 0.0 + var descent: CGFloat = 0.0 + CTLineGetTypographicBounds(line.line, &ascent, &descent, nil) + + var startIndex: Int? + var currentIndex: Int? + + let nsString = (attributedString.string as NSString) + nsString.enumerateSubstrings(in: range, options: .byComposedCharacterSequences) { substring, range, _, _ in + if let substring = substring, substring.rangeOfCharacter(from: .whitespacesAndNewlines) != nil { + if let currentStartIndex = startIndex { + startIndex = nil + let endIndex = range.location + addSpoilerWord(line: line, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex) + } + } else if startIndex == nil { + startIndex = range.location + } + currentIndex = range.location + range.length + } + + if let currentStartIndex = startIndex, let currentIndex = currentIndex { + startIndex = nil + let endIndex = currentIndex + addSpoilerWord(line: line, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex, rightInset: 0.0) + } + + addSpoiler(line: line, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) + } else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] { + let lowerX = floor(CTLineGetOffsetForStringIndex(line.line, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(line.line, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + line.strikethroughs.append(InteractiveTextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: line.frame.height))) + } + + if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) { + var ascent: CGFloat = 0.0 + var descent: CGFloat = 0.0 + CTLineGetTypographicBounds(line.line, &ascent, &descent, nil) + + var isHiddenBySpoiler = attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil + if displayContentsUnderSpoilers { + isHiddenBySpoiler = false + } + + addEmbeddedItem(item: embeddedItem, isHiddenBySpoiler: isHiddenBySpoiler, line: line, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) + } + + if let attachment = attributes[NSAttributedString.Key.attachment] as? UIImage { + var ascent: CGFloat = 0.0 + var descent: CGFloat = 0.0 + CTLineGetTypographicBounds(line.line, &ascent, &descent, nil) + + addAttachment(attachment: attachment, line: line, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) + } + } + } + + segmentLines.append(line) + + if firstLineOffset == nil, let firstLine = segmentLines.first { + firstLineOffset = firstLine.descent + } + } + + if !isCollapsed, let blockQuote = segment.blockQuote, blockQuote.isCollapsible, !segment.lines.isEmpty { + let lastLine = segment.lines[segment.lines.count - 1] + if lastLine.frame.maxX + 16.0 <= constrainedSize.width { + lastLine.frame.size.width += 16.0 + blockWidth = max(blockWidth, lastLine.frame.maxX) + } else { + segmentHeight += 10.0 + effectiveSegmentHeight += 10.0 + } + } + + segmentHeight = ceil(segmentHeight) + effectiveSegmentHeight = ceil(effectiveSegmentHeight) + + size.height += effectiveSegmentHeight + let blockMaxY = size.height - insets.bottom + + if i != calculatedSegments.count - 1 { + if segment.blockQuote != nil { + size.height += 8.0 + } + } else { + if segment.blockQuote != nil { + size.height += 6.0 + } + } + + var segmentBlockQuote: InteractiveTextNodeBlockQuote? + if let blockQuote = segment.blockQuote, let tintColor = segment.tintColor, let blockIndex { + segmentBlockQuote = InteractiveTextNodeBlockQuote(id: blockIndex, frame: CGRect(origin: CGPoint(x: 0.0, y: blockMinY - 2.0), size: CGSize(width: blockWidth, height: blockMaxY - (blockMinY - 2.0) + 3.0)), data: blockQuote, tintColor: tintColor, secondaryTintColor: segment.secondaryTintColor, tertiaryTintColor: segment.tertiaryTintColor, backgroundColor: blockQuote.backgroundColor, isCollapsed: (blockQuote.isCollapsible && segmentLines.count > 3) ? isCollapsed : nil) + } + + segments.append(InteractiveTextNodeSegment( + lines: segmentLines, + visibleLineCount: visibleLineCount, + tintColor: segment.tintColor, + secondaryTintColor: segment.secondaryTintColor, + tertiaryTintColor: segment.tertiaryTintColor, + blockQuote: segmentBlockQuote, + attributedString: attributedString, + resolvedAlignment: alignment, + layoutSize: size + )) + } + + size.width = ceil(size.width) + size.height = ceil(size.height) + + let rawTextSize = size + size.width += insets.left + insets.right + size.height += insets.top + insets.bottom + + return InteractiveTextNodeLayout( + attributedString: attributedString, + maximumNumberOfLines: maximumNumberOfLines, + truncationType: truncationType, + constrainedSize: constrainedSize, + explicitAlignment: alignment, + resolvedAlignment: alignment, + verticalAlignment: verticalAlignment, + lineSpacing: lineSpacingFactor, + cutout: cutout, + insets: insets, + size: size, + rawTextSize: rawTextSize, + truncated: isTruncated, + firstLineOffset: firstLineOffset ?? 0.0, + segments: segments, + backgroundColor: backgroundColor, + lineColor: lineColor, + textShadowColor: textShadowColor, + textShadowBlur: textShadowBlur, + textStroke: textStroke, + displayContentsUnderSpoilers: displayContentsUnderSpoilers, + expandedBlocks: expandedBlocks + ) + } + + static func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displayContentsUnderSpoilers: Bool, customTruncationToken: NSAttributedString?, expandedBlocks: Set) -> InteractiveTextNodeLayout { + guard let attributedString else { + return InteractiveTextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, segments: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, expandedBlocks: expandedBlocks) + } + + return calculateLayoutV2(attributedString: attributedString, minimumNumberOfLines: minimumNumberOfLines, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, alignment: alignment, verticalAlignment: verticalAlignment, lineSpacingFactor: lineSpacingFactor, cutout: cutout, insets: insets, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, customTruncationToken: customTruncationToken, expandedBlocks: expandedBlocks) + } + + private func updateContentItems(animation: ListViewItemUpdateAnimation) { + guard let cachedLayout = self.cachedLayout else { + return + } + + let animateContents = self.isDisplayingContentsUnderSpoilers != nil && self.isDisplayingContentsUnderSpoilers != cachedLayout.displayContentsUnderSpoilers && animation.isAnimated + let synchronous = animateContents + self.isDisplayingContentsUnderSpoilers = cachedLayout.displayContentsUnderSpoilers + + let topLeftOffset = CGPoint(x: cachedLayout.insets.left, y: cachedLayout.insets.top) + + var validIds: [Int] = [] + var nextItemId = 0 + for segment in cachedLayout.segments { + let itemId = nextItemId + nextItemId += 1 + + var segmentRect = CGRect() + for line in segment.lines { + var lineRect = line.frame + lineRect.origin.y = topLeftOffset.y + line.frame.minY + lineRect.origin.x = topLeftOffset.x + line.frame.minX + if segmentRect.isEmpty { + segmentRect = lineRect + } else { + segmentRect = segmentRect.union(lineRect) + } + } + + segmentRect.size.width += cachedLayout.insets.left + cachedLayout.insets.right + segmentRect.origin.x -= cachedLayout.insets.left + segmentRect.size.height += cachedLayout.insets.top + cachedLayout.insets.bottom + segmentRect.origin.y -= cachedLayout.insets.top + + segmentRect = segmentRect.integral + + let contentItem = TextContentItem( + id: itemId, + size: segmentRect.size, + attributedString: cachedLayout.attributedString, + textShadowColor: cachedLayout.textShadowColor, + textShadowBlur: cachedLayout.textShadowBlur, + textStroke: cachedLayout.textStroke, + contentOffset: CGPoint(x: -segmentRect.minX + topLeftOffset.x, y: -segmentRect.minY + topLeftOffset.y), + segment: segment, + displayContentsUnderSpoilers: cachedLayout.displayContentsUnderSpoilers + ) + validIds.append(contentItem.id) + + let contentItemFrame = CGRect(origin: CGPoint(x: segmentRect.minX, y: segmentRect.minY), size: CGSize(width: contentItem.size.width, height: contentItem.size.height)) + + var contentItemAnimation = animation + let contentItemLayer: TextContentItemLayer + if let current = self.contentItemLayers[itemId] { + contentItemLayer = current + } else { + contentItemAnimation = .None + contentItemLayer = TextContentItemLayer() + self.contentItemLayers[contentItem.id] = contentItemLayer + self.layer.addSublayer(contentItemLayer) + } + + contentItemLayer.update(item: contentItem, animation: contentItemAnimation, synchronously: synchronous, animateContents: animateContents && contentItemAnimation.isAnimated) + + contentItemAnimation.animator.updateFrame(layer: contentItemLayer, frame: contentItemFrame, completion: nil) + } + var removedIds: [Int] = [] + for (id, contentItemLayer) in self.contentItemLayers { + if !validIds.contains(id) { + removedIds.append(id) + contentItemLayer.removeFromSuperlayer() + } + } + for id in removedIds { + self.contentItemLayers.removeValue(forKey: id) + } + + if !self.contentItemLayers.isEmpty { + if self.tapRecognizer == nil { + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGesture(_:))) + self.tapRecognizer = tapRecognizer + self.view.addGestureRecognizer(tapRecognizer) + tapRecognizer.delegate = self + } + } else if let tapRecognizer = self.tapRecognizer { + self.tapRecognizer = nil + self.view.removeGestureRecognizer(tapRecognizer) + } + } + + public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } + + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + let point = recognizer.location(in: self.view) + if let cachedLayout = self.cachedLayout, !cachedLayout.displayContentsUnderSpoilers, let (_, attributes) = self.attributesAtPoint(point) { + if attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil { + self.requestDisplayContentsUnderSpoilers?() + return + } + } + if let blockId = self.collapsibleBlockAtPoint(point) { + self.requestToggleBlockCollapsed?(blockId) + } + } + } + + public static func asyncLayout(_ maybeNode: InteractiveTextNode?) -> (InteractiveTextNodeLayoutArguments) -> (InteractiveTextNodeLayout, (ListViewItemUpdateAnimation) -> InteractiveTextNode) { + let existingLayout: InteractiveTextNodeLayout? = maybeNode?.cachedLayout + + return { arguments in + var layout: InteractiveTextNodeLayout + + if let existingLayout = existingLayout, existingLayout.constrainedSize == arguments.constrainedSize && existingLayout.maximumNumberOfLines == arguments.maximumNumberOfLines && existingLayout.truncationType == arguments.truncationType && existingLayout.cutout == arguments.cutout && existingLayout.explicitAlignment == arguments.alignment && existingLayout.lineSpacing.isEqual(to: arguments.lineSpacing) && existingLayout.expandedBlocks == arguments.expandedBlocks { + let stringMatch: Bool + + var colorMatch: Bool = true + if let backgroundColor = arguments.backgroundColor, let previousBackgroundColor = existingLayout.backgroundColor { + if !backgroundColor.isEqual(previousBackgroundColor) { + colorMatch = false + } + } else if (arguments.backgroundColor != nil) != (existingLayout.backgroundColor != nil) { + colorMatch = false + } + + if !colorMatch { + stringMatch = false + } else if let existingString = existingLayout.attributedString, let string = arguments.attributedString { + stringMatch = existingString.isEqual(to: string) + } else if existingLayout.attributedString == nil && arguments.attributedString == nil { + stringMatch = true + } else { + stringMatch = false + } + + if stringMatch { + layout = existingLayout + if layout.displayContentsUnderSpoilers != arguments.displayContentsUnderSpoilers { + layout = layout.withUpdatedDisplayContentsUnderSpoilers(arguments.displayContentsUnderSpoilers) + } + } else { + layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks) + } + } else { + layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks) + } + + let node = maybeNode ?? InteractiveTextNode() + + return (layout, { animation in + if node.cachedLayout !== layout { + node.cachedLayout = layout + node.updateContentItems(animation: animation) + } + + return node + }) + } + } +} + +final class TextContentItem { + let id: Int + let size: CGSize + let attributedString: NSAttributedString? + let textShadowColor: UIColor? + let textShadowBlur: CGFloat? + let textStroke: (UIColor, CGFloat)? + let contentOffset: CGPoint + let segment: InteractiveTextNodeSegment + let displayContentsUnderSpoilers: Bool + + init( + id: Int, + size: CGSize, + attributedString: NSAttributedString?, + textShadowColor: UIColor?, + textShadowBlur: CGFloat?, + textStroke: (UIColor, CGFloat)?, + contentOffset: CGPoint, + segment: InteractiveTextNodeSegment, + displayContentsUnderSpoilers: Bool + ) { + self.id = id + self.size = size + self.attributedString = attributedString + self.textShadowColor = textShadowColor + self.textShadowBlur = textShadowBlur + self.textStroke = textStroke + self.contentOffset = contentOffset + self.segment = segment + self.displayContentsUnderSpoilers = displayContentsUnderSpoilers + } +} + +final class TextContentItemLayer: SimpleLayer { + final class RenderMask { + let image: UIImage + let isOpaque: Bool + let frame: CGRect + + init(image: UIImage, isOpaque: Bool, frame: CGRect) { + self.image = image + self.isOpaque = isOpaque + self.frame = frame + } + } + + fileprivate final class RenderParams: NSObject { + let size: CGSize + let item: TextContentItem + let mask: RenderMask? + + init(size: CGSize, item: TextContentItem, mask: RenderMask?) { + self.size = size + self.item = item + self.mask = mask + + super.init() + } + } + + final class RenderNode: ASDisplayNode { + fileprivate var params: RenderParams? + + override init() { + super.init() + + self.isOpaque = false + self.backgroundColor = nil + self.layer.masksToBounds = true + self.layer.contentsGravity = .bottomLeft + self.layer.contentsScale = UIScreenScale + } + + override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { + return self.params + } + + @objc override static func display(withParameters parameters: Any?, isCancelled isCancelledBlock: () -> Bool) -> UIImage? { + guard let params = parameters as? RenderParams else { + return nil + } + if isCancelledBlock() { + return nil + } + guard let renderingContext = DrawingContext(size: params.size, opaque: false, clear: true) else { + return nil + } + + renderingContext.withContext { context in + UIGraphicsPushContext(context) + defer { + UIGraphicsPopContext() + } + + if let mask = params.mask { + context.clip(to: [mask.frame]) + } + + context.saveGState() + + context.setAllowsAntialiasing(true) + + context.setAllowsFontSmoothing(false) + context.setShouldSmoothFonts(false) + + context.setAllowsFontSubpixelPositioning(false) + context.setShouldSubpixelPositionFonts(false) + + context.setAllowsFontSubpixelQuantization(true) + context.setShouldSubpixelQuantizeFonts(true) + + if let textShadowColor = params.item.textShadowColor { + context.setTextDrawingMode(.fill) + context.setShadow(offset: params.item.textShadowBlur != nil ? .zero : CGSize(width: 0.0, height: 1.0), blur: params.item.textShadowBlur ?? 0.0, color: textShadowColor.cgColor) + } + + if let (textStrokeColor, textStrokeWidth) = params.item.textStroke { + context.setBlendMode(.normal) + + context.setLineCap(.round) + context.setLineJoin(.round) + context.setStrokeColor(textStrokeColor.cgColor) + context.setFillColor(textStrokeColor.cgColor) + context.setLineWidth(textStrokeWidth) + context.setTextDrawingMode(.fillStroke) + } + + let textMatrix = context.textMatrix + let textPosition = context.textPosition + context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) + + let offset = params.item.contentOffset + let alignment: NSTextAlignment = .left + + for i in 0 ..< params.item.segment.lines.count { + let line = params.item.segment.lines[i] + + var lineFrame = line.frame + lineFrame.origin.y += offset.y + + if alignment == .center { + lineFrame.origin.x = offset.x + floor((params.size.width - lineFrame.width) / 2.0) + } else if alignment == .natural || alignment == .left { + if line.isRTL { + lineFrame.origin.x = offset.x + floor(params.size.width - lineFrame.width) + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: params.size), cutout: nil) + } else { + lineFrame.origin.x += offset.x + } + } else if alignment == .right { + lineFrame.origin.x = offset.x + (params.size.width - lineFrame.width) + } + + context.textPosition = CGPoint(x: lineFrame.minX, y: lineFrame.maxY - line.descent) + + let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray + + if glyphRuns.count != 0 { + let hasAttachments = !line.attachments.isEmpty + let hasHiddenSpoilers = !params.item.displayContentsUnderSpoilers && !line.spoilers.isEmpty + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + let attributes = CTRunGetAttributes(run) as NSDictionary + if attributes["Attribute__EmbeddedItem"] != nil { + continue + } + if hasHiddenSpoilers && (attributes["Attribute__Spoiler"] != nil || attributes["TelegramSpoiler"] != nil) { + continue + } + + /*if renderContentTypes != .all { + if let font = attributes["NSFont"] as? UIFont, font.fontName.contains("ColorEmoji") { + if !renderContentTypes.contains(.emoji) { + continue + } + } else { + if !renderContentTypes.contains(.text) { + continue + } + } + }*/ + + var fixDoubleEmoji = false + if glyphCount == 2, let font = attributes["NSFont"] as? UIFont, font.fontName.contains("ColorEmoji"), let string = params.item.attributedString { + let range = CTRunGetStringRange(run) + + if range.location < string.length && (range.location + range.length) <= string.length { + let substring = string.attributedSubstring(from: NSMakeRange(range.location, range.length)).string + + let heart = Unicode.Scalar(0x2764)! + let man = Unicode.Scalar(0x1F468)! + let woman = Unicode.Scalar(0x1F469)! + let leftHand = Unicode.Scalar(0x1FAF1)! + let rightHand = Unicode.Scalar(0x1FAF2)! + + if substring.unicodeScalars.contains(heart) && (substring.unicodeScalars.contains(man) || substring.unicodeScalars.contains(woman)) { + fixDoubleEmoji = true + } else if substring.unicodeScalars.contains(leftHand) && substring.unicodeScalars.contains(rightHand) { + fixDoubleEmoji = true + } + } + } + + if fixDoubleEmoji { + context.setBlendMode(.normal) + } + + if hasAttachments { + let stringRange = CTRunGetStringRange(run) + if line.attachments.contains(where: { $0.range.contains(stringRange.location) }) { + } else { + CTRunDraw(run, context, CFRangeMake(0, glyphCount)) + } + } else { + CTRunDraw(run, context, CFRangeMake(0, glyphCount)) + } + + if fixDoubleEmoji { + context.setBlendMode(.normal) + } + } + } + + for attachment in line.attachments { + let image = attachment.attachment + var textColor: UIColor? + params.item.attributedString?.enumerateAttributes(in: attachment.range, options: []) { attributes, range, _ in + if let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor { + textColor = color + } + } + if let textColor { + if let tintedImage = generateTintedImage(image: image, color: textColor) { + let imageRect = CGRect(origin: CGPoint(x: attachment.frame.midX - tintedImage.size.width * 0.5, y: attachment.frame.midY - tintedImage.size.height * 0.5 + 1.0), size: tintedImage.size).offsetBy(dx: lineFrame.minX, dy: lineFrame.minY) + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + context.draw(tintedImage.cgImage!, in: imageRect) + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + } + } + } + + if !line.strikethroughs.isEmpty { + for strikethrough in line.strikethroughs { + guard let lineRange = line.range else { + continue + } + var textColor: UIColor? + params.item.attributedString?.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in + if range == strikethrough.range, let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor { + textColor = color + } + } + if let textColor = textColor { + context.setFillColor(textColor.cgColor) + } + let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY) + context.fill(CGRect(x: frame.minX, y: frame.minY - 5.0, width: frame.width, height: 1.0)) + } + } + + if let (additionalTrailingLine, _) = line.additionalTrailingLine { + context.textPosition = CGPoint(x: lineFrame.maxX, y: lineFrame.minY) + + let glyphRuns = CTLineGetGlyphRuns(additionalTrailingLine) as NSArray + if glyphRuns.count != 0 { + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + let attributes = CTRunGetAttributes(run) as NSDictionary + if attributes["Attribute__EmbeddedItem"] != nil { + continue + } + + var fixDoubleEmoji = false + if glyphCount == 2, let font = attributes["NSFont"] as? UIFont, font.fontName.contains("ColorEmoji"), let string = params.item.attributedString { + let range = CTRunGetStringRange(run) + + if range.location < string.length && (range.location + range.length) <= string.length { + let substring = string.attributedSubstring(from: NSMakeRange(range.location, range.length)).string + + let heart = Unicode.Scalar(0x2764)! + let man = Unicode.Scalar(0x1F468)! + let woman = Unicode.Scalar(0x1F469)! + let leftHand = Unicode.Scalar(0x1FAF1)! + let rightHand = Unicode.Scalar(0x1FAF2)! + + if substring.unicodeScalars.contains(heart) && (substring.unicodeScalars.contains(man) || substring.unicodeScalars.contains(woman)) { + fixDoubleEmoji = true + } else if substring.unicodeScalars.contains(leftHand) && substring.unicodeScalars.contains(rightHand) { + fixDoubleEmoji = true + } + } + } + + if fixDoubleEmoji { + context.setBlendMode(.normal) + } + CTRunDraw(run, context, CFRangeMake(0, glyphCount)) + if fixDoubleEmoji { + context.setBlendMode(.normal) + } + } + } + } + } + + context.textMatrix = textMatrix + context.textPosition = CGPoint(x: textPosition.x, y: textPosition.y) + + context.setShadow(offset: CGSize(), blur: 0.0) + context.setAlpha(1.0) + + context.restoreGState() + + if let mask = params.mask, !mask.isOpaque { + mask.image.draw(in: mask.frame, blendMode: .destinationIn, alpha: 1.0) + } + } + + return renderingContext.generateImage() + } + } + + private(set) var item: TextContentItem? + + let renderNode: RenderNode + private var contentMaskNode: ASImageNode? + private var blockBackgroundView: MessageInlineBlockBackgroundView? + private var quoteTypeIconNode: ASImageNode? + private var blockExpandArrow: SimpleLayer? + + private var currentAnimationId: Int = 0 + private var isAnimating: Bool = false + private var currentContentMask: RenderMask? + + override init() { + self.renderNode = RenderNode() + + super.init() + + self.addSublayer(self.renderNode.layer) + } + + override init(layer: Any) { + self.renderNode = RenderNode() + + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(item: TextContentItem, animation: ListViewItemUpdateAnimation, synchronously: Bool = false, animateContents: Bool = false) { + self.item = item + + let contentFrame = CGRect(origin: CGPoint(), size: item.size) + var effectiveContentFrame = contentFrame + var contentMask: RenderMask? + + if let blockQuote = item.segment.blockQuote { + let blockBackgroundView: MessageInlineBlockBackgroundView + if let current = self.blockBackgroundView { + blockBackgroundView = current + } else { + blockBackgroundView = MessageInlineBlockBackgroundView() + self.blockBackgroundView = blockBackgroundView + self.insertSublayer(blockBackgroundView.layer, at: 0) + } + + let blockExpandArrow: SimpleLayer + if let current = self.blockExpandArrow { + blockExpandArrow = current + } else { + blockExpandArrow = SimpleLayer() + self.blockExpandArrow = blockExpandArrow + self.addSublayer(blockExpandArrow) + blockExpandArrow.contents = expandArrowIcon.cgImage + } + blockExpandArrow.layerTintColor = blockQuote.tintColor.cgColor + + let blockBackgroundFrame = blockQuote.frame.offsetBy(dx: item.contentOffset.x, dy: item.contentOffset.y + 4.0) + + if animation.isAnimated { + self.isAnimating = true + self.currentAnimationId += 1 + let animationId = self.currentAnimationId + animation.animator.updateFrame(layer: blockBackgroundView.layer, frame: blockBackgroundFrame, completion: { [weak self] completed in + guard completed, let self, self.currentAnimationId == animationId, let item = self.item else { + return + } + self.isAnimating = false + self.update(item: item, animation: .None, synchronously: true) + }) + } else { + blockBackgroundView.layer.frame = blockBackgroundFrame + } + blockBackgroundView.update( + size: blockBackgroundFrame.size, + isTransparent: false, + primaryColor: blockQuote.tintColor, + secondaryColor: blockQuote.secondaryTintColor, + thirdColor: blockQuote.tertiaryTintColor, + backgroundColor: nil, + pattern: nil, + patternTopRightPosition: nil, + patternAlpha: 1.0, + animation: animation + ) + + var quoteTypeIcon: UIImage? + switch blockQuote.data.kind { + case .code: + quoteTypeIcon = codeIcon + case .quote: + quoteTypeIcon = quoteIcon + } + + if let quoteTypeIcon { + let quoteTypeIconNode: ASImageNode + if let current = self.quoteTypeIconNode { + quoteTypeIconNode = current + } else { + quoteTypeIconNode = ASImageNode() + self.quoteTypeIconNode = quoteTypeIconNode + self.addSublayer(quoteTypeIconNode.layer) + } + if quoteTypeIconNode.image !== quoteTypeIcon { + quoteTypeIconNode.image = quoteTypeIcon + } + let quoteTypeIconFrame = CGRect(origin: CGPoint(x: blockBackgroundFrame.maxX - 4.0 - quoteTypeIcon.size.width, y: blockBackgroundFrame.minY + 4.0), size: quoteTypeIcon.size) + quoteTypeIconNode.layer.layerTintColor = blockQuote.tintColor.cgColor + animation.animator.updateFrame(layer: quoteTypeIconNode.layer, frame: quoteTypeIconFrame, completion: nil) + } else if let quoteTypeIconNode = self.quoteTypeIconNode { + self.quoteTypeIconNode = nil + quoteTypeIconNode.removeFromSupernode() + } + + if let isCollapsed = blockQuote.isCollapsed { + let expandArrowFrame = CGRect(origin: CGPoint(x: blockBackgroundFrame.maxX - 6.0 - expandArrowIcon.size.width, y: blockBackgroundFrame.maxY - 3.0 - expandArrowIcon.size.height), size: expandArrowIcon.size) + animation.animator.updatePosition(layer: blockExpandArrow, position: expandArrowFrame.center, completion: nil) + animation.animator.updateBounds(layer: blockExpandArrow, bounds: CGRect(origin: CGPoint(), size: expandArrowFrame.size), completion: nil) + animation.animator.updateTransform(layer: blockExpandArrow, transform: CATransform3DMakeRotation(isCollapsed ? 0.0 : CGFloat.pi, 0.0, 0.0, 1.0), completion: nil) + + let contentMaskFrame = CGRect(origin: CGPoint(x: 0.0, y: blockBackgroundFrame.minY - contentFrame.minY), size: CGSize(width: contentFrame.width, height: blockBackgroundFrame.height)) + contentMask = RenderMask(image: expandableBlockMaskImage, isOpaque: !isCollapsed, frame: contentMaskFrame) + effectiveContentFrame.size.height = ceil(contentMaskFrame.height - contentMaskFrame.minY) + } else { + if let blockExpandArrow = self.blockExpandArrow { + self.blockExpandArrow = nil + blockExpandArrow.removeFromSuperlayer() + } + } + } else { + if let blockBackgroundView = self.blockBackgroundView { + self.blockBackgroundView = nil + blockBackgroundView.removeFromSuperview() + } + if let blockExpandArrow = self.blockExpandArrow { + self.blockExpandArrow = nil + blockExpandArrow.removeFromSuperlayer() + } + if let quoteTypeIconNode = self.quoteTypeIconNode { + self.quoteTypeIconNode = nil + quoteTypeIconNode.removeFromSupernode() + } + + if self.isAnimating { + self.isAnimating = false + self.currentAnimationId += 1 + } + } + + animation.animator.updateFrame(layer: self.renderNode.layer, frame: effectiveContentFrame, completion: nil) + + var staticContentMask = contentMask + if let contentMask, self.isAnimating { + staticContentMask = nil + + var contentMaskAnimation = animation + let contentMaskNode: ASImageNode + if let current = self.contentMaskNode { + contentMaskNode = current + } else { + contentMaskNode = ASImageNode() + contentMaskNode.isLayerBacked = true + contentMaskNode.backgroundColor = .clear + self.contentMaskNode = contentMaskNode + self.renderNode.layer.mask = contentMaskNode.layer + + if let currentContentMask = self.currentContentMask { + contentMaskNode.frame = currentContentMask.frame + } else { + contentMaskAnimation = .None + } + + contentMaskNode.image = contentMask.image + } + + contentMaskAnimation.animator.updateBackgroundColor(layer: contentMaskNode.layer, color: contentMask.isOpaque ? UIColor.white : UIColor.clear, completion: nil) + contentMaskAnimation.animator.updateFrame(layer: contentMaskNode.layer, frame: contentMask.frame, completion: nil) + } else { + if let contentMaskNode = self.contentMaskNode { + self.contentMaskNode = nil + self.renderNode.layer.mask = nil + contentMaskNode.layer.removeFromSuperlayer() + } + } + + self.currentContentMask = contentMask + + self.renderNode.params = RenderParams(size: contentFrame.size, item: item, mask: staticContentMask) + if synchronously { + let previousContents = self.renderNode.layer.contents + self.renderNode.displayImmediately() + if animateContents, let previousContents { + animation.transition.animateContents(layer: self.renderNode.layer, from: previousContents) + } + } else { + self.renderNode.setNeedsDisplay() + } + } +} diff --git a/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextNodeWithEntities.swift b/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextNodeWithEntities.swift new file mode 100644 index 00000000000..77fd404cb17 --- /dev/null +++ b/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextNodeWithEntities.swift @@ -0,0 +1,375 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import CoreText +import AppBundle +import ComponentFlow +import TextFormat +import AccountContext +import AnimationCache +import MultiAnimationRenderer +import TelegramCore +import EmojiTextAttachmentView +import InvisibleInkDustNode + +private final class InlineStickerItem: Hashable { + let emoji: ChatTextInputTextCustomEmojiAttribute + let file: TelegramMediaFile? + let fontSize: CGFloat + + init(emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, fontSize: CGFloat) { + self.emoji = emoji + self.file = file + self.fontSize = fontSize + } + + func hash(into hasher: inout Hasher) { + hasher.combine(emoji.fileId) + hasher.combine(self.fontSize) + } + + static func ==(lhs: InlineStickerItem, rhs: InlineStickerItem) -> Bool { + if lhs.emoji.fileId != rhs.emoji.fileId { + return false + } + if lhs.file?.fileId != rhs.file?.fileId { + return false + } + if lhs.fontSize != rhs.fontSize { + return false + } + return true + } +} + +private final class RunDelegateData { + let ascent: CGFloat + let descent: CGFloat + let width: CGFloat + + init(ascent: CGFloat, descent: CGFloat, width: CGFloat) { + self.ascent = ascent + self.descent = descent + self.width = width + } +} + +public final class InteractiveTextNodeWithEntities { + public final class Arguments { + public let context: AccountContext + public let cache: AnimationCache + public let renderer: MultiAnimationRenderer + public let placeholderColor: UIColor + public let attemptSynchronous: Bool + public let textColor: UIColor + public let spoilerEffectColor: UIColor + public let animation: ListViewItemUpdateAnimation + + public init( + context: AccountContext, + cache: AnimationCache, + renderer: MultiAnimationRenderer, + placeholderColor: UIColor, + attemptSynchronous: Bool, + textColor: UIColor, + spoilerEffectColor: UIColor, + animation: ListViewItemUpdateAnimation + ) { + self.context = context + self.cache = cache + self.renderer = renderer + self.placeholderColor = placeholderColor + self.attemptSynchronous = attemptSynchronous + self.textColor = textColor + self.spoilerEffectColor = spoilerEffectColor + self.animation = animation + } + + public func withUpdatedPlaceholderColor(_ color: UIColor) -> Arguments { + return Arguments( + context: self.context, + cache: self.cache, + renderer: self.renderer, + placeholderColor: color, + attemptSynchronous: self.attemptSynchronous, + textColor: self.textColor, + spoilerEffectColor: self.spoilerEffectColor, + animation: self.animation + ) + } + } + + private final class InlineStickerItemLayerData { + let itemLayer: InlineStickerItemLayer + var rect: CGRect = CGRect() + + init(itemLayer: InlineStickerItemLayer) { + self.itemLayer = itemLayer + } + } + + public let textNode: InteractiveTextNode + + private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayerData] = [:] + private var dustEffectNodes: [Int: InvisibleInkDustNode] = [:] + + private var enableLooping: Bool = true + + public var visibilityRect: CGRect? { + didSet { + if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibilityRect { + for (_, itemLayerData) in self.inlineStickerItemLayers { + let isItemVisible: Bool + if let visibilityRect = self.visibilityRect { + if itemLayerData.rect.intersects(visibilityRect) { + isItemVisible = true + } else { + isItemVisible = false + } + } else { + isItemVisible = false + } + itemLayerData.itemLayer.isVisibleForAnimations = self.enableLooping && isItemVisible + } + } + } + } + + public init() { + self.textNode = InteractiveTextNode() + } + + private init(textNode: InteractiveTextNode) { + self.textNode = textNode + } + + public static func asyncLayout(_ maybeNode: InteractiveTextNodeWithEntities?) -> (InteractiveTextNodeLayoutArguments) -> (InteractiveTextNodeLayout, (InteractiveTextNodeWithEntities.Arguments?) -> InteractiveTextNodeWithEntities) { + let makeLayout = InteractiveTextNode.asyncLayout(maybeNode?.textNode) + return { [weak maybeNode] arguments in + var updatedString: NSAttributedString? + if let sourceString = arguments.attributedString { + let string = NSMutableAttributedString(attributedString: sourceString) + + var fullRange = NSRange(location: 0, length: string.length) + var originalTextId = 0 + while true { + var found = false + string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, stop in + if let value = value as? ChatTextInputTextCustomEmojiAttribute, let font = string.attribute(.font, at: range.location, effectiveRange: nil) as? UIFont { + let updatedSubstring = NSMutableAttributedString(string: "&") + + let replacementRange = NSRange(location: 0, length: updatedSubstring.length) + updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange) + updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange) + updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange) + originalTextId += 1 + + let itemSize = (font.pointSize * 24.0 / 17.0) + + let runDelegateData = RunDelegateData( + ascent: font.ascender, + descent: font.descender, + width: itemSize + ) + var callbacks = CTRunDelegateCallbacks( + version: kCTRunDelegateCurrentVersion, + dealloc: { dataRef in + Unmanaged.fromOpaque(dataRef).release() + }, + getAscent: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().ascent + }, + getDescent: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().descent + }, + getWidth: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().width + } + ) + + if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) { + updatedSubstring.addAttribute(NSAttributedString.Key(kCTRunDelegateAttributeName as String), value: runDelegate, range: replacementRange) + } + + string.replaceCharacters(in: range, with: updatedSubstring) + let updatedRange = NSRange(location: range.location, length: updatedSubstring.length) + + found = true + stop.pointee = ObjCBool(true) + fullRange = NSRange(location: updatedRange.upperBound, length: fullRange.upperBound - range.upperBound) + } + }) + if !found { + break + } + } + + updatedString = string + } + + let (layout, apply) = makeLayout(arguments.withAttributedString(updatedString)) + return (layout, { applyArguments in + let animation: ListViewItemUpdateAnimation = applyArguments?.animation ?? .None + + let result = apply(animation) + + if let maybeNode = maybeNode { + if let applyArguments = applyArguments { + maybeNode.updateInteractiveContents(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false, textColor: applyArguments.textColor, spoilerEffectColor: applyArguments.spoilerEffectColor, animation: animation) + } + + return maybeNode + } else { + let resultNode = InteractiveTextNodeWithEntities(textNode: result) + + if let applyArguments = applyArguments { + resultNode.updateInteractiveContents(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false, textColor: applyArguments.textColor, spoilerEffectColor: applyArguments.spoilerEffectColor, animation: .None) + } + + return resultNode + } + }) + } + } + + private func isItemVisible(itemRect: CGRect) -> Bool { + if let visibilityRect = self.visibilityRect { + return itemRect.intersects(visibilityRect) + } else { + return false + } + } + + private func updateInteractiveContents( + context: AccountContext, + cache: AnimationCache, + renderer: MultiAnimationRenderer, + textLayout: InteractiveTextNodeLayout?, + placeholderColor: UIColor, + attemptSynchronousLoad: Bool, + textColor: UIColor, + spoilerEffectColor: UIColor, + animation: ListViewItemUpdateAnimation + ) { + self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji + + var displayContentsUnderSpoilers = false + if let textLayout { + displayContentsUnderSpoilers = textLayout.displayContentsUnderSpoilers + } + + var nextIndexById: [Int64: Int] = [:] + var validIds: [InlineStickerItemLayer.Key] = [] + + var validDustEffectIds: [Int] = [] + + if let textLayout { + for i in 0 ..< textLayout.segments.count { + let segment = textLayout.segments[i] + guard let segmentLayer = self.textNode.segmentLayer(index: i), let segmentItem = segmentLayer.item else { + continue + } + + for item in segment.embeddedItems { + if let stickerItem = item.value as? InlineStickerItem { + let index: Int + if let currentNext = nextIndexById[stickerItem.emoji.fileId] { + index = currentNext + } else { + index = 0 + } + nextIndexById[stickerItem.emoji.fileId] = index + 1 + let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index) + validIds.append(id) + + let itemSize = floorToScreenPixels(stickerItem.fontSize * 24.0 / 17.0) + + var itemFrame = CGRect(origin: item.rect.center, size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0) + itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x) + itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y) + + itemFrame.origin.x += segmentItem.contentOffset.x + itemFrame.origin.y += segmentItem.contentOffset.y + + let itemLayerData: InlineStickerItemLayerData + var itemLayerTransition = animation.transition + if let current = self.inlineStickerItemLayers[id] { + itemLayerData = current + itemLayerData.itemLayer.dynamicColor = item.textColor + + if itemLayerData.itemLayer.superlayer !== segmentLayer.renderNode.layer { + segmentLayer.addSublayer(itemLayerData.itemLayer) + } + } else { + itemLayerTransition = .immediate + let pointSize = floor(itemSize * 1.3) + itemLayerData = InlineStickerItemLayerData(itemLayer: InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor)) + self.inlineStickerItemLayers[id] = itemLayerData + segmentLayer.renderNode.layer.addSublayer(itemLayerData.itemLayer) + + itemLayerData.itemLayer.isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame.offsetBy(dx: -segmentItem.contentOffset.x, dy: -segmentItem.contentOffset.x)) + } + + itemLayerTransition.updateAlpha(layer: itemLayerData.itemLayer, alpha: item.isHiddenBySpoiler ? 0.0 : 1.0) + + itemLayerData.itemLayer.frame = itemFrame + itemLayerData.rect = itemFrame.offsetBy(dx: -segmentItem.contentOffset.x, dy: -segmentItem.contentOffset.y) + } + } + + if !segment.spoilers.isEmpty { + validDustEffectIds.append(i) + + let dustEffectNode: InvisibleInkDustNode + if let current = self.dustEffectNodes[i] { + dustEffectNode = current + if dustEffectNode.layer.superlayer !== segmentLayer.renderNode.layer { + segmentLayer.renderNode.layer.addSublayer(dustEffectNode.layer) + } + } else { + dustEffectNode = InvisibleInkDustNode(textNode: nil, enableAnimations: context.sharedContext.energyUsageSettings.fullTranslucency) + self.dustEffectNodes[i] = dustEffectNode + segmentLayer.renderNode.layer.addSublayer(dustEffectNode.layer) + } + let dustNodeFrame = CGRect(origin: CGPoint(), size: segmentItem.size).insetBy(dx: -3.0, dy: -3.0) + dustEffectNode.frame = dustNodeFrame + dustEffectNode.update( + size: dustNodeFrame.size, + color: spoilerEffectColor, + textColor: textColor, + rects: segment.spoilers.map { $0.1.offsetBy(dx: 3.0 + segmentItem.contentOffset.x, dy: segmentItem.contentOffset.y + 3.0).insetBy(dx: 1.0, dy: 1.0) }, + wordRects: segment.spoilerWords.map { $0.1.offsetBy(dx: segmentItem.contentOffset.x + 3.0, dy: segmentItem.contentOffset.y + 3.0).insetBy(dx: 1.0, dy: 1.0) } + ) + + animation.transition.updateAlpha(node: dustEffectNode, alpha: displayContentsUnderSpoilers ? 0.0 : 1.0) + } + } + } + + var removeKeys: [InlineStickerItemLayer.Key] = [] + for (key, itemLayerData) in self.inlineStickerItemLayers { + if !validIds.contains(key) { + removeKeys.append(key) + itemLayerData.itemLayer.removeFromSuperlayer() + } + } + for key in removeKeys { + self.inlineStickerItemLayers.removeValue(forKey: key) + } + + var removeDustEffectIds: [Int] = [] + for (id, dustEffectNode) in self.dustEffectNodes { + if !validDustEffectIds.contains(id) { + removeDustEffectIds.append(id) + dustEffectNode.removeFromSupernode() + } + } + for id in removeDustEffectIds { + self.dustEffectNodes.removeValue(forKey: id) + } + } +} diff --git a/submodules/TelegramUI/Components/ItemListDatePickerItem/BUILD b/submodules/TelegramUI/Components/ItemListDatePickerItem/BUILD index 4fd87b12e81..c455c4f2ca6 100644 --- a/submodules/TelegramUI/Components/ItemListDatePickerItem/BUILD +++ b/submodules/TelegramUI/Components/ItemListDatePickerItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/LegacyCamera/BUILD b/submodules/TelegramUI/Components/LegacyCamera/BUILD index b9cc01c7284..0840d34cef3 100644 --- a/submodules/TelegramUI/Components/LegacyCamera/BUILD +++ b/submodules/TelegramUI/Components/LegacyCamera/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/LegacyInstantVideoController/BUILD b/submodules/TelegramUI/Components/LegacyInstantVideoController/BUILD index 4ffab8aeb76..ad0e2e95c5e 100644 --- a/submodules/TelegramUI/Components/LegacyInstantVideoController/BUILD +++ b/submodules/TelegramUI/Components/LegacyInstantVideoController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD b/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD index ce295914a0d..2d1a19c39d3 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift index 2472f96dfca..a3d855082c4 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -20,6 +20,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { private let context: AccountContext private let chatLocation: ChatLocation private let isScheduledMessages: Bool + private let isFile: Bool private let present: (ViewController) -> Void private let presentInGlobalOverlay: (ViewController) -> Void private let makeEntityInputView: () -> LegacyMessageInputPanelInputView? @@ -44,6 +45,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, + isFile: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void, makeEntityInputView: @escaping () -> LegacyMessageInputPanelInputView? @@ -51,6 +53,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { self.context = context self.chatLocation = chatLocation self.isScheduledMessages = isScheduledMessages + self.isFile = isFile self.present = present self.presentInGlobalOverlay = presentInGlobalOverlay self.makeEntityInputView = makeEntityInputView @@ -158,17 +161,21 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let theme = defaultDarkColorPresentationTheme - var timeoutValue: String + var timeoutValue: String? var timeoutSelected = false - if let timeout = self.currentTimeout { - if timeout == viewOnceTimeout { - timeoutValue = "1" + if self.isFile { + timeoutValue = nil + } else { + if let timeout = self.currentTimeout { + if timeout == viewOnceTimeout { + timeoutValue = "1" + } else { + timeoutValue = "\(timeout)" + } + timeoutSelected = true } else { - timeoutValue = "\(timeout)" + timeoutValue = "1" } - timeoutSelected = true - } else { - timeoutValue = "1" } var maxInputPanelHeight = maxHeight diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/BUILD b/submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/BUILD index e3708e9d4f7..6aed185ee65 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/BUILD +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanelInputView/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/ListActionItemComponent/BUILD b/submodules/TelegramUI/Components/ListActionItemComponent/BUILD index e9b7043eb54..2b983259ea3 100644 --- a/submodules/TelegramUI/Components/ListActionItemComponent/BUILD +++ b/submodules/TelegramUI/Components/ListActionItemComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift index 11620ebcf9a..12ca89591c7 100644 --- a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift +++ b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift @@ -105,7 +105,7 @@ public final class ListActionItemComponent: Component { } case check(Check) - case custom(AnyComponentWithIdentity) + case custom(AnyComponentWithIdentity, Bool) } public enum Highlighting { @@ -113,42 +113,62 @@ public final class ListActionItemComponent: Component { case disabled } + public enum Alignment { + case `default` + case center + } + public let theme: PresentationTheme + public let background: AnyComponent? public let title: AnyComponent + public let titleAlignment: Alignment public let contentInsets: UIEdgeInsets public let leftIcon: LeftIcon? public let icon: Icon? public let accessory: Accessory? public let action: ((UIView) -> Void)? public let highlighting: Highlighting + public let updateIsHighlighted: ((UIView, Bool) -> Void)? public init( theme: PresentationTheme, + background: AnyComponent? = nil, 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, accessory: Accessory? = .arrow, action: ((UIView) -> Void)?, - highlighting: Highlighting = .default + highlighting: Highlighting = .default, + updateIsHighlighted: ((UIView, Bool) -> Void)? = nil ) { self.theme = theme + self.background = background self.title = title + self.titleAlignment = titleAlignment self.contentInsets = contentInsets self.leftIcon = leftIcon self.icon = icon self.accessory = accessory self.action = action self.highlighting = highlighting + self.updateIsHighlighted = updateIsHighlighted } public static func ==(lhs: ListActionItemComponent, rhs: ListActionItemComponent) -> Bool { if lhs.theme !== rhs.theme { return false } + if lhs.background != rhs.background { + return false + } if lhs.title != rhs.title { return false } + if lhs.titleAlignment != rhs.titleAlignment { + return false + } if lhs.contentInsets != rhs.contentInsets { return false } @@ -244,6 +264,7 @@ public final class ListActionItemComponent: Component { } public final class View: HighlightTrackingButton, ListSectionComponent.ChildView { + private var background: ComponentView? private let title = ComponentView() private var leftIcon: ComponentView? private var leftCheckView: CheckView? @@ -276,6 +297,7 @@ public final class ListActionItemComponent: Component { guard let self, let component = self.component, component.action != nil else { return } + component.updateIsHighlighted?(self, isHighlighted) if isHighlighted, component.highlighting == .disabled { return } @@ -373,14 +395,13 @@ public final class ListActionItemComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width - contentLeftInset - contentRightInset, height: availableSize.height) ) - let titleFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: contentHeight), size: titleSize) - if let titleView = self.title.view { - if titleView.superview == nil { - titleView.isUserInteractionEnabled = false - self.addSubview(titleView) - } - transition.setFrame(view: titleView, frame: titleFrame) + + if case .center = component.titleAlignment { + contentLeftInset = floor((availableSize.width - titleSize.width) / 2.0) } + + + let titleY = contentHeight contentHeight += titleSize.height contentHeight += component.contentInsets.bottom @@ -484,9 +505,9 @@ public final class ListActionItemComponent: Component { transition.setBounds(view: leftCheckView, bounds: CGRect(origin: CGPoint(), size: checkFrame.size)) leftCheckView.update(size: checkFrame.size, theme: component.theme, isSelected: check.isSelected, transition: transition) } - case let .custom(customLeftIcon): + case let .custom(customLeftIcon, adjustLeftInset): var resetLeftIcon = false - if case let .custom(previousCustomLeftIcon) = previousComponent?.leftIcon { + if case let .custom(previousCustomLeftIcon, _) = previousComponent?.leftIcon { if previousCustomLeftIcon.id != customLeftIcon.id { resetLeftIcon = true } @@ -526,7 +547,13 @@ public final class ListActionItemComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width, height: availableSize.height) ) - let leftIconFrame = CGRect(origin: CGPoint(x: floor((contentLeftInset - leftIconSize.width) * 0.5), y: floor((min(60.0, contentHeight) - leftIconSize.height) * 0.5)), size: leftIconSize) + let leftIconX: CGFloat + if adjustLeftInset { + leftIconX = 15.0 + } else { + leftIconX = floor((contentLeftInset - leftIconSize.width) * 0.5) + } + let leftIconFrame = CGRect(origin: CGPoint(x: leftIconX, y: floor((min(60.0, contentHeight) - leftIconSize.height) * 0.5)), size: leftIconSize) if let leftIconView = leftIcon.view { if leftIconView.superview == nil { leftIconView.isUserInteractionEnabled = false @@ -535,6 +562,9 @@ public final class ListActionItemComponent: Component { } leftIconTransition.setFrame(view: leftIconView, frame: leftIconFrame) } + if adjustLeftInset { + contentLeftInset = 22.0 + leftIconSize.width + } } } else { if let leftIcon = self.leftIcon { @@ -722,8 +752,54 @@ public final class ListActionItemComponent: Component { } } + let titleFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: titleY), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + titleView.isUserInteractionEnabled = false + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + self.separatorInset = contentLeftInset + if let backgroundComponent = component.background { + var backgroundTransition = transition + let background: ComponentView + if let current = self.background { + background = current + } else { + backgroundTransition = backgroundTransition.withAnimation(.none) + background = ComponentView() + self.background = background + } + + let backgroundSize = background.update( + transition: backgroundTransition, + component: backgroundComponent, + environment: {}, + containerSize: CGSize(width: availableSize.width, height: contentHeight) + ) + let backgroundFrame = CGRect(origin: .zero, size: backgroundSize) + if let backgroundView = background.view { + if backgroundView.superview == nil { + backgroundView.isUserInteractionEnabled = false + self.addSubview(backgroundView) + transition.animateAlpha(view: backgroundView, from: 0.0, to: 1.0) + } + backgroundTransition.setFrame(view: backgroundView, frame: backgroundFrame) + } + } else { + if let background = self.background { + self.background = nil + if let backgroundView = background.view { + transition.setAlpha(view: backgroundView, alpha: 0.0, completion: { [weak backgroundView] _ in + backgroundView?.removeFromSuperview() + }) + } + } + } + return CGSize(width: availableSize.width, height: contentHeight) } } diff --git a/submodules/TelegramUI/Components/ListItemComponentAdaptor/BUILD b/submodules/TelegramUI/Components/ListItemComponentAdaptor/BUILD index 1ebd9ea2ec5..1043fb1bae3 100644 --- a/submodules/TelegramUI/Components/ListItemComponentAdaptor/BUILD +++ b/submodules/TelegramUI/Components/ListItemComponentAdaptor/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/ListItemSliderSelectorComponent/BUILD b/submodules/TelegramUI/Components/ListItemSliderSelectorComponent/BUILD index 9e20c794e64..e7e09cd25fa 100644 --- a/submodules/TelegramUI/Components/ListItemSliderSelectorComponent/BUILD +++ b/submodules/TelegramUI/Components/ListItemSliderSelectorComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/ListItemSwipeOptionContainer/BUILD b/submodules/TelegramUI/Components/ListItemSwipeOptionContainer/BUILD index c5634af4055..09b935cbc61 100644 --- a/submodules/TelegramUI/Components/ListItemSwipeOptionContainer/BUILD +++ b/submodules/TelegramUI/Components/ListItemSwipeOptionContainer/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/BUILD b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/BUILD index cab35652152..4b7a198226e 100644 --- a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/BUILD +++ b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift index 64e26e0594a..1a9ccf535fa 100644 --- a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift +++ b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift @@ -257,6 +257,7 @@ public final class ListMultilineTextFieldItemComponent: Component { externalState: self.textFieldExternalState, fontSize: 17.0, textColor: component.theme.list.itemPrimaryTextColor, + accentColor: component.theme.list.itemPrimaryTextColor, insets: UIEdgeInsets(top: verticalInset, left: sideInset - 8.0, bottom: verticalInset, right: sideInset - 8.0 + measureTextLimitInset), hideKeyboard: false, customInputView: nil, diff --git a/submodules/TelegramUI/Components/ListSectionComponent/BUILD b/submodules/TelegramUI/Components/ListSectionComponent/BUILD index e216e0bb5df..ed4933e7fbb 100644 --- a/submodules/TelegramUI/Components/ListSectionComponent/BUILD +++ b/submodules/TelegramUI/Components/ListSectionComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/ListTextFieldItemComponent/BUILD b/submodules/TelegramUI/Components/ListTextFieldItemComponent/BUILD index bbb2f2118f7..c42d4c602a4 100644 --- a/submodules/TelegramUI/Components/ListTextFieldItemComponent/BUILD +++ b/submodules/TelegramUI/Components/ListTextFieldItemComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/LottieAnimationCache/BUILD b/submodules/TelegramUI/Components/LottieAnimationCache/BUILD index 15079a80909..0451a5708e1 100644 --- a/submodules/TelegramUI/Components/LottieAnimationCache/BUILD +++ b/submodules/TelegramUI/Components/LottieAnimationCache/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/LottieComponent/BUILD b/submodules/TelegramUI/Components/LottieComponent/BUILD index bf53f8f7924..e9c9361efb9 100644 --- a/submodules/TelegramUI/Components/LottieComponent/BUILD +++ b/submodules/TelegramUI/Components/LottieComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/LottieComponentEmojiContent/BUILD b/submodules/TelegramUI/Components/LottieComponentEmojiContent/BUILD index e6e41c711f8..85d512824a8 100644 --- a/submodules/TelegramUI/Components/LottieComponentEmojiContent/BUILD +++ b/submodules/TelegramUI/Components/LottieComponentEmojiContent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/LottieComponentResourceContent/BUILD b/submodules/TelegramUI/Components/LottieComponentResourceContent/BUILD index e745190452d..e84f9dbc5f7 100644 --- a/submodules/TelegramUI/Components/LottieComponentResourceContent/BUILD +++ b/submodules/TelegramUI/Components/LottieComponentResourceContent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/LottieCpp/BUILD b/submodules/TelegramUI/Components/LottieCpp/BUILD new file mode 100644 index 00000000000..9743934e12a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/BUILD @@ -0,0 +1,48 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +objc_library( + name = "LottieCpp", + enable_modules = True, + module_name = "LottieCpp", + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.mm", + "Sources/**/*.h", + "Sources/**/*.c", + "Sources/**/*.cpp", + "Sources/**/*.hpp", + ]), + copts = [ + "-Werror", + "-I{}/Sources".format(package_name()), + ], + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + deps = [ + ], + sdk_frameworks = [ + "Foundation", + ], + visibility = [ + "//visibility:public", + ], +) + +cc_library( + name = "LottieCppBinding", + srcs = [], + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + copts = [], + visibility = ["//visibility:public"], + linkstatic = 1, + tags = ["swift_module=LottieCppBinding"], +) diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h new file mode 100644 index 00000000000..b17f942761f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h @@ -0,0 +1,155 @@ +#ifndef BezierPath_h +#define BezierPath_h + +#ifdef __cplusplus + +#include +#include +#include +#include + +#include + +namespace lottie { + +struct BezierTrimPathPosition { + float start; + float end; + + explicit BezierTrimPathPosition(float start_, float end_); +}; + +class BezierPathContents: public std::enable_shared_from_this { +public: + explicit BezierPathContents(CurveVertex const &startPoint); + + BezierPathContents(); + + explicit BezierPathContents(lottiejson11::Json const &jsonAny) noexcept(false); + + BezierPathContents(const BezierPathContents&) = delete; + BezierPathContents& operator=(BezierPathContents&) = delete; + + lottiejson11::Json toJson() const; + + std::shared_ptr cgPath() const; + +public: + std::vector elements; + std::optional closed; + + float length(); + +private: + std::optional _length; + +public: + void moveToStartPoint(CurveVertex const &vertex); + void addVertex(CurveVertex const &vertex); + + void reserveCapacity(size_t capacity); + void setElementCount(size_t count); + void invalidateLength(); + + void addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent); + void addLine(Vector2D const &toPoint); + void close(); + void addElement(PathElement const &pathElement); + void updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure); + + /// Trims a path fromLength toLength with an offset. + /// + /// Length and offset are defined in the length coordinate space. + /// If any argument is outside the range of this path, then it will be looped over the path from finish to start. + /// + /// Cutting the curve when fromLength is less than toLength + /// x x x x + /// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- + /// |Offset |fromLength toLength| | + /// + /// Cutting the curve when from Length is greater than toLength + /// x x x x x + /// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo + /// | toLength| |Offset |fromLength | + /// + std::vector> trim(float fromLength, float toLength, float offsetLength); + + // MARK: Private + + std::vector> trimPathAtLengths(std::vector const &positions); +}; + +class BezierPath { +public: + explicit BezierPath(CurveVertex const &startPoint); + BezierPath(); + explicit BezierPath(lottiejson11::Json const &jsonAny) noexcept(false); + + lottiejson11::Json toJson() const; + + float length(); + + void moveToStartPoint(CurveVertex const &vertex); + void addVertex(CurveVertex const &vertex); + void reserveCapacity(size_t capacity); + void setElementCount(size_t count); + void invalidateLength(); + void addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent); + void addLine(Vector2D const &toPoint); + void close(); + void addElement(PathElement const &pathElement); + void updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure); + + /// Trims a path fromLength toLength with an offset. + /// + /// Length and offset are defined in the length coordinate space. + /// If any argument is outside the range of this path, then it will be looped over the path from finish to start. + /// + /// Cutting the curve when fromLength is less than toLength + /// x x x x + /// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- + /// |Offset |fromLength toLength| | + /// + /// Cutting the curve when from Length is greater than toLength + /// x x x x x + /// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo + /// | toLength| |Offset |fromLength | + /// + std::vector trim(float fromLength, float toLength, float offsetLength); + + std::vector const &elements() const; + std::vector &mutableElements(); + std::optional const &closed() const; + void setClosed(std::optional const &closed); + std::shared_ptr cgPath() const; + BezierPath copyUsingTransform(Transform2D const &transform) const; + +public: + BezierPath(std::shared_ptr contents); + +public: + std::shared_ptr _contents; +}; + +class BezierPathsBoundingBoxContext { +public: + BezierPathsBoundingBoxContext(); + ~BezierPathsBoundingBoxContext(); + +public: + float *pointsX = nullptr; + float *pointsY = nullptr; + int pointsSize = 0; +}; + +CGRect bezierPathsBoundingBox(std::vector const &paths); +CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector const &paths); +CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, BezierPath const &path); + +std::vector trimBezierPaths(std::vector &sourcePaths, float start, float end, float offset, TrimType type); + +} + +#endif + +#endif /* BezierPath_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPath.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPath.h new file mode 100644 index 00000000000..1ddeeb81edf --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPath.h @@ -0,0 +1,80 @@ +#ifndef CGPath_hpp +#define CGPath_hpp + +#ifdef __cplusplus + +#include + +#include +#include + +namespace lottie { + +struct CGPathItem { + enum class Type { + MoveTo, + LineTo, + CurveTo, + Close + }; + + Type type; + Vector2D points[3] = { Vector2D(0.0, 0.0), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0) }; + + explicit CGPathItem(Type type_) : + type(type_) { + } + + bool operator==(const CGPathItem &rhs) const { + if (type != rhs.type) { + return false; + } + if (points[0] != rhs.points[0]) { + return false; + } + if (points[1] != rhs.points[1]) { + return false; + } + if (points[2] != rhs.points[2]) { + return false; + } + + return true; + } + + bool operator!=(const CGPathItem &rhs) const { + return !(*this == rhs); + } +}; + +class CGPath { +public: + static std::shared_ptr makePath(); + + virtual ~CGPath() = default; + + virtual CGRect boundingBox() const = 0; + + virtual bool empty() const = 0; + + virtual std::shared_ptr copyUsingTransform(Transform2D const &transform) const = 0; + + virtual void addLineTo(Vector2D const &point) = 0; + virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) = 0; + virtual void moveTo(Vector2D const &point) = 0; + virtual void closeSubpath() = 0; + virtual void addRect(CGRect const &rect) = 0; + virtual void addPath(std::shared_ptr const &path) = 0; + + virtual void enumerate(std::function) = 0; + + virtual bool isEqual(CGPath *other) const = 0; +}; + +Vector2D transformVector(Vector2D const &v, Transform2D const &m); + +} + +#endif + +#endif /* CGPath_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPathCocoa.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPathCocoa.h new file mode 100644 index 00000000000..af32f20bb59 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPathCocoa.h @@ -0,0 +1,47 @@ +#ifndef CGPathCocoa_h +#define CGPathCocoa_h + +#ifdef __cplusplus + +#include + +#include +#include + +CGRect calculatePathBoundingBox(CGPathRef path); + +namespace lottie { + +class CGPathCocoaImpl: public CGPath { +public: + CGPathCocoaImpl(); + explicit CGPathCocoaImpl(CGMutablePathRef path); + virtual ~CGPathCocoaImpl(); + + virtual CGRect boundingBox() const override; + + virtual bool empty() const override; + + virtual std::shared_ptr copyUsingTransform(Transform2D const &transform) const override; + + virtual void addLineTo(Vector2D const &point) override; + virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) override; + virtual void moveTo(Vector2D const &point) override; + virtual void closeSubpath() override; + virtual void addRect(CGRect const &rect) override; + virtual void addPath(std::shared_ptr const &path) override; + virtual CGPathRef nativePath() const; + virtual bool isEqual(CGPath *other) const override; + virtual void enumerate(std::function) override; + + static void withNativePath(std::shared_ptr const &path, std::function f); + +private: + ::CGMutablePathRef _path = nil; +}; + +} + +#endif + +#endif /* CGPathCocoa_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Color.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Color.h new file mode 100644 index 00000000000..6e404f29ae9 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Color.h @@ -0,0 +1,55 @@ +#ifndef LottieColor_h +#define LottieColor_h + +#ifdef __cplusplus + +#include +#include + +namespace lottie { + +enum class ColorFormatDenominator { + One, + OneHundred, + TwoFiftyFive +}; + +struct Color { + float r; + float g; + float b; + float a; + + bool operator==(Color const &rhs) const { + if (r != rhs.r) { + return false; + } + if (g != rhs.g) { + return false; + } + if (b != rhs.b) { + return false; + } + if (a != rhs.a) { + return false; + } + return true; + } + + bool operator!=(Color const &rhs) const { + return !(*this == rhs); + } + + explicit Color(float r_, float g_, float b_, float a_, ColorFormatDenominator denominator = ColorFormatDenominator::One); + explicit Color(lottiejson11::Json const &jsonAny) noexcept(false); + + lottiejson11::Json toJson() const; + + static Color fromString(std::string const &string); +}; + +} + +#endif + +#endif /* LottieColor_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CurveVertex.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CurveVertex.h new file mode 100644 index 00000000000..90e3ccf305f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CurveVertex.h @@ -0,0 +1,200 @@ +#ifndef CurveVertex_h +#define CurveVertex_h + +#ifdef __cplusplus + +#include +#include + +#include + +namespace lottie { + +template +struct CurveVertexSplitResult { + T start; + T trimPoint; + T end; + + explicit CurveVertexSplitResult( + T const &start_, + T const &trimPoint_, + T const &end_ + ) : + start(start_), + trimPoint(trimPoint_), + end(end_) { + } +}; + +/// A single vertex with an in and out tangent +struct __attribute__((packed)) CurveVertex { +private: + /// Initializes a curve point with absolute or relative values + explicit CurveVertex(Vector2D const &point_, Vector2D const &inTangent_, Vector2D const &outTangent_, bool isRelative_) : + point(point_), + inTangent(isRelative_ ? (point_ + inTangent_) : inTangent_), + outTangent(isRelative_ ? (point_ + outTangent_) : outTangent_) { + } + +public: + static CurveVertex absolute(Vector2D const &point_, Vector2D const &inTangent_, Vector2D const &outTangent_) { + return CurveVertex(point_, inTangent_, outTangent_, false); + } + + static CurveVertex relative(Vector2D const &point_, Vector2D const &inTangent_, Vector2D const &outTangent_) { + return CurveVertex(point_, inTangent_, outTangent_, true); + } + + Vector2D inTangentRelative() const { + Vector2D result = inTangent - point; + return result; + } + + Vector2D outTangentRelative() const { + Vector2D result = outTangent - point; + return result; + } + + CurveVertex reversed() const { + return CurveVertex(point, outTangent, inTangent, false); + } + + CurveVertex translated(Vector2D const &translation) const { + return CurveVertex(point + translation, inTangent + translation, outTangent + translation, false); + } + + CurveVertex transformed(Transform2D const &transform) const { + return CurveVertex(transformVector(point, transform), transformVector(inTangent, transform), transformVector(outTangent, transform), false); + } + +public: + Vector2D point = Vector2D::Zero(); + Vector2D inTangent = Vector2D::Zero(); + Vector2D outTangent = Vector2D::Zero(); + + /// Trims a path defined by two Vertices at a specific position, from 0 to 1 + /// + /// The path can be visualized below. + /// + /// F is fromVertex. + /// V is the vertex of the receiver. + /// P is the position from 0-1. + /// O is the outTangent of fromVertex. + /// F====O=========P=======I====V + /// + /// After trimming the curve can be visualized below. + /// + /// S is the returned Start vertex. + /// E is the returned End vertex. + /// T is the trim point. + /// TI and TO are the new tangents for the trimPoint + /// NO and NI are the new tangents for the startPoint and endPoints + /// S==NO=========TI==T==TO=======NI==E + CurveVertexSplitResult splitCurve(CurveVertex const &toVertex, float position) const { + /// If position is less than or equal to 0, trim at start. + if (position <= 0.0) { + return CurveVertexSplitResult( + CurveVertex(point, inTangentRelative(), Vector2D::Zero(), true), + CurveVertex(point, Vector2D::Zero(), outTangentRelative(), true), + toVertex + ); + } + + /// If position is greater than or equal to 1, trim at end. + if (position >= 1.0) { + return CurveVertexSplitResult( + *this, + CurveVertex(toVertex.point, toVertex.inTangentRelative(), Vector2D::Zero(), true), + CurveVertex(toVertex.point, Vector2D::Zero(), toVertex.outTangentRelative(), true) + ); + } + + if (outTangentRelative().isZero() && toVertex.inTangentRelative().isZero()) { + /// If both tangents are zero, then span to be trimmed is a straight line. + Vector2D trimPoint = interpolate(point, toVertex.point, position); + return CurveVertexSplitResult( + *this, + CurveVertex(trimPoint, Vector2D::Zero(), Vector2D::Zero(), true), + toVertex + ); + } + /// Cutting by amount gives incorrect length.... + /// One option is to cut by a stride until it gets close then edge it down. + /// Measuring a percentage of the spans does not equal the same as measuring a percentage of length. + /// This is where the historical trim path bugs come from. + Vector2D a = interpolate(point, outTangent, position); + Vector2D b = interpolate(outTangent, toVertex.inTangent, position); + Vector2D c = interpolate(toVertex.inTangent, toVertex.point, position); + Vector2D d = interpolate(a, b, position); + Vector2D e = interpolate(b, c, position); + Vector2D f = interpolate(d, e, position); + return CurveVertexSplitResult( + CurveVertex::absolute(point, inTangent, a), + CurveVertex::absolute(f, d, e), + CurveVertex::absolute(toVertex.point, c, toVertex.outTangent) + ); + } + + /// Trims a curve of a known length to a specific length and returns the points. + /// + /// There is not a performant yet accurate way to cut a curve to a specific length. + /// This calls splitCurve(toVertex: position:) to split the curve and then measures + /// the length of the new curve. The function then iterates through the samples, + /// adjusting the position of the cut for a more precise cut. + /// Usually a single iteration is enough to get within 0.5 points of the desired + /// length. + /// + /// This function should probably live in PathElement, since it deals with curve + /// lengths. + CurveVertexSplitResult trimCurve(CurveVertex const &toVertex, float atLength, float curveLength, int maxSamples, float accuracy = 1.0f) const { + float currentPosition = atLength / curveLength; + auto results = splitCurve(toVertex, currentPosition); + + if (maxSamples == 0) { + return results; + } + + for (int i = 1; i <= maxSamples; i++) { + auto length = results.start.distanceTo(results.trimPoint); + auto lengthDiff = atLength - length; + /// Check if length is correct. + if (lengthDiff < accuracy) { + return results; + } + auto diffPosition = std::max(std::min((currentPosition / length) * lengthDiff, currentPosition * 0.5f), currentPosition * (-0.5f)); + currentPosition = diffPosition + currentPosition; + results = splitCurve(toVertex, currentPosition); + } + return results; + } + + /// The distance from the receiver to the provided vertex. + /// + /// For lines (zeroed tangents) the distance between the two points is measured. + /// For curves the curve is iterated over by sample count and the points are measured. + /// This is ~99% accurate at a sample count of 30 + float distanceTo(CurveVertex const &toVertex, int sampleCount = 25) const { + if (outTangentRelative().isZero() && toVertex.inTangentRelative().isZero()) { + /// Return a linear distance. + return point.distanceTo(toVertex.point); + } + + float distance = 0.0; + + auto previousPoint = point; + for (int i = 0; i < sampleCount; i++) { + auto pointOnCurve = splitCurve(toVertex, ((float)(i)) / ((float)(sampleCount))).trimPoint; + distance = distance + previousPoint.distanceTo(pointOnCurve.point); + previousPoint = pointOnCurve.point; + } + distance = distance + previousPoint.distanceTo(toVertex.point); + return distance; + } +}; + +} + +#endif + +#endif /* CurveVertex_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimation.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimation.h new file mode 100644 index 00000000000..e7112776ede --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimation.h @@ -0,0 +1,28 @@ +#ifndef LottieAnimation_h +#define LottieAnimation_h + +#import + +#import "LottieRenderTree.h" + +#ifdef __cplusplus +extern "C" { +#endif + +@interface LottieAnimation : NSObject + +@property (nonatomic, readonly) NSInteger frameCount; +@property (nonatomic, readonly) NSInteger framesPerSecond; +@property (nonatomic, readonly) CGSize size; + +- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data; + +- (NSData * _Nonnull)toJson; + +@end + +#ifdef __cplusplus +} +#endif + +#endif /* LottieAnimation_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h new file mode 100644 index 00000000000..f4e936e77da --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h @@ -0,0 +1,71 @@ +#ifndef LottieAnimationContainer_h +#define LottieAnimationContainer_h + +#ifdef __cplusplus + +#import "LottieAnimation.h" +#import "LottieRenderTree.h" +#import "LottieAnimationContainer.h" + +#include + +namespace lottie { + +class RenderTreeNode; + +} + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + CGRect bounds; + CGPoint position; + CATransform3D transform; + float opacity; + bool masksToBounds; + bool isHidden; +} LottieRenderNodeLayerData; + +typedef struct { + int64_t internalId; + bool isValid; + LottieRenderNodeLayerData layer; + CGRect globalRect; + CGRect localRect; + CATransform3D globalTransform; + bool drawsContent; + bool hasSimpleContents; + int drawContentDescendants; + bool isInvertedMatte; + int64_t maskId; + int subnodeCount; +} LottieRenderNodeProxy; + +@interface LottieAnimationContainer : NSObject + +@property (nonatomic, strong, readonly) LottieAnimation * _Nonnull animation; + +- (instancetype _Nonnull)initWithAnimation:(LottieAnimation * _Nonnull)animation; + +- (void)update:(NSInteger)frame; +- (LottieRenderNode * _Nullable)getCurrentRenderTreeForSize:(CGSize)size; + +#ifdef __cplusplus +- (std::shared_ptr)internalGetRootRenderTreeNode; +#endif + +- (int64_t)getRootRenderNodeProxy; +- (LottieRenderNodeProxy)getRenderNodeProxyById:(int64_t)nodeId __attribute__((objc_direct)); +- (LottieRenderNodeProxy)getRenderNodeSubnodeProxyById:(int64_t)nodeId index:(int)index __attribute__((objc_direct)); + +@end + +#ifdef __cplusplus +} +#endif + +#endif /* LottieAnimationContainer_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieCpp.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieCpp.h new file mode 100644 index 00000000000..22091e064b4 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieCpp.h @@ -0,0 +1,20 @@ +#ifndef LottieCpp_h +#define LottieCpp_h + +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#endif /* LottieCpp_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieRenderTree.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieRenderTree.h new file mode 100644 index 00000000000..e4158c3ccbe --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieRenderTree.h @@ -0,0 +1,147 @@ +#ifndef LottieRenderTree_h +#define LottieRenderTree_h + +#import + +#ifdef __cplusplus +extern "C" { +#endif + +typedef NS_ENUM(NSUInteger, LottiePathItemType) { + LottiePathItemTypeMoveTo, + LottiePathItemTypeLineTo, + LottiePathItemTypeCurveTo, + LottiePathItemTypeClose +}; + +typedef struct { + LottiePathItemType type; + CGPoint points[4]; +} LottiePathItem; + +typedef struct { + CGFloat r; + CGFloat g; + CGFloat b; + CGFloat a; +} LottieColor; + +typedef NS_ENUM(NSUInteger, LottieFillRule) { + LottieFillRuleEvenOdd, + LottieFillRuleWinding +}; + +typedef NS_ENUM(NSUInteger, LottieGradientType) { + LottieGradientTypeLinear, + LottieGradientTypeRadial +}; + +@interface LottieColorStop : NSObject + +@property (nonatomic, readonly, direct) LottieColor color; +@property (nonatomic, readonly, direct) CGFloat location; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithColor:(LottieColor)color location:(CGFloat)location __attribute__((objc_direct)); + +@end + +@interface LottiePath : NSObject + +- (void)enumerateItems:(void (^ _Nonnull)(LottiePathItem * _Nonnull))iterate __attribute__((objc_direct)); + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithCustomData:(NSData * _Nonnull)customData __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentShading : NSObject + +@end + +@interface LottieRenderContentSolidShading : LottieRenderContentShading + +@property (nonatomic, readonly, direct) LottieColor color; +@property (nonatomic, readonly, direct) CGFloat opacity; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithColor:(LottieColor)color opacity:(CGFloat)opacity __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentGradientShading : LottieRenderContentShading + +@property (nonatomic, readonly, direct) CGFloat opacity; +@property (nonatomic, readonly, direct) LottieGradientType gradientType; +@property (nonatomic, strong, readonly, direct) NSArray * _Nonnull colorStops; +@property (nonatomic, readonly, direct) CGPoint start; +@property (nonatomic, readonly, direct) CGPoint end; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithOpacity:(CGFloat)opacity gradientType:(LottieGradientType)gradientType colorStops:(NSArray * _Nonnull)colorStops start:(CGPoint)start end:(CGPoint)end __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentFill : NSObject + +@property (nonatomic, strong, readonly, direct) LottieRenderContentShading * _Nonnull shading; +@property (nonatomic, readonly, direct) LottieFillRule fillRule; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading fillRule:(LottieFillRule)fillRule __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentStroke : NSObject + +@property (nonatomic, strong, readonly, direct) LottieRenderContentShading * _Nonnull shading; +@property (nonatomic, readonly, direct) CGFloat lineWidth; +@property (nonatomic, readonly, direct) CGLineJoin lineJoin; +@property (nonatomic, readonly, direct) CGLineCap lineCap; +@property (nonatomic, readonly, direct) CGFloat miterLimit; +@property (nonatomic, readonly, direct) CGFloat dashPhase; +@property (nonatomic, strong, readonly, direct) NSArray * _Nullable dashPattern; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading lineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit dashPhase:(CGFloat)dashPhase dashPattern:(NSArray * _Nullable)dashPattern __attribute__((objc_direct)); + +@end + +@interface LottieRenderContent : NSObject + +@property (nonatomic, strong, readonly, direct) LottiePath * _Nonnull path; +@property (nonatomic, strong, readonly, direct) LottieRenderContentStroke * _Nullable stroke; +@property (nonatomic, strong, readonly, direct) LottieRenderContentFill * _Nullable fill; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithPath:(LottiePath * _Nonnull)path stroke:(LottieRenderContentStroke * _Nullable)stroke fill:(LottieRenderContentFill * _Nullable)fill __attribute__((objc_direct)); + +@end + +@interface LottieRenderNode : NSObject + +@property (nonatomic, readonly, direct) CGPoint position; +@property (nonatomic, readonly, direct) CGRect bounds; +@property (nonatomic, readonly, direct) CATransform3D transform; +@property (nonatomic, readonly, direct) CGFloat opacity; +@property (nonatomic, readonly, direct) bool masksToBounds; +@property (nonatomic, readonly, direct) bool isHidden; + +@property (nonatomic, readonly, direct) CGRect globalRect; +@property (nonatomic, readonly, direct) CATransform3D globalTransform; +@property (nonatomic, readonly, direct) LottieRenderContent * _Nullable renderContent; +@property (nonatomic, readonly, direct) bool hasSimpleContents; +@property (nonatomic, readonly, direct) bool isInvertedMatte; +@property (nonatomic, readonly, direct) NSArray * _Nonnull subnodes; +@property (nonatomic, readonly, direct) LottieRenderNode * _Nullable mask; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithPosition:(CGPoint)position bounds:(CGRect)bounds transform:(CATransform3D)transform opacity:(CGFloat)opacity masksToBounds:(bool)masksToBounds isHidden:(bool)isHidden globalRect:(CGRect)globalRect globalTransform:(CATransform3D)globalTransform renderContent:(LottieRenderContent * _Nullable)renderContent hasSimpleContents:(bool)hasSimpleContents isInvertedMatte:(bool)isInvertedMatte subnodes:(NSArray * _Nonnull)subnodes mask:(LottieRenderNode * _Nullable)mask __attribute__((objc_direct)); + +@end + +#ifdef __cplusplus +} +#endif + +#endif /* LottieRenderTree_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/PathElement.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/PathElement.h new file mode 100644 index 00000000000..bb8a8f578d4 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/PathElement.h @@ -0,0 +1,94 @@ +#ifndef PathElement_h +#define PathElement_h + +#ifdef __cplusplus + +#include + +namespace lottie { + +template +struct PathSplitResultSpan { + T start; + T end; + + explicit PathSplitResultSpan(T const &start_, T const &end_) : + start(start_), end(end_) { + } +}; + +template +struct PathSplitResult { + PathSplitResultSpan leftSpan; + PathSplitResultSpan rightSpan; + + explicit PathSplitResult(PathSplitResultSpan const &leftSpan_, PathSplitResultSpan const &rightSpan_) : + leftSpan(leftSpan_), rightSpan(rightSpan_) { + } +}; + +/// A path section, containing one point and its length to the previous point. +/// +/// The relationship between this path element and the previous is implicit. +/// Ideally a path section would be defined by two vertices and a length. +/// We don't do this however, as it would effectively double the memory footprint +/// of path data. +/// +struct __attribute__((packed)) PathElement { + /// Initializes a new path with length of 0 + explicit PathElement(CurveVertex const &vertex_) : + vertex(vertex_) { + } + + /// Initializes a new path with length + explicit PathElement(std::optional length_, CurveVertex const &vertex_) : + vertex(vertex_) { + } + + /// The vertex of the element + CurveVertex vertex; + + /// Returns a new path element define the span from the receiver to the new vertex. + PathElement pathElementTo(CurveVertex const &toVertex) const { + return PathElement(std::nullopt, toVertex); + } + + PathElement updateVertex(CurveVertex const &newVertex) const { + return PathElement(newVertex); + } + + /// Splits an element span defined by the receiver and fromElement to a position 0-1 + PathSplitResult splitElementAtPosition(PathElement const &fromElement, float atLength) { + /// Trim the span. Start and trim go into the first, trim and end go into second. + auto trimResults = fromElement.vertex.trimCurve(vertex, atLength, length(fromElement), 3); + + /// Create the elements for the break + auto spanAStart = PathElement( + std::nullopt, + CurveVertex::absolute( + fromElement.vertex.point, + fromElement.vertex.inTangent, + trimResults.start.outTangent + )); + /// Recalculating the length here is a waste as the trimCurve function also accurately calculates this length. + auto spanAEnd = spanAStart.pathElementTo(trimResults.trimPoint); + + auto spanBStart = PathElement(trimResults.trimPoint); + auto spanBEnd = spanBStart.pathElementTo(trimResults.end); + return PathSplitResult( + PathSplitResultSpan(spanAStart, spanAEnd), + PathSplitResultSpan(spanBStart, spanBEnd) + ); + } + + float length(PathElement const &previous) { + float result = previous.vertex.distanceTo(vertex); + return result; + } +}; + +} + +#endif + +#endif /* PathElement_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h new file mode 100644 index 00000000000..09616bdf0a8 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h @@ -0,0 +1,471 @@ +#ifndef RenderTreeNode_hpp +#define RenderTreeNode_hpp + +#ifdef __cplusplus + +#include +#include +#include +#include +#include + +#include + +namespace lottie { + +class ProcessedRenderTreeNodeData { +public: + ProcessedRenderTreeNodeData() { + } + + bool isValid = false; + bool isInvertedMatte = false; +}; + +class RenderableItem { +public: + enum class Type { + Shape, + GradientFill + }; + +public: + RenderableItem() { + } + + virtual ~RenderableItem() = default; + + virtual Type type() const = 0; + virtual CGRect boundingRect() const = 0; + + virtual bool isEqual(std::shared_ptr rhs) const = 0; +}; + +class ShapeRenderableItem: public RenderableItem { +public: + struct Fill { + Color color; + FillRule rule; + + Fill(Color color_, FillRule rule_) : + color(color_), rule(rule_) { + } + + bool operator==(Fill const &rhs) const { + if (color != rhs.color) { + return false; + } + if (rule != rhs.rule) { + return false; + } + return true; + } + + bool operator!=(Fill const &rhs) const { + return !(*this == rhs); + } + }; + + struct Stroke { + Color color; + float lineWidth = 0.0; + LineJoin lineJoin = LineJoin::Round; + LineCap lineCap = LineCap::Square; + float dashPhase = 0.0; + std::vector dashPattern; + + Stroke( + Color color_, + float lineWidth_, + LineJoin lineJoin_, + LineCap lineCap_, + float dashPhase_, + std::vector dashPattern_ + ) : + color(color_), + lineWidth(lineWidth_), + lineJoin(lineJoin_), + lineCap(lineCap_), + dashPhase(dashPhase_), + dashPattern(dashPattern_) { + } + + bool operator==(Stroke const &rhs) const { + if (color != rhs.color) { + return false; + } + if (lineWidth != rhs.lineWidth) { + return false; + } + if (lineJoin != rhs.lineJoin) { + return false; + } + if (lineCap != rhs.lineCap) { + return false; + } + if (dashPhase != rhs.dashPhase) { + return false; + } + if (dashPattern != rhs.dashPattern) { + return false; + } + return true; + } + + bool operator!=(Stroke const &rhs) const { + return !(*this == rhs); + } + }; + +public: + ShapeRenderableItem( + std::shared_ptr path_, + std::optional const &fill_, + std::optional const &stroke_ + ) : + path(path_), + fill(fill_), + stroke(stroke_) { + } + + virtual Type type() const override { + return Type::Shape; + } + + virtual CGRect boundingRect() const override { + if (path) { + CGRect shapeBounds = path->boundingBox(); + if (stroke) { + shapeBounds = shapeBounds.insetBy(-stroke->lineWidth / 2.0, -stroke->lineWidth / 2.0); + } + return shapeBounds; + } else { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + } + + virtual bool isEqual(std::shared_ptr rhs) const override { + if (rhs->type() != type()) { + return false; + } + ShapeRenderableItem *other = (ShapeRenderableItem *)rhs.get(); + if ((path == nullptr) != (other->path == nullptr)) { + return false; + } else if (path) { + if (!path->isEqual(other->path.get())) { + return false; + } + } + if (fill != other->fill) { + return false; + } + if (stroke != other->stroke) { + return false; + } + return false; + } + +public: + std::shared_ptr path; + std::optional fill; + std::optional stroke; +}; + +class GradientFillRenderableItem: public RenderableItem { +public: + GradientFillRenderableItem( + std::shared_ptr path_, + FillRule pathFillRule_, + GradientType gradientType_, + std::vector const &colors_, + std::vector const &locations_, + Vector2D const &start_, + Vector2D const &end_, + CGRect bounds_ + ) : + path(path_), + pathFillRule(pathFillRule_), + gradientType(gradientType_), + colors(colors_), + locations(locations_), + start(start_), + end(end_), + bounds(bounds_) { + } + + virtual Type type() const override { + return Type::GradientFill; + } + + virtual CGRect boundingRect() const override { + return bounds; + } + + virtual bool isEqual(std::shared_ptr rhs) const override { + if (rhs->type() != type()) { + return false; + } + GradientFillRenderableItem *other = (GradientFillRenderableItem *)rhs.get(); + + if (gradientType != other->gradientType) { + return false; + } + if (colors != other->colors) { + return false; + } + if (locations != other->locations) { + return false; + } + if (start != other->start) { + return false; + } + if (end != other->end) { + return false; + } + if (bounds != other->bounds) { + return false; + } + + return true; + } + +public: + std::shared_ptr path; + FillRule pathFillRule; + GradientType gradientType; + std::vector colors; + std::vector locations; + Vector2D start; + Vector2D end; + CGRect bounds; +}; + +class RenderTreeNodeContentShadingVariant; + +struct RenderTreeNodeContentPath { +public: + explicit RenderTreeNodeContentPath(BezierPath path_) : + path(path_) { + } + + BezierPath path; + CGRect bounds = CGRect(0.0, 0.0, 0.0, 0.0); + bool needsBoundsRecalculation = true; +}; + +class RenderTreeNodeContentItem { +public: + enum class ShadingType { + Solid, + Gradient + }; + + class Shading { + public: + Shading() { + } + + virtual ~Shading() = default; + + virtual ShadingType type() const = 0; + }; + + class SolidShading: public Shading { + public: + SolidShading(Color const &color_, float opacity_) : + color(color_), + opacity(opacity_) { + } + + virtual ShadingType type() const override { + return ShadingType::Solid; + } + + public: + Color color; + float opacity = 0.0; + }; + + class GradientShading: public Shading { + public: + GradientShading( + float opacity_, + GradientType gradientType_, + std::vector const &colors_, + std::vector const &locations_, + Vector2D const &start_, + Vector2D const &end_ + ) : + opacity(opacity_), + gradientType(gradientType_), + colors(colors_), + locations(locations_), + start(start_), + end(end_) { + } + + virtual ShadingType type() const override { + return ShadingType::Gradient; + } + + public: + float opacity = 0.0; + GradientType gradientType; + std::vector colors; + std::vector locations; + Vector2D start; + Vector2D end; + }; + + struct Stroke { + std::shared_ptr shading; + float lineWidth = 0.0; + LineJoin lineJoin = LineJoin::Round; + LineCap lineCap = LineCap::Square; + float miterLimit = 4.0; + float dashPhase = 0.0; + std::vector dashPattern; + + Stroke( + std::shared_ptr shading_, + float lineWidth_, + LineJoin lineJoin_, + LineCap lineCap_, + float miterLimit_, + float dashPhase_, + std::vector dashPattern_ + ) : + shading(shading_), + lineWidth(lineWidth_), + lineJoin(lineJoin_), + lineCap(lineCap_), + miterLimit(miterLimit_), + dashPhase(dashPhase_), + dashPattern(dashPattern_) { + } + }; + + struct Fill { + std::shared_ptr shading; + FillRule rule; + + Fill( + std::shared_ptr shading_, + FillRule rule_ + ) : + shading(shading_), + rule(rule_) { + } + }; + +public: + RenderTreeNodeContentItem() { + } + +public: + bool isGroup = false; + Transform2D transform = Transform2D::identity(); + float alpha = 0.0; + std::optional trimParams; + std::shared_ptr path; + std::vector> shadings; + std::vector> subItems; + int drawContentCount = 0; + + ProcessedRenderTreeNodeData renderData; +}; + +class RenderTreeNodeContentShadingVariant { +public: + RenderTreeNodeContentShadingVariant() { + } + +public: + std::shared_ptr stroke; + std::shared_ptr fill; + std::optional> explicitPath; + + size_t subItemLimit = 0; +}; + +class RenderTreeNode { +public: + RenderTreeNode( + Vector2D size_, + Transform2D transform_, + float alpha_, + bool masksToBounds_, + bool isHidden_, + std::vector> subnodes_, + std::shared_ptr mask_, + bool invertMask_ + ) : + _size(size_), + _transform(transform_), + _alpha(alpha_), + _masksToBounds(masksToBounds_), + _isHidden(isHidden_), + _subnodes(subnodes_), + _mask(mask_), + _invertMask(invertMask_) { + for (const auto &subnode : _subnodes) { + drawContentCount += subnode->drawContentCount; + } + } + + ~RenderTreeNode() { + } + +public: + Vector2D const &size() const { + return _size; + } + + Transform2D const &transform() const { + return _transform; + } + + float alpha() const { + return _alpha; + } + + bool masksToBounds() const { + return _masksToBounds; + } + + bool isHidden() const { + return _isHidden; + } + + std::vector> const &subnodes() const { + return _subnodes; + } + + std::shared_ptr const &mask() const { + return _mask; + } + + bool invertMask() const { + return _invertMask; + } + +public: + Vector2D _size; + Transform2D _transform = Transform2D::identity(); + float _alpha = 1.0f; + bool _masksToBounds = false; + bool _isHidden = false; + std::shared_ptr _contentItem; + int drawContentCount = 0; + std::vector> _subnodes; + std::shared_ptr _mask; + bool _invertMask = false; + + ProcessedRenderTreeNodeData renderData; +}; + +} + +#endif + +#endif /* RenderTreeNode_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h new file mode 100644 index 00000000000..c3c6f9d809a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h @@ -0,0 +1,57 @@ +#ifndef ShapeAttributes_h +#define ShapeAttributes_h + +#ifdef __cplusplus + +namespace lottie { + +enum class FillRule: int { + None = 0, + NonZeroWinding = 1, + EvenOdd = 2 +}; + +enum class LineCap: int { + None = 0, + Butt = 1, + Round = 2, + Square = 3 +}; + +enum class LineJoin: int { + None = 0, + Miter = 1, + Round = 2, + Bevel = 3 +}; + +enum class GradientType: int { + None = 0, + Linear = 1, + Radial = 2 +}; + +enum class TrimType: int { + Simultaneously = 1, + Individually = 2 +}; + +struct TrimParams { + float start = 0.0; + float end = 0.0; + float offset = 0.0; + TrimType type = TrimType::Simultaneously; + + TrimParams(float start_, float end_, float offset_, TrimType type_) : + start(start_), + end(end_), + offset(offset_), + type(type_) { + } +}; + +} + +#endif + +#endif /* LottieColor_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Vectors.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Vectors.h new file mode 100644 index 00000000000..ffe55968720 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Vectors.h @@ -0,0 +1,278 @@ +#ifndef Vectors_hpp +#define Vectors_hpp + +#ifdef __cplusplus + +#include +#include + +#include + +#import + +namespace lottie { + +struct Vector1D { + enum class InternalRepresentationType { + SingleNumber, + Array + }; + + explicit Vector1D(float value_) : + value(value_) { + } + + explicit Vector1D(lottiejson11::Json const &json) noexcept(false); + lottiejson11::Json toJson() const; + + float value; + + float distanceTo(Vector1D const &to) const { + return abs(to.value - value); + } +}; + +float interpolate(float value, float to, float amount); + +Vector1D interpolate( + Vector1D const &from, + Vector1D const &to, + float amount +); + +struct __attribute__((packed)) Vector2D { + static Vector2D Zero() { + return Vector2D(0.0, 0.0); + } + + Vector2D() : + x(0.0), + y(0.0) { + } + + explicit Vector2D(float x_, float y_) : + x(x_), + y(y_) { + } + + explicit Vector2D(lottiejson11::Json const &json) noexcept(false); + lottiejson11::Json toJson() const; + + float x; + float y; + + Vector2D operator+(Vector2D const &rhs) const { + return Vector2D(x + rhs.x, y + rhs.y); + } + + Vector2D operator-(Vector2D const &rhs) const { + return Vector2D(x - rhs.x, y - rhs.y); + } + + Vector2D operator*(float scalar) const { + return Vector2D(x * scalar, y * scalar); + } + + bool operator==(Vector2D const &rhs) const { + return x == rhs.x && y == rhs.y; + } + + bool operator!=(Vector2D const &rhs) const { + return !(*this == rhs); + } + + bool isZero() const { + return x == 0.0 && y == 0.0; + } + + float distanceTo(Vector2D const &to) const { + auto deltaX = to.x - x; + auto deltaY = to.y - y; + return sqrt(deltaX * deltaX + deltaY * deltaY); + } + + bool colinear(Vector2D const &a, Vector2D const &b) const { + float area = x * (a.y - b.y) + a.x * (b.y - y) + b.x * (y - a.y); + float accuracy = 0.05; + if (area < accuracy && area > -accuracy) { + return true; + } + return false; + } + + Vector2D pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, float amount) const; + + Vector2D interpolate(Vector2D const &to, float amount) const; + + Vector2D interpolate( + Vector2D const &to, + Vector2D const &outTangent, + Vector2D const &inTangent, + float amount, + int maxIterations = 3, + int samples = 20, + float accuracy = 1.0 + ) const; +}; + +Vector2D interpolate( + Vector2D const &from, + Vector2D const &to, + float amount +); + +struct Vector3D { + explicit Vector3D(float x_, float y_, float z_) : + x(x_), + y(y_), + z(z_) { + } + + explicit Vector3D(lottiejson11::Json const &json) noexcept(false); + lottiejson11::Json toJson() const; + + float x = 0.0; + float y = 0.0; + float z = 0.0; +}; + +Vector3D interpolate( + Vector3D const &from, + Vector3D const &to, + float amount +); + +inline float degreesToRadians(float value) { + return value * M_PI / 180.0f; +} + +inline float radiansToDegrees(float value) { + return value * 180.0f / M_PI; +} + +struct Transform2D { + static Transform2D const &identity() { + return _identity; + } + + explicit Transform2D(simd_float3x3 const &rows_) : + _rows(rows_) { + } + + Transform2D operator*(Transform2D const &other) const { + return Transform2D(simd_mul(other._rows, _rows)); + } + + bool isInvertible() const { + return simd_determinant(_rows) > 0.00000001; + } + + Transform2D inverted() const { + return Transform2D(simd_inverse(_rows)); + } + + bool isIdentity() const { + return (*this) == identity(); + } + + static Transform2D makeTranslation(float tx, float ty); + static Transform2D makeScale(float sx, float sy); + static Transform2D makeRotation(float radians); + static Transform2D makeSkew(float skew, float skewAxis); + static Transform2D makeTransform( + Vector2D const &anchor, + Vector2D const &position, + Vector2D const &scale, + float rotation, + std::optional skew, + std::optional skewAxis + ); + + Transform2D rotated(float degrees) const; + Transform2D translated(Vector2D const &translation) const; + Transform2D scaled(Vector2D const &scale) const; + Transform2D skewed(float skew, float skewAxis) const; + + bool operator==(Transform2D const &rhs) const { + return simd_equal(_rows, rhs._rows); + } + + bool operator!=(Transform2D const &rhs) const { + return !((*this) == rhs); + } + + simd_float3x3 const &rows() const { + return _rows; + } +private: + static Transform2D _identity; + + simd_float3x3 _rows; +}; + +struct CGRect { + explicit CGRect(float x_, float y_, float width_, float height_) : + x(x_), y(y_), width(width_), height(height_) { + } + + float x = 0.0f; + float y = 0.0f; + float width = 0.0f; + float height = 0.0f; + + static CGRect veryLarge() { + return CGRect( + -100000000.0f, + -100000000.0f, + 200000000.0f, + 200000000.0f + ); + } + + bool operator==(CGRect const &rhs) const { + return x == rhs.x && y == rhs.y && width == rhs.width && height == rhs.height; + } + + bool operator!=(CGRect const &rhs) const { + return !(*this == rhs); + } + + bool empty() const { + return width <= 0.0 || height <= 0.0; + } + + CGRect insetBy(float dx, float dy) const { + CGRect result = *this; + + result.x += dx; + result.y += dy; + result.width -= dx * 2.0f; + result.height -= dy * 2.0f; + + return result; + } + + bool intersects(CGRect const &other) const; + bool contains(CGRect const &other) const; + + CGRect intersection(CGRect const &other) const; + CGRect unionWith(CGRect const &other) const; + + CGRect applyingTransform(Transform2D const &transform) const; +}; + +inline bool isInRangeOrEqual(float value, float from, float to) { + return from <= value && value <= to; +} + +inline bool isInRange(float value, float from, float to) { + return from < value && value < to; +} + +float cubicBezierInterpolate(float value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3); + +} + +#endif + +#endif /* Vectors_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/VectorsCocoa.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/VectorsCocoa.h new file mode 100644 index 00000000000..ef77ad99668 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/VectorsCocoa.h @@ -0,0 +1,17 @@ +#ifndef VectorsCocoa_h +#define VectorsCocoa_h + +#ifdef __cplusplus + +#import + +namespace lottie { + +::CATransform3D nativeTransform(Transform2D const &value); +Transform2D fromNativeTransform(::CATransform3D const &value); + +} + +#endif + +#endif /* VectorsCocoa_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/lottiejson11.hpp b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/lottiejson11.hpp new file mode 100644 index 00000000000..aa188f366b1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/lottiejson11.hpp @@ -0,0 +1,236 @@ +/* json11 + * + * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + * + * The core object provided by the library is json11::Json. A Json object represents any JSON + * value: null, bool, number (int or double), string (std::string), array (std::vector), or + * object (std::map). + * + * Json objects act like values: they can be assigned, copied, moved, compared for equality or + * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and + * Json::parse (static) to parse a std::string as a Json object. + * + * Internally, the various types of Json object are represented by the JsonValue class + * hierarchy. + * + * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, + * so some JSON implementations distinguish between integers and floating-point numbers, while + * some don't. In json11, we choose the latter. Because some JSON implementations (namely + * Javascript itself) treat all numbers as the same type, distinguishing the two leads + * to JSON that will be *silently* changed by a round-trip through those implementations. + * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also + * provides integer helpers. + * + * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the + * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 + * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch + * will be exact for +/- 275 years.) + */ + +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#ifdef __cplusplus + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + #if _MSC_VER <= 1800 // VS 2013 + #ifndef noexcept + #define noexcept throw() + #endif + + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #endif +#endif + +namespace lottiejson11 { + +enum JsonParse { + STANDARD, COMMENTS +}; + +class JsonValue; + +class Json final { +public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector array; + typedef std::map object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const std::string &value); // STRING + Json(std::string &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template ().begin()->first)>::value + && std::is_constructible().begin()->second)>::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template ().begin())>::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const std::string &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](size_t i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const std::string &key) const; + + // Serialize. + void dump(std::string &out) const; + std::string dump() const { + std::string out; + dump(out); + return out; + } + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(std::string(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + // Parse multiple objects, concatenated or separated by whitespace + static std::vector parse_multi( + const std::string & in, + std::string::size_type & parser_stop_pos, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + + static inline std::vector parse_multi( + const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + std::string::size_type parser_stop_pos; + return parse_multi(in, parser_stop_pos, err, strategy); + } + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + bool operator> (const Json &rhs) const { return (rhs < *this); } + bool operator>= (const Json &rhs) const { return !(*this < rhs); } + + /* has_shape(types, err) + * + * Return true if this is a JSON object and, for each item in types, has a field of + * the given type. If not, return false and set err to a descriptive message. + */ + typedef std::initializer_list> shape; + bool has_shape(const shape & types, std::string & err) const; + +private: + std::shared_ptr m_ptr; +}; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. +class JsonValue { +protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(std::string &out) const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const std::string &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](size_t i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const std::string &key) const; + virtual ~JsonValue() {} +}; + +} // namespace lottiejson11 + +#endif \ No newline at end of file diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp new file mode 100644 index 00000000000..53f850f090b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp @@ -0,0 +1,17 @@ +#include "CompositionLayer.hpp" + +namespace lottie { + +InvertedMatteLayer::InvertedMatteLayer(std::shared_ptr inputMatte) : +_inputMatte(inputMatte) { + setSize(inputMatte->size()); + + addSublayer(_inputMatte); +} + +std::shared_ptr makeInvertedMatteLayer(std::shared_ptr compositionLayer) { + auto result = std::make_shared(compositionLayer); + return result; +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp new file mode 100644 index 00000000000..e82b9c8b338 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp @@ -0,0 +1,201 @@ +#ifndef CompositionLayer_hpp +#define CompositionLayer_hpp + +#include +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp" + +#include + +namespace lottie { + +class CompositionLayer; +class InvertedMatteLayer; + +/// A layer that inverses the alpha output of its input layer. +class InvertedMatteLayer: public CALayer { +public: + InvertedMatteLayer(std::shared_ptr inputMatte); + + std::shared_ptr _inputMatte; + + virtual bool isInvertedMatte() const override { + return true; + } +}; + +std::shared_ptr makeInvertedMatteLayer(std::shared_ptr compositionLayer); + +/// The base class for a child layer of CompositionContainer +class CompositionLayer: public CALayer, public KeypathSearchable { +public: + CompositionLayer(std::shared_ptr const &layer, Vector2D size) { + _contentsLayer = std::make_shared(); + + _transformNode = std::make_shared(layer->transform); + + if (layer->masks.has_value()) { + _maskLayer = std::make_shared(layer->masks.value()); + } else { + _maskLayer = nullptr; + } + + _matteType = layer->matte; + + _inFrame = layer->inFrame; + _outFrame = layer->outFrame; + _timeStretch = layer->timeStretch(); + _startFrame = layer->startTime; + if (layer->name.has_value()) { + _keypathName = layer->name.value(); + } else { + _keypathName = "Layer"; + } + + _childKeypaths.push_back(_transformNode->transformProperties()); + + _contentsLayer->setSize(size); + + if (layer->blendMode.has_value() && layer->blendMode.value() != BlendMode::Normal) { + setCompositingFilter(layer->blendMode); + } + + addSublayer(_contentsLayer); + + if (_maskLayer) { + _contentsLayer->setMask(_maskLayer); + } + } + + virtual std::string keypathName() const override { + return _keypathName; + } + + virtual std::map> keypathProperties() const override { + return {}; + } + + virtual std::shared_ptr keypathLayer() const override { + return _contentsLayer; + } + + void displayWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) { + bool layerVisible = isInRangeOrEqual(frame, _inFrame, _outFrame); + + if (_transformNode->updateTree(frame, forceUpdates) || _contentsLayer->isHidden() != !layerVisible) { + _contentsLayer->setTransform(_transformNode->globalTransform()); + _contentsLayer->setOpacity(_transformNode->opacity()); + _contentsLayer->setIsHidden(!layerVisible); + + updateContentsLayerParameters(); + } + + /// Only update contents if current time is within the layers time bounds. + if (layerVisible) { + displayContentsWithFrame(frame, forceUpdates, boundingBoxContext); + if (_maskLayer) { + _maskLayer->updateWithFrame(frame, forceUpdates); + } + } + } + + virtual void updateContentsLayerParameters() { + } + + virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) { + /// To be overridden by subclass + } + + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + std::shared_ptr _matteLayer; + void setMatteLayer(std::shared_ptr matteLayer) { + _matteLayer = matteLayer; + if (matteLayer) { + if (_matteType.has_value() && _matteType.value() == MatteType::Invert) { + setMask(makeInvertedMatteLayer(matteLayer)); + } else { + setMask(matteLayer); + } + } else { + setMask(nullptr); + } + } + + std::shared_ptr const &contentsLayer() const { + return _contentsLayer; + } + + std::shared_ptr const &maskLayer() const { + return _maskLayer; + } + void setMaskLayer(std::shared_ptr const &maskLayer) { + _maskLayer = maskLayer; + } + + std::optional const &matteType() const { + return _matteType; + } + + float inFrame() const { + return _inFrame; + } + float outFrame() const { + return _outFrame; + } + float startFrame() const { + return _startFrame; + } + float timeStretch() const { + return _timeStretch; + } + + virtual std::shared_ptr renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) { + return nullptr; + } + +public: + std::shared_ptr const transformNode() const { + return _transformNode; + } + +protected: + std::shared_ptr _contentsLayer; + std::optional _matteType; + +private: + std::shared_ptr _transformNode; + + std::shared_ptr _maskLayer; + + float _inFrame = 0.0; + float _outFrame = 0.0; + float _startFrame = 0.0; + float _timeStretch = 0.0; + + // MARK: Keypath Searchable + + std::string _keypathName; + +public: + virtual bool isImageCompositionLayer() const { + return false; + } + + virtual bool isTextCompositionLayer() const { + return false; + } + +protected: + std::vector> _childKeypaths; +}; + +} + +#endif /* CompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.cpp new file mode 100644 index 00000000000..ad3b669b24a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "ImageCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp new file mode 100644 index 00000000000..ff6dc769fa0 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp @@ -0,0 +1,43 @@ +#ifndef ImageCompositionLayer_hpp +#define ImageCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Assets/ImageAsset.hpp" +#include "Lottie/Private/Model/Layers/ImageLayerModel.hpp" + +namespace lottie { + +class ImageCompositionLayer: public CompositionLayer { +public: + ImageCompositionLayer(std::shared_ptr const &imageLayer, Vector2D const &size) : + CompositionLayer(imageLayer, size) { + _imageReferenceID = imageLayer->referenceID; + + contentsLayer()->setMasksToBounds(true); + } + + std::shared_ptr image() { + return _image; + } + void setImage(std::shared_ptr image) { + _image = image; + //contentsLayer()->setContents(image); + } + + std::string const &imageReferenceID() { + return _imageReferenceID; + } + +public: + virtual bool isImageCompositionLayer() const override { + return true; + } + +private: + std::string _imageReferenceID; + std::shared_ptr _image; +}; + +} + +#endif /* ImageCompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.cpp new file mode 100644 index 00000000000..398d52d57c1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.cpp @@ -0,0 +1,5 @@ +#include "MaskContainerLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp new file mode 100644 index 00000000000..95bdb3f0932 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp @@ -0,0 +1,178 @@ +#ifndef MaskContainerLayer_hpp +#define MaskContainerLayer_hpp + +#include "Lottie/Private/Model/Objects/Mask.hpp" +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" + +namespace lottie { + +inline MaskMode usableMaskMode(MaskMode mode) { + switch (mode) { + case MaskMode::Add: + return MaskMode::Add; + case MaskMode::Subtract: + return MaskMode::Subtract; + case MaskMode::Intersect: + return MaskMode::Intersect; + case MaskMode::Lighten: + return MaskMode::Add; + case MaskMode::Darken: + return MaskMode::Darken; + case MaskMode::Difference: + return MaskMode::Intersect; + case MaskMode::None: + return MaskMode::None; + } +} + +class MaskNodeProperties: public NodePropertyMap { +public: + MaskNodeProperties(std::shared_ptr const &mask) : + _mode(mask->mode()), + _inverted(mask->inverted) { + _opacity = std::make_shared>(std::make_shared>(mask->opacity->keyframes)); + _shape = std::make_shared>(std::make_shared>(mask->shape.keyframes)); + _expansion = std::make_shared>(std::make_shared>(mask->expansion->keyframes)); + + _propertyMap.insert(std::make_pair("Opacity", _opacity)); + _propertyMap.insert(std::make_pair("Shape", _shape)); + _propertyMap.insert(std::make_pair("Expansion", _expansion)); + + for (const auto &it : _propertyMap) { + _properties.push_back(it.second); + } + } + + virtual std::vector> &properties() override { + return _properties; + } + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + std::shared_ptr> const &opacity() const { + return _opacity; + } + + std::shared_ptr> const &shape() const { + return _shape; + } + + std::shared_ptr> const &expansion() const { + return _expansion; + } + + MaskMode mode() const { + return _mode; + } + + bool inverted() const { + return _inverted; + } + +private: + std::map> _propertyMap; + std::vector> _childKeypaths; + + std::vector> _properties; + + MaskMode _mode = MaskMode::Add; + bool _inverted = false; + + std::shared_ptr> _opacity; + std::shared_ptr> _shape; + std::shared_ptr> _expansion; +}; + +class MaskLayer: public CALayer { +public: + MaskLayer(std::shared_ptr const &mask) : + _properties(mask) { + _maskLayer = std::make_shared(); + + addSublayer(_maskLayer); + + if (mask->mode() == MaskMode::Add) { + _maskLayer->setFillColor(Color(1.0, 0.0, 0.0, 1.0)); + } else { + _maskLayer->setFillColor(Color(0.0, 1.0, 0.0, 1.0)); + } + _maskLayer->setFillRule(FillRule::EvenOdd); + } + + virtual ~MaskLayer() = default; + + void updateWithFrame(float frame, bool forceUpdates) { + if (_properties.opacity()->needsUpdate(frame) || forceUpdates) { + _properties.opacity()->update(frame); + setOpacity(_properties.opacity()->value().value); + } + + if (_properties.shape()->needsUpdate(frame) || forceUpdates) { + _properties.shape()->update(frame); + _properties.expansion()->update(frame); + + auto path = _properties.shape()->value().cgPath(); + auto usableMode = usableMaskMode(_properties.mode()); + if ((usableMode == MaskMode::Subtract && !_properties.inverted()) || + (usableMode == MaskMode::Add && _properties.inverted())) { + /// Add a bounds rect to invert the mask + auto newPath = CGPath::makePath(); + newPath->addRect(CGRect::veryLarge()); + newPath->addPath(path); + path = std::static_pointer_cast(newPath); + } + _maskLayer->setPath(path); + } + } + +private: + MaskNodeProperties _properties; + + std::shared_ptr _maskLayer; +}; + +class MaskContainerLayer: public CALayer { +public: + MaskContainerLayer(std::vector> const &masks) { + auto containerLayer = std::make_shared(); + bool firstObject = true; + for (const auto &mask : masks) { + auto maskLayer = std::make_shared(mask); + _maskLayers.push_back(maskLayer); + + auto usableMode = usableMaskMode(mask->mode()); + if (usableMode == MaskMode::None) { + continue; + } else if (usableMode == MaskMode::Add || firstObject) { + firstObject = false; + containerLayer->addSublayer(maskLayer); + } else { + containerLayer->setMask(maskLayer); + auto newContainer = std::make_shared(); + newContainer->addSublayer(containerLayer); + containerLayer = newContainer; + } + } + addSublayer(containerLayer); + } + + // MARK: Internal + + void updateWithFrame(float frame, bool forceUpdates) { + for (const auto &maskLayer : _maskLayers) { + maskLayer->updateWithFrame(frame, forceUpdates); + } + } + +private: + std::vector> _maskLayers; +}; + +} + +#endif /* MaskContainerLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.cpp new file mode 100644 index 00000000000..6c5345cfd61 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "NullCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp new file mode 100644 index 00000000000..c3d3dea5cc0 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp @@ -0,0 +1,17 @@ +#ifndef NullCompositionLayer_hpp +#define NullCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" + +namespace lottie { + +class NullCompositionLayer: public CompositionLayer { +public: + NullCompositionLayer(std::shared_ptr const &layer) : + CompositionLayer(layer, Vector2D::Zero()) { + } +}; + +} + +#endif /* NullCompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.cpp new file mode 100644 index 00000000000..d3428a9b1b0 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "PreCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp new file mode 100644 index 00000000000..c170f21a582 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp @@ -0,0 +1,202 @@ +#ifndef PreCompositionLayer_hpp +#define PreCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Layers/PreCompLayerModel.hpp" +#include "Lottie/Private/Model/Assets/PrecompAsset.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp" +#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp" +#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp" +#include "Lottie/Private/Model/Assets/AssetLibrary.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp" + +namespace lottie { + +class PreCompositionLayer: public CompositionLayer { +public: + PreCompositionLayer( + std::shared_ptr const &precomp, + PrecompAsset const &asset, + std::shared_ptr const &layerImageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider, + std::shared_ptr const &assetLibrary, + float frameRate + ) : CompositionLayer(precomp, Vector2D(precomp->width, precomp->height)) { + if (precomp->timeRemapping) { + _remappingNode = std::make_shared>(std::make_shared>(precomp->timeRemapping->keyframes)); + } + _frameRate = frameRate; + + setSize(Vector2D(precomp->width, precomp->height)); + contentsLayer()->setMasksToBounds(true); + contentsLayer()->setSize(size()); + + auto layers = initializeCompositionLayers( + asset.layers, + assetLibrary, + layerImageProvider, + textProvider, + fontProvider, + frameRate + ); + + std::vector> imageLayers; + + std::shared_ptr mattedLayer; + + for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) { + std::shared_ptr layer = *layerIt; + layer->setSize(size()); + _animationLayers.push_back(layer); + + if (layer->isImageCompositionLayer()) { + imageLayers.push_back(std::static_pointer_cast(layer)); + } + if (mattedLayer) { + /// The previous layer requires this layer to be its matte + mattedLayer->setMatteLayer(layer); + mattedLayer = nullptr; + continue; + } + if (layer->matteType().has_value() && (layer->matteType().value() == MatteType::Add || layer->matteType().value() == MatteType::Invert)) { + /// We have a layer that requires a matte. + mattedLayer = layer; + } + contentsLayer()->addSublayer(layer); + } + + for (const auto &layer : layers) { + _childKeypaths.push_back(layer); + } + + layerImageProvider->addImageLayers(imageLayers); + } + + virtual std::map> keypathProperties() const override { + if (!_remappingNode) { + return {}; + } + + std::map> result; + result.insert(std::make_pair("Time Remap", _remappingNode)); + + return result; + } + + virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override { + float localFrame = 0.0; + if (_remappingNode) { + _remappingNode->update(frame); + localFrame = _remappingNode->value().value * _frameRate; + } else { + localFrame = (frame - startFrame()) / timeStretch(); + } + + for (const auto &animationLayer : _animationLayers) { + animationLayer->displayWithFrame(localFrame, forceUpdates, boundingBoxContext); + } + } + + virtual std::shared_ptr renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) override { + if (!_renderTreeNode) { + std::vector> renderTreeSubnodes; + for (const auto &animationLayer : _animationLayers) { + bool found = false; + for (const auto &sublayer : contentsLayer()->sublayers()) { + if (animationLayer == sublayer) { + found = true; + break; + } + } + if (found) { + auto node = animationLayer->renderTreeNode(boundingBoxContext); + if (node) { + renderTreeSubnodes.push_back(node); + } + } + } + + std::vector> renderTreeValue; + auto renderTreeContentItem = std::make_shared( + Vector2D(0.0, 0.0), + Transform2D::identity(), + 1.0, + false, + false, + renderTreeSubnodes, + nullptr, + false + ); + if (renderTreeContentItem) { + renderTreeValue.push_back(renderTreeContentItem); + } + + _contentsTreeNode = std::make_shared( + Vector2D(0.0, 0.0), + Transform2D::identity(), + 1.0, + false, + false, + renderTreeValue, + nullptr, + false + ); + + std::vector> subnodes; + subnodes.push_back(_contentsTreeNode); + + std::shared_ptr maskNode; + bool invertMask = false; + if (_matteLayer) { + maskNode = _matteLayer->renderTreeNode(boundingBoxContext); + if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) { + invertMask = true; + } + } + + _renderTreeNode = std::make_shared( + Vector2D(0.0, 0.0), + Transform2D::identity(), + 1.0, + false, + false, + subnodes, + maskNode, + invertMask + ); + } + + _contentsTreeNode->_size = _contentsLayer->size(); + _contentsTreeNode->_masksToBounds = _contentsLayer->masksToBounds(); + + _renderTreeNode->_size = size(); + _renderTreeNode->_transform = transform(); + _renderTreeNode->_alpha = opacity(); + _renderTreeNode->_masksToBounds = masksToBounds(); + _renderTreeNode->_isHidden = isHidden(); + + return _renderTreeNode; + } + + virtual void updateContentsLayerParameters() override { + _contentsTreeNode->_transform = _contentsLayer->transform(); + _contentsTreeNode->_alpha = _contentsLayer->opacity(); + _contentsTreeNode->_isHidden = _contentsLayer->isHidden(); + } + +private: + float _frameRate = 0.0; + std::shared_ptr> _remappingNode; + + std::vector> _animationLayers; + + std::shared_ptr _renderTreeNode; + std::shared_ptr _contentsTreeNode; +}; + +} + +#endif /* PreCompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp new file mode 100644 index 00000000000..e7ad955e0f2 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp @@ -0,0 +1,1378 @@ +#include "ShapeCompositionLayer.hpp" + +#include "Lottie/Private/Model/ShapeItems/Group.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/ShapeItems/Rectangle.hpp" +#include "Lottie/Private/Model/ShapeItems/Star.hpp" +#include "Lottie/Private/Model/ShapeItems/Shape.hpp" +#include "Lottie/Private/Model/ShapeItems/Trim.hpp" +#include "Lottie/Private/Model/ShapeItems/Stroke.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientStroke.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp" +#include "Lottie/Private/Model/ShapeItems/ShapeTransform.hpp" + +namespace lottie { + +class ShapeLayerPresentationTree { +public: + class FillOutput { + public: + FillOutput() { + } + ~FillOutput() = default; + + virtual void update(AnimationFrameTime frameTime) = 0; + virtual std::shared_ptr fill() = 0; + }; + + class SolidFillOutput : public FillOutput { + public: + explicit SolidFillOutput(Fill const &fill) : + rule(fill.fillRule.value_or(FillRule::NonZeroWinding)), + color(fill.color.keyframes), + opacity(fill.opacity.keyframes) { + auto solid = std::make_shared(Color(0.0, 0.0, 0.0, 0.0), 0.0); + _fill = std::make_shared( + solid, + rule + ); + } + + virtual ~SolidFillOutput() = default; + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (color.hasUpdate(frameTime)) { + hasUpdates = true; + colorValue = color.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (hasUpdates) { + RenderTreeNodeContentItem::SolidShading *solid = (RenderTreeNodeContentItem::SolidShading *)_fill->shading.get(); + solid->color = colorValue; + solid->opacity = opacityValue * 0.01; + } + } + + virtual std::shared_ptr fill() override { + return _fill; + } + + private: + FillRule rule; + + KeyframeInterpolator color; + Color colorValue = Color(0.0, 0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + float opacityValue = 0.0; + + std::shared_ptr _fill; + }; + + class GradientFillOutput : public FillOutput { + public: + explicit GradientFillOutput(GradientFill const &gradientFill) : + rule(FillRule::NonZeroWinding), + numberOfColors(gradientFill.numberOfColors), + gradientType(gradientFill.gradientType), + colors(gradientFill.colors.keyframes), + startPoint(gradientFill.startPoint.keyframes), + endPoint(gradientFill.endPoint.keyframes), + opacity(gradientFill.opacity.keyframes) { + auto gradient = std::make_shared( + 0.0, + gradientType, + std::vector(), + std::vector(), + Vector2D(0.0, 0.0), + Vector2D(0.0, 0.0) + ); + _fill = std::make_shared( + gradient, + rule + ); + } + + virtual ~GradientFillOutput() = default; + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (colors.hasUpdate(frameTime)) { + hasUpdates = true; + colorsValue = colors.value(frameTime); + } + + if (startPoint.hasUpdate(frameTime)) { + hasUpdates = true; + startPointValue = startPoint.value(frameTime); + } + + if (endPoint.hasUpdate(frameTime)) { + hasUpdates = true; + endPointValue = endPoint.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (hasUpdates) { + std::vector colors; + std::vector locations; + getGradientParameters(numberOfColors, colorsValue, colors, locations); + + RenderTreeNodeContentItem::GradientShading *gradient = ((RenderTreeNodeContentItem::GradientShading *)_fill->shading.get()); + gradient->opacity = opacityValue * 0.01; + gradient->colors = colors; + gradient->locations = locations; + gradient->start = Vector2D(startPointValue.x, startPointValue.y); + gradient->end = Vector2D(endPointValue.x, endPointValue.y); + } + } + + virtual std::shared_ptr fill() override { + return _fill; + } + + private: + FillRule rule; + int numberOfColors = 0; + GradientType gradientType; + + KeyframeInterpolator colors; + GradientColorSet colorsValue; + + KeyframeInterpolator startPoint; + Vector3D startPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator endPoint; + Vector3D endPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + float opacityValue = 0.0; + + std::shared_ptr _fill; + }; + + class StrokeOutput { + public: + StrokeOutput() { + } + ~StrokeOutput() = default; + + virtual void update(AnimationFrameTime frameTime) = 0; + virtual std::shared_ptr stroke() = 0; + }; + + class SolidStrokeOutput : public StrokeOutput { + public: + SolidStrokeOutput(Stroke const &stroke) : + lineJoin(stroke.lineJoin), + lineCap(stroke.lineCap), + miterLimit(stroke.miterLimit.value_or(4.0)), + color(stroke.color.keyframes), + opacity(stroke.opacity.keyframes), + width(stroke.width.keyframes) { + if (stroke.dashPattern.has_value()) { + StrokeShapeDashConfiguration dashConfiguration(stroke.dashPattern.value()); + dashPattern = std::make_unique(dashConfiguration.dashPatterns); + + if (!dashConfiguration.dashPhase.empty()) { + dashPhase = std::make_unique>(dashConfiguration.dashPhase); + } + } + + auto solid = std::make_shared(Color(0.0, 0.0, 0.0, 0.0), 0.0); + _stroke = std::make_shared( + solid, + 0.0, + lineJoin, + lineCap, + miterLimit, + 0.0, + std::vector() + ); + } + + virtual ~SolidStrokeOutput() = default; + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (color.hasUpdate(frameTime)) { + hasUpdates = true; + colorValue = color.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (width.hasUpdate(frameTime)) { + hasUpdates = true; + widthValue = width.value(frameTime).value; + } + + if (dashPattern) { + if (dashPattern->hasUpdate(frameTime)) { + hasUpdates = true; + dashPatternValue = dashPattern->value(frameTime); + } + } + + if (dashPhase) { + if (dashPhase->hasUpdate(frameTime)) { + hasUpdates = true; + dashPhaseValue = dashPhase->value(frameTime).value; + } + } + + if (hasUpdates) { + bool hasNonZeroDashes = false; + if (!dashPatternValue.values.empty()) { + for (const auto &value : dashPatternValue.values) { + if (value != 0) { + hasNonZeroDashes = true; + break; + } + } + } + + RenderTreeNodeContentItem::SolidShading *solid = (RenderTreeNodeContentItem::SolidShading *)_stroke->shading.get(); + solid->color = colorValue; + solid->opacity = opacityValue * 0.01; + + _stroke->lineWidth = widthValue; + _stroke->dashPhase = hasNonZeroDashes ? dashPhaseValue : 0.0; + _stroke->dashPattern = hasNonZeroDashes ? dashPatternValue.values : std::vector(); + } + } + + virtual std::shared_ptr stroke() override { + return _stroke; + } + + private: + LineJoin lineJoin; + LineCap lineCap; + float miterLimit = 4.0; + + KeyframeInterpolator color; + Color colorValue = Color(0.0, 0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + float opacityValue = 0.0; + + KeyframeInterpolator width; + float widthValue = 0.0; + + std::unique_ptr dashPattern; + DashPattern dashPatternValue = DashPattern({}); + + std::unique_ptr> dashPhase; + float dashPhaseValue = 0.0; + + std::shared_ptr _stroke; + }; + + class GradientStrokeOutput : public StrokeOutput { + public: + GradientStrokeOutput(GradientStroke const &gradientStroke) : + lineJoin(gradientStroke.lineJoin), + lineCap(gradientStroke.lineCap), + miterLimit(gradientStroke.miterLimit.value_or(4.0)), + numberOfColors(gradientStroke.numberOfColors), + gradientType(gradientStroke.gradientType), + colors(gradientStroke.colors.keyframes), + startPoint(gradientStroke.startPoint.keyframes), + endPoint(gradientStroke.endPoint.keyframes), + opacity(gradientStroke.opacity.keyframes), + width(gradientStroke.width.keyframes) { + if (gradientStroke.dashPattern.has_value()) { + StrokeShapeDashConfiguration dashConfiguration(gradientStroke.dashPattern.value()); + dashPattern = std::make_unique(dashConfiguration.dashPatterns); + + if (!dashConfiguration.dashPhase.empty()) { + dashPhase = std::make_unique>(dashConfiguration.dashPhase); + } + } + + auto gradient = std::make_shared( + 0.0, + gradientType, + std::vector(), + std::vector(), + Vector2D(0.0, 0.0), + Vector2D(0.0, 0.0) + ); + _stroke = std::make_shared( + gradient, + 0.0, + lineJoin, + lineCap, + miterLimit, + 0.0, + std::vector() + ); + } + + virtual ~GradientStrokeOutput() = default; + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (colors.hasUpdate(frameTime)) { + hasUpdates = true; + colorsValue = colors.value(frameTime); + } + + if (startPoint.hasUpdate(frameTime)) { + hasUpdates = true; + startPointValue = startPoint.value(frameTime); + } + + if (endPoint.hasUpdate(frameTime)) { + hasUpdates = true; + endPointValue = endPoint.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (width.hasUpdate(frameTime)) { + hasUpdates = true; + widthValue = width.value(frameTime).value; + } + + if (dashPattern) { + if (dashPattern->hasUpdate(frameTime)) { + hasUpdates = true; + dashPatternValue = dashPattern->value(frameTime); + } + } + + if (dashPhase) { + if (dashPhase->hasUpdate(frameTime)) { + hasUpdates = true; + dashPhaseValue = dashPhase->value(frameTime).value; + } + } + + if (hasUpdates) { + bool hasNonZeroDashes = false; + if (!dashPatternValue.values.empty()) { + for (const auto &value : dashPatternValue.values) { + if (value != 0) { + hasNonZeroDashes = true; + break; + } + } + } + + std::vector colors; + std::vector locations; + getGradientParameters(numberOfColors, colorsValue, colors, locations); + + RenderTreeNodeContentItem::GradientShading *gradient = ((RenderTreeNodeContentItem::GradientShading *)_stroke->shading.get()); + gradient->opacity = opacityValue * 0.01; + gradient->colors = colors; + gradient->locations = locations; + gradient->start = Vector2D(startPointValue.x, startPointValue.y); + gradient->end = Vector2D(endPointValue.x, endPointValue.y); + + _stroke->lineWidth = widthValue; + _stroke->dashPhase = hasNonZeroDashes ? dashPhaseValue : 0.0; + _stroke->dashPattern = hasNonZeroDashes ? dashPatternValue.values : std::vector(); + } + } + + virtual std::shared_ptr stroke() override { + return _stroke; + } + + private: + LineJoin lineJoin; + LineCap lineCap; + float miterLimit = 4.0; + + int numberOfColors = 0; + GradientType gradientType; + + KeyframeInterpolator colors; + GradientColorSet colorsValue; + + KeyframeInterpolator startPoint; + Vector3D startPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator endPoint; + Vector3D endPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + float opacityValue = 0.0; + + KeyframeInterpolator width; + float widthValue = 0.0; + + std::unique_ptr dashPattern; + DashPattern dashPatternValue = DashPattern({}); + + std::unique_ptr> dashPhase; + float dashPhaseValue = 0.0; + + std::shared_ptr _stroke; + }; + + class TrimParamsOutput { + public: + TrimParamsOutput(Trim const &trim) : + type(trim.trimType), + start(trim.start.keyframes), + end(trim.end.keyframes), + offset(trim.offset.keyframes) { + } + + void update(AnimationFrameTime frameTime) { + if (start.hasUpdate(frameTime)) { + startValue = start.value(frameTime).value; + } + + if (end.hasUpdate(frameTime)) { + endValue = end.value(frameTime).value; + } + + if (offset.hasUpdate(frameTime)) { + offsetValue = offset.value(frameTime).value; + } + } + + TrimParams trimParams() { + float resolvedStartValue = startValue * 0.01; + float resolvedEndValue = endValue * 0.01; + float resolvedStart = std::min(resolvedStartValue, resolvedEndValue); + float resolvedEnd = std::max(resolvedStartValue, resolvedEndValue); + + float resolvedOffset = fmod(offsetValue, 360.0) / 360.0; + + return TrimParams(resolvedStart, resolvedEnd, resolvedOffset, type); + } + + private: + TrimType type; + + KeyframeInterpolator start; + float startValue = 0.0; + + KeyframeInterpolator end; + float endValue = 0.0; + + KeyframeInterpolator offset; + float offsetValue = 0.0; + }; + + struct ShadingVariant { + std::shared_ptr fill; + std::shared_ptr stroke; + size_t subItemLimit = 0; + }; + + struct TransformedPath { + BezierPath path; + Transform2D transform; + + TransformedPath(BezierPath const &path_, Transform2D const &transform_) : + path(path_), + transform(transform_) { + } + }; + + class PathOutput { + public: + PathOutput() { + } + virtual ~PathOutput() = default; + + virtual void update(AnimationFrameTime frameTime) = 0; + virtual std::shared_ptr ¤tPath() = 0; + }; + + class StaticPathOutput : public PathOutput { + public: + explicit StaticPathOutput(BezierPath const &path) : + resolvedPath(std::make_shared(path)) { + } + + virtual void update(AnimationFrameTime frameTime) override { + } + + virtual std::shared_ptr ¤tPath() override { + return resolvedPath; + } + + private: + std::shared_ptr resolvedPath; + }; + + class ShapePathOutput : public PathOutput { + public: + explicit ShapePathOutput(Shape const &shape) : + path(shape.path.keyframes), + resolvedPath(std::make_shared(BezierPath())) { + } + + virtual void update(AnimationFrameTime frameTime) override { + if (!hasValidData || path.hasUpdate(frameTime)) { + path.update(frameTime, resolvedPath->path); + resolvedPath->needsBoundsRecalculation = true; + } + hasValidData = true; + } + + virtual std::shared_ptr ¤tPath() override { + return resolvedPath; + } + + private: + bool hasValidData = false; + + BezierPathKeyframeInterpolator path; + + std::shared_ptr resolvedPath; + }; + + class RectanglePathOutput : public PathOutput { + public: + explicit RectanglePathOutput(Rectangle const &rectangle) : + direction(rectangle.direction.value_or(PathDirection::Clockwise)), + position(rectangle.position.keyframes), + size(rectangle.size.keyframes), + cornerRadius(rectangle.cornerRadius.keyframes), + resolvedPath(std::make_shared(BezierPath())) { + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (!hasValidData || position.hasUpdate(frameTime)) { + hasUpdates = true; + positionValue = position.value(frameTime); + } + if (!hasValidData || size.hasUpdate(frameTime)) { + hasUpdates = true; + sizeValue = size.value(frameTime); + } + if (!hasValidData || cornerRadius.hasUpdate(frameTime)) { + hasUpdates = true; + cornerRadiusValue = cornerRadius.value(frameTime).value; + } + + if (hasUpdates) { + ValueInterpolator::setInplace(makeRectangleBezierPath(Vector2D(positionValue.x, positionValue.y), Vector2D(sizeValue.x, sizeValue.y), cornerRadiusValue, direction), resolvedPath->path); + resolvedPath->needsBoundsRecalculation = true; + } + + hasValidData = true; + } + + virtual std::shared_ptr ¤tPath() override { + return resolvedPath; + } + + private: + bool hasValidData = false; + + PathDirection direction; + + KeyframeInterpolator position; + Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator size; + Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator cornerRadius; + float cornerRadiusValue = 0.0; + + std::shared_ptr resolvedPath; + }; + + class EllipsePathOutput : public PathOutput { + public: + explicit EllipsePathOutput(Ellipse const &ellipse) : + direction(ellipse.direction.value_or(PathDirection::Clockwise)), + position(ellipse.position.keyframes), + size(ellipse.size.keyframes), + resolvedPath(std::make_shared(BezierPath())) { + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (!hasValidData || position.hasUpdate(frameTime)) { + hasUpdates = true; + positionValue = position.value(frameTime); + } + if (!hasValidData || size.hasUpdate(frameTime)) { + hasUpdates = true; + sizeValue = size.value(frameTime); + } + + if (hasUpdates) { + ValueInterpolator::setInplace(makeEllipseBezierPath(Vector2D(sizeValue.x, sizeValue.y), Vector2D(positionValue.x, positionValue.y), direction), resolvedPath->path); + resolvedPath->needsBoundsRecalculation = true; + } + + hasValidData = true; + } + + virtual std::shared_ptr ¤tPath() override { + return resolvedPath; + } + + private: + bool hasValidData = false; + + PathDirection direction; + + KeyframeInterpolator position; + Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator size; + Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0); + + std::shared_ptr resolvedPath; + }; + + class StarPathOutput : public PathOutput { + public: + explicit StarPathOutput(Star const &star) : + direction(star.direction.value_or(PathDirection::Clockwise)), + position(star.position.keyframes), + outerRadius(star.outerRadius.keyframes), + outerRoundedness(star.outerRoundness.keyframes), + rotation(star.rotation.keyframes), + points(star.points.keyframes), + resolvedPath(std::make_shared(BezierPath())) { + if (star.innerRadius.has_value()) { + innerRadius = std::make_unique>(std::make_shared>(star.innerRadius->keyframes)); + } else { + innerRadius = std::make_unique>(std::make_shared>(Vector1D(0.0))); + } + + if (star.innerRoundness.has_value()) { + innerRoundedness = std::make_unique>(std::make_shared>(star.innerRoundness->keyframes)); + } else { + innerRoundedness = std::make_unique>(std::make_shared>(Vector1D(0.0))); + } + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (!hasValidData || position.hasUpdate(frameTime)) { + hasUpdates = true; + positionValue = position.value(frameTime); + } + + if (!hasValidData || outerRadius.hasUpdate(frameTime)) { + hasUpdates = true; + outerRadiusValue = outerRadius.value(frameTime).value; + } + + innerRadius->update(frameTime); + if (!hasValidData || innerRadiusValue != innerRadius->value().value) { + hasUpdates = true; + innerRadiusValue = innerRadius->value().value; + } + + if (!hasValidData || outerRoundedness.hasUpdate(frameTime)) { + hasUpdates = true; + outerRoundednessValue = outerRoundedness.value(frameTime).value; + } + + innerRoundedness->update(frameTime); + if (!hasValidData || innerRoundednessValue != innerRoundedness->value().value) { + hasUpdates = true; + innerRoundednessValue = innerRoundedness->value().value; + } + + if (!hasValidData || points.hasUpdate(frameTime)) { + hasUpdates = true; + pointsValue = points.value(frameTime).value; + } + + if (!hasValidData || rotation.hasUpdate(frameTime)) { + hasUpdates = true; + rotationValue = rotation.value(frameTime).value; + } + + if (hasUpdates) { + ValueInterpolator::setInplace(makeStarBezierPath(Vector2D(positionValue.x, positionValue.y), outerRadiusValue, innerRadiusValue, outerRoundednessValue, innerRoundednessValue, pointsValue, rotationValue, direction), resolvedPath->path); + resolvedPath->needsBoundsRecalculation = true; + } + + hasValidData = true; + } + + virtual std::shared_ptr ¤tPath() override { + return resolvedPath; + } + + private: + bool hasValidData = false; + + PathDirection direction; + + KeyframeInterpolator position; + Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator outerRadius; + float outerRadiusValue = 0.0; + + KeyframeInterpolator outerRoundedness; + float outerRoundednessValue = 0.0; + + std::unique_ptr> innerRadius; + float innerRadiusValue = 0.0; + + std::unique_ptr> innerRoundedness; + float innerRoundednessValue = 0.0; + + KeyframeInterpolator rotation; + float rotationValue = 0.0; + + KeyframeInterpolator points; + float pointsValue = 0.0; + + std::shared_ptr resolvedPath; + }; + + class TransformOutput { + public: + TransformOutput(std::shared_ptr shapeTransform) { + if (shapeTransform->anchor) { + _anchor = std::make_unique>(shapeTransform->anchor->keyframes); + } + if (shapeTransform->position) { + _position = std::make_unique>(shapeTransform->position->keyframes); + } + if (shapeTransform->scale) { + _scale = std::make_unique>(shapeTransform->scale->keyframes); + } + if (shapeTransform->rotation) { + _rotation = std::make_unique>(shapeTransform->rotation->keyframes); + } + if (shapeTransform->skew) { + _skew = std::make_unique>(shapeTransform->skew->keyframes); + } + if (shapeTransform->skewAxis) { + _skewAxis = std::make_unique>(shapeTransform->skewAxis->keyframes); + } + if (shapeTransform->opacity) { + _opacity = std::make_unique>(shapeTransform->opacity->keyframes); + } + } + + void update(AnimationFrameTime frameTime) { + bool hasUpdates = false; + + if (!hasValidData) { + hasUpdates = true; + } + if (_anchor && _anchor->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_position && _position->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_scale && _scale->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_rotation && _rotation->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_skew && _skew->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_skewAxis && _skewAxis->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_opacity && _opacity->hasUpdate(frameTime)) { + hasUpdates = true; + } + + if (hasUpdates) { + //TODO:optimize by storing components + + Vector3D anchorValue(0.0, 0.0, 0.0); + if (_anchor) { + anchorValue = _anchor->value(frameTime); + } + + Vector3D positionValue(0.0, 0.0, 0.0); + if (_position) { + positionValue = _position->value(frameTime); + } + + Vector3D scaleValue(100.0, 100.0, 100.0); + if (_scale) { + scaleValue = _scale->value(frameTime); + } + + float rotationValue = 0.0; + if (_rotation) { + rotationValue = _rotation->value(frameTime).value; + } + + float skewValue = 0.0; + if (_skew) { + skewValue = _skew->value(frameTime).value; + } + + float skewAxisValue = 0.0; + if (_skewAxis) { + skewAxisValue = _skewAxis->value(frameTime).value; + } + + if (_opacity) { + _opacityValue = _opacity->value(frameTime).value * 0.01; + } else { + _opacityValue = 1.0; + } + + _transformValue = Transform2D::identity().translated(Vector2D(positionValue.x, positionValue.y)).rotated(rotationValue).skewed(-skewValue, skewAxisValue).scaled(Vector2D(scaleValue.x * 0.01, scaleValue.y * 0.01)).translated(Vector2D(-anchorValue.x, -anchorValue.y)); + + hasValidData = true; + } + } + + Transform2D const &transform() { + return _transformValue; + } + + float opacity() { + return _opacityValue; + } + + private: + bool hasValidData = false; + + std::unique_ptr> _anchor; + std::unique_ptr> _position; + std::unique_ptr> _scale; + std::unique_ptr> _rotation; + std::unique_ptr> _skew; + std::unique_ptr> _skewAxis; + std::unique_ptr> _opacity; + + Transform2D _transformValue = Transform2D::identity(); + float _opacityValue = 1.0; + }; + + class ContentItem { + public: + ContentItem() { + } + + public: + bool isGroup = false; + + void setPath(std::unique_ptr &&path_) { + path = std::move(path_); + } + + void setTransform(std::unique_ptr &&transform_) { + transform = std::move(transform_); + } + + private: + std::unique_ptr path; + std::unique_ptr transform; + + std::vector shadings; + std::vector> trims; + + public: + std::vector> subItems; + std::shared_ptr _contentItem; + + private: + std::vector collectPaths(size_t subItemLimit, Transform2D const &parentTransform, bool skipApplyTransform) { + std::vector mappedPaths; + + //TODO:remove skipApplyTransform + Transform2D effectiveTransform = parentTransform; + if (!skipApplyTransform && isGroup && transform) { + effectiveTransform = transform->transform() * effectiveTransform; + } + + size_t maxSubitem = std::min(subItems.size(), subItemLimit); + + if (_contentItem->path) { + mappedPaths.emplace_back(_contentItem->path->path, effectiveTransform); + } + + for (size_t i = 0; i < maxSubitem; i++) { + auto &subItem = subItems[i]; + + std::optional currentTrim; + if (!trims.empty()) { + currentTrim = trims[0]->trimParams(); + } + + auto subItemPaths = subItem->collectPaths(INT32_MAX, effectiveTransform, false); + + if (currentTrim) { + CompoundBezierPath tempPath; + for (auto &path : subItemPaths) { + tempPath.appendPath(path.path.copyUsingTransform(path.transform)); + } + CompoundBezierPath trimmedPath = trimCompoundPath(tempPath, currentTrim->start, currentTrim->end, currentTrim->offset, currentTrim->type); + for (auto &path : trimmedPath.paths) { + mappedPaths.emplace_back(path, Transform2D::identity()); + } + } else { + for (auto &path : subItemPaths) { + mappedPaths.emplace_back(path.path, path.transform); + } + } + } + + return mappedPaths; + } + + public: + void addSubItem(std::shared_ptr const &subItem) { + subItems.push_back(subItem); + } + + void addFill(std::shared_ptr fill) { + ShadingVariant shading; + shading.subItemLimit = subItems.size(); + shading.fill = fill; + shadings.insert(shadings.begin(), shading); + } + + void addStroke(std::shared_ptr stroke) { + ShadingVariant shading; + shading.subItemLimit = subItems.size(); + shading.stroke = stroke; + shadings.insert(shadings.begin(), shading); + } + + void addTrim(Trim const &trim) { + trims.push_back(std::make_shared(trim)); + } + + public: + void initializeRenderChildren() { + _contentItem = std::make_shared(); + _contentItem->isGroup = isGroup; + + if (path) { + _contentItem->path = path->currentPath(); + } + + if (!shadings.empty()) { + for (int i = 0; i < shadings.size(); i++) { + auto &shadingVariant = shadings[i]; + + if (!(shadingVariant.fill || shadingVariant.stroke)) { + continue; + } + + _contentItem->drawContentCount++; + + auto itemShadingVariant = std::make_shared(); + if (shadingVariant.fill) { + itemShadingVariant->fill = shadingVariant.fill->fill(); + } + if (shadingVariant.stroke) { + itemShadingVariant->stroke = shadingVariant.stroke->stroke(); + } + itemShadingVariant->subItemLimit = shadingVariant.subItemLimit; + + _contentItem->shadings.push_back(itemShadingVariant); + } + } + + if (isGroup && !subItems.empty()) { + std::vector> subItemNodes; + for (const auto &subItem : subItems) { + subItem->initializeRenderChildren(); + _contentItem->drawContentCount += subItem->_contentItem->drawContentCount; + _contentItem->subItems.push_back(subItem->_contentItem); + } + } + } + + public: + void updateFrame(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) { + if (transform) { + transform->update(frameTime); + } + + if (path) { + path->update(frameTime); + } + for (const auto &trim : trims) { + trim->update(frameTime); + } + + for (const auto &shadingVariant : shadings) { + if (shadingVariant.fill) { + shadingVariant.fill->update(frameTime); + } + if (shadingVariant.stroke) { + shadingVariant.stroke->update(frameTime); + } + } + + for (const auto &subItem : subItems) { + subItem->updateFrame(frameTime, boundingBoxContext); + } + } + + bool hasTrims() { + if (!trims.empty()) { + return true; + } + + for (const auto &subItem : subItems) { + if (subItem->hasTrims()) { + return true; + } + } + + return false; + } + + void updateContents(std::optional parentTrim) { + Transform2D containerTransform = Transform2D::identity(); + float containerOpacity = 1.0; + if (transform) { + containerTransform = transform->transform(); + containerOpacity = transform->opacity(); + } + _contentItem->transform = containerTransform; + _contentItem->alpha = containerOpacity; + + if (!trims.empty()) { + _contentItem->trimParams = trims[0]->trimParams(); + } + + for (int i = 0; i < shadings.size(); i++) { + const auto &shadingVariant = shadings[i]; + + if (!(shadingVariant.fill || shadingVariant.stroke)) { + continue; + } + + //std::optional currentTrim = parentTrim; + //TODO:investigate + /*if (!trims.empty()) { + currentTrim = trims[0]; + }*/ + + if (parentTrim) { + CompoundBezierPath compoundPath; + auto paths = collectPaths(shadingVariant.subItemLimit, Transform2D::identity(), true); + for (const auto &path : paths) { + compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); + } + + compoundPath = trimCompoundPath(compoundPath, parentTrim->start, parentTrim->end, parentTrim->offset, parentTrim->type); + + std::vector resultPaths; + for (const auto &path : compoundPath.paths) { + resultPaths.push_back(path); + } + _contentItem->shadings[i]->explicitPath = resultPaths; + } else { + if (hasTrims()) { + CompoundBezierPath compoundPath; + auto paths = collectPaths(shadingVariant.subItemLimit, Transform2D::identity(), true); + for (const auto &path : paths) { + compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); + } + std::vector resultPaths; + for (const auto &path : compoundPath.paths) { + resultPaths.push_back(path); + } + + _contentItem->shadings[i]->explicitPath = resultPaths; + } else { + _contentItem->shadings[i]->explicitPath = std::nullopt; + } + } + } + + if (isGroup && !subItems.empty()) { + for (int i = (int)subItems.size() - 1; i >= 0; i--) { + std::optional childTrim = parentTrim; + for (const auto &trim : trims) { + //TODO:allow combination + //assert(!parentTrim); + childTrim = trim->trimParams(); + } + + subItems[i]->updateContents(childTrim); + } + } + } + }; + +public: + ShapeLayerPresentationTree(std::vector> const &items) { + itemTree = std::make_shared(); + itemTree->isGroup = true; + ShapeLayerPresentationTree::renderTreeContent(items, itemTree); + } + + ShapeLayerPresentationTree(std::shared_ptr const &solidLayer) { + itemTree = std::make_shared(); + itemTree->isGroup = true; + + std::vector> items; + items.push_back(std::make_shared( + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + solidLayer->hidden, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + KeyframeGroup(Vector3D(0.0, 0.0, 0.0)), + KeyframeGroup(Vector3D(solidLayer->width, solidLayer->height, 0.0)), + KeyframeGroup(Vector1D(0.0)) + )); + ShapeLayerPresentationTree::renderTreeContent(items, itemTree); + } + + virtual ~ShapeLayerPresentationTree() = default; + +private: + static void renderTreeContent(std::vector> const &items, std::shared_ptr &itemTree) { + for (const auto &item : items) { + if (item->hidden()) { + continue; + } + + switch (item->type) { + case ShapeType::Fill: { + Fill const &fill = *((Fill *)item.get()); + + itemTree->addFill(std::make_shared(fill)); + + break; + } + case ShapeType::GradientFill: { + GradientFill const &gradientFill = *((GradientFill *)item.get()); + + itemTree->addFill(std::make_shared(gradientFill)); + + break; + } + case ShapeType::Stroke: { + Stroke const &stroke = *((Stroke *)item.get()); + + itemTree->addStroke(std::make_shared(stroke)); + + break; + } + case ShapeType::GradientStroke: { + GradientStroke const &gradientStroke = *((GradientStroke *)item.get()); + + itemTree->addStroke(std::make_shared(gradientStroke)); + + break; + } + case ShapeType::Group: { + Group const &group = *((Group *)item.get()); + + auto groupItem = std::make_shared(); + groupItem->isGroup = true; + + ShapeLayerPresentationTree::renderTreeContent(group.items, groupItem); + + itemTree->addSubItem(groupItem); + + break; + } + case ShapeType::Shape: { + Shape const &shape = *((Shape *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(shape)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::Trim: { + Trim const &trim = *((Trim *)item.get()); + + auto groupItem = std::make_shared(); + groupItem->isGroup = true; + for (const auto &subItem : itemTree->subItems) { + groupItem->addSubItem(subItem); + } + groupItem->addTrim(trim); + itemTree->subItems.clear(); + itemTree->addSubItem(groupItem); + + break; + } + case ShapeType::Transform: { + auto transform = std::static_pointer_cast(item); + + itemTree->setTransform(std::make_unique(transform)); + + break; + } + case ShapeType::Ellipse: { + Ellipse const &ellipse = *((Ellipse *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(ellipse)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::Merge: { + //assert(false); + break; + } + case ShapeType::Rectangle: { + Rectangle const &rectangle = *((Rectangle *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(rectangle)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::Repeater: { + assert(false); + break; + } + case ShapeType::Star: { + Star const &star = *((Star *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(star)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::RoundedRectangle: { + //TODO:restore + break; + } + default: { + break; + } + } + } + + itemTree->initializeRenderChildren(); + } + +public: + std::shared_ptr itemTree; +}; + +ShapeCompositionLayer::ShapeCompositionLayer(std::shared_ptr const &shapeLayer) : +CompositionLayer(shapeLayer, Vector2D::Zero()) { + _contentTree = std::make_shared(shapeLayer->items); +} + +ShapeCompositionLayer::ShapeCompositionLayer(std::shared_ptr const &solidLayer) : +CompositionLayer(solidLayer, Vector2D::Zero()) { + _contentTree = std::make_shared(solidLayer); +} + +void ShapeCompositionLayer::displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) { + _frameTime = frame; + _frameTimeInitialized = true; + _contentTree->itemTree->updateFrame(_frameTime, boundingBoxContext); + _contentTree->itemTree->updateContents(std::nullopt); +} + +std::shared_ptr ShapeCompositionLayer::renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) { + if (!_frameTimeInitialized) { + _frameTime = 0.0; + _frameTimeInitialized = true; + _contentTree->itemTree->updateFrame(_frameTime, boundingBoxContext); + _contentTree->itemTree->updateContents(std::nullopt); + } + + if (!_renderTreeNode) { + _contentRenderTreeNode = std::make_shared( + Vector2D(0.0, 0.0), + Transform2D::identity(), + 1.0, + false, + false, + std::vector>(), + nullptr, + false + ); + _contentRenderTreeNode->_contentItem = _contentTree->itemTree->_contentItem; + _contentRenderTreeNode->drawContentCount = _contentTree->itemTree->_contentItem->drawContentCount; + + std::vector> subnodes; + subnodes.push_back(_contentRenderTreeNode); + + std::shared_ptr maskNode; + bool invertMask = false; + if (_matteLayer) { + maskNode = _matteLayer->renderTreeNode(boundingBoxContext); + if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) { + invertMask = true; + } + } + + _renderTreeNode = std::make_shared( + Vector2D(0.0, 0.0), + Transform2D::identity(), + 1.0, + false, + false, + subnodes, + maskNode, + invertMask + ); + } + + _contentRenderTreeNode->_size = _contentsLayer->size(); + _contentRenderTreeNode->_masksToBounds = _contentsLayer->masksToBounds(); + + _renderTreeNode->_masksToBounds = masksToBounds(); + + _renderTreeNode->_size = size(); + + return _renderTreeNode; +} + +void ShapeCompositionLayer::updateContentsLayerParameters() { + _contentRenderTreeNode->_transform = _contentsLayer->transform(); + _contentRenderTreeNode->_alpha = _contentsLayer->opacity(); + _contentRenderTreeNode->_isHidden = _contentsLayer->isHidden(); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp new file mode 100644 index 00000000000..8aff5c77a6b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp @@ -0,0 +1,36 @@ +#ifndef ShapeCompositionLayer_hpp +#define ShapeCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Layers/ShapeLayerModel.hpp" +#include "Lottie/Private/Model/Layers/SolidLayerModel.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" + +namespace lottie { + +class ShapeLayerPresentationTree; + +/// A CompositionLayer responsible for initializing and rendering shapes +class ShapeCompositionLayer: public CompositionLayer { +public: + ShapeCompositionLayer(std::shared_ptr const &shapeLayer); + ShapeCompositionLayer(std::shared_ptr const &solidLayer); + + virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override; + virtual std::shared_ptr renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) override; + void initializeContentsLayerParameters(); + virtual void updateContentsLayerParameters() override; + +private: + std::shared_ptr _contentTree; + + AnimationFrameTime _frameTime = 0.0; + bool _frameTimeInitialized = false; + + std::shared_ptr _renderTreeNode; + std::shared_ptr _contentRenderTreeNode; +}; + +} + +#endif /* ShapeCompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp new file mode 100644 index 00000000000..71fc3cc1d75 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp @@ -0,0 +1,487 @@ +#include "BezierPathUtils.hpp" + +namespace lottie { + +BezierPath makeEllipseBezierPath( + Vector2D const &size, + Vector2D const ¢er, + PathDirection direction +) { + const float ControlPointConstant = 0.55228; + + Vector2D half = size * 0.5; + if (direction == PathDirection::CounterClockwise) { + half.x = half.x * -1.0; + } + + Vector2D q1(center.x, center.y - half.y); + Vector2D q2(center.x + half.x, center.y); + Vector2D q3(center.x, center.y + half.y); + Vector2D q4(center.x - half.x, center.y); + + Vector2D cp = half * ControlPointConstant; + + BezierPath path(CurveVertex::relative( + q1, + Vector2D(-cp.x, 0), + Vector2D(cp.x, 0))); + path.addVertex(CurveVertex::relative( + q2, + Vector2D(0, -cp.y), + Vector2D(0, cp.y))); + + path.addVertex(CurveVertex::relative( + q3, + Vector2D(cp.x, 0), + Vector2D(-cp.x, 0))); + + path.addVertex(CurveVertex::relative( + q4, + Vector2D(0, cp.y), + Vector2D(0, -cp.y))); + + path.addVertex(CurveVertex::relative( + q1, + Vector2D(-cp.x, 0), + Vector2D(cp.x, 0))); + path.close(); + return path; +} + +BezierPath makeRectangleBezierPath( + Vector2D const &position, + Vector2D const &inputSize, + float cornerRadius, + PathDirection direction +) { + const float ControlPointConstant = 0.55228; + + Vector2D size = inputSize * 0.5; + float radius = std::min(std::min(cornerRadius, (float)size.x), (float)size.y); + + BezierPath bezierPath; + std::vector points; + + if (radius <= 0.0) { + /// No Corners + points = { + /// Lead In + CurveVertex::relative( + Vector2D(size.x, -size.y), + Vector2D::Zero(), + Vector2D::Zero()) + .translated(position), + /// Corner 1 + CurveVertex::relative( + Vector2D(size.x, size.y), + Vector2D::Zero(), + Vector2D::Zero()) + .translated(position), + /// Corner 2 + CurveVertex::relative( + Vector2D(-size.x, size.y), + Vector2D::Zero(), + Vector2D::Zero()) + .translated(position), + /// Corner 3 + CurveVertex::relative( + Vector2D(-size.x, -size.y), + Vector2D::Zero(), + Vector2D::Zero()) + .translated(position), + /// Corner 4 + CurveVertex::relative( + Vector2D(size.x, -size.y), + Vector2D::Zero(), + Vector2D::Zero()) + .translated(position) + }; + } else { + float controlPoint = radius * ControlPointConstant; + points = { + /// Lead In + CurveVertex::absolute( + Vector2D(radius, 0), + Vector2D(radius, 0), + Vector2D(radius, 0)) + .translated(Vector2D(-radius, radius)) + .translated(Vector2D(size.x, -size.y)) + .translated(position), + /// Corner 1 + CurveVertex::absolute( + Vector2D(radius, 0), // Point + Vector2D(radius, 0), // In tangent + Vector2D(radius, controlPoint)) + .translated(Vector2D(-radius, -radius)) + .translated(Vector2D(size.x, size.y)) + .translated(position), + CurveVertex::absolute( + Vector2D(0, radius), // Point + Vector2D(controlPoint, radius), // In tangent + Vector2D(0, radius)) // Out Tangent + .translated(Vector2D(-radius, -radius)) + .translated(Vector2D(size.x, size.y)) + .translated(position), + /// Corner 2 + CurveVertex::absolute( + Vector2D(0, radius), // Point + Vector2D(0, radius), // In tangent + Vector2D(-controlPoint, radius))// Out tangent + .translated(Vector2D(radius, -radius)) + .translated(Vector2D(-size.x, size.y)) + .translated(position), + CurveVertex::absolute( + Vector2D(-radius, 0), // Point + Vector2D(-radius, controlPoint), // In tangent + Vector2D(-radius, 0)) // Out tangent + .translated(Vector2D(radius, -radius)) + .translated(Vector2D(-size.x, size.y)) + .translated(position), + /// Corner 3 + CurveVertex::absolute( + Vector2D(-radius, 0), // Point + Vector2D(-radius, 0), // In tangent + Vector2D(-radius, -controlPoint)) // Out tangent + .translated(Vector2D(radius, radius)) + .translated(Vector2D(-size.x, -size.y)) + .translated(position), + CurveVertex::absolute( + Vector2D(0, -radius), // Point + Vector2D(-controlPoint, -radius), // In tangent + Vector2D(0, -radius)) // Out tangent + .translated(Vector2D(radius, radius)) + .translated(Vector2D(-size.x, -size.y)) + .translated(position), + /// Corner 4 + CurveVertex::absolute( + Vector2D(0, -radius), // Point + Vector2D(0, -radius), // In tangent + Vector2D(controlPoint, -radius)) // Out tangent + .translated(Vector2D(-radius, radius)) + .translated(Vector2D(size.x, -size.y)) + .translated(position), + CurveVertex::absolute( + Vector2D(radius, 0), // Point + Vector2D(radius, -controlPoint), // In tangent + Vector2D(radius, 0)) // Out tangent + .translated(Vector2D(-radius, radius)) + .translated(Vector2D(size.x, -size.y)) + .translated(position) + }; + } + bool reversed = direction == PathDirection::CounterClockwise; + if (reversed) { + for (auto vertexIt = points.rbegin(); vertexIt != points.rend(); vertexIt++) { + bezierPath.addVertex((*vertexIt).reversed()); + } + } else { + for (auto vertexIt = points.begin(); vertexIt != points.end(); vertexIt++) { + bezierPath.addVertex(*vertexIt); + } + } + bezierPath.close(); + return bezierPath; +} + +/// Magic number needed for building path data +static constexpr float StarNodePolystarConstant = 0.47829; + +BezierPath makeStarBezierPath( + Vector2D const &position, + float outerRadius, + float innerRadius, + float inputOuterRoundedness, + float inputInnerRoundedness, + float numberOfPoints, + float rotation, + PathDirection direction +) { + float currentAngle = degreesToRadians(rotation - 90.0); + float anglePerPoint = (2.0 * M_PI) / numberOfPoints; + float halfAnglePerPoint = anglePerPoint / 2.0; + float partialPointAmount = numberOfPoints - floor(numberOfPoints); + float outerRoundedness = inputOuterRoundedness * 0.01; + float innerRoundedness = inputInnerRoundedness * 0.01; + + Vector2D point = Vector2D::Zero(); + + float partialPointRadius = 0.0; + if (partialPointAmount != 0.0) { + currentAngle += halfAnglePerPoint * (1 - partialPointAmount); + partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius); + point.x = (partialPointRadius * cos(currentAngle)); + point.y = (partialPointRadius * sin(currentAngle)); + currentAngle += anglePerPoint * partialPointAmount / 2; + } else { + point.x = (outerRadius * cos(currentAngle)); + point.y = (outerRadius * sin(currentAngle)); + currentAngle += halfAnglePerPoint; + } + + std::vector vertices; + vertices.push_back(CurveVertex::relative(point + position, Vector2D::Zero(), Vector2D::Zero())); + + Vector2D previousPoint = point; + bool longSegment = false; + int numPoints = (int)(ceil(numberOfPoints) * 2.0); + for (int i = 0; i < numPoints; i++) { + float radius = longSegment ? outerRadius : innerRadius; + float dTheta = halfAnglePerPoint; + if (partialPointRadius != 0.0 && i == numPoints - 2) { + dTheta = anglePerPoint * partialPointAmount / 2; + } + if (partialPointRadius != 0.0 && i == numPoints - 1) { + radius = partialPointRadius; + } + previousPoint = point; + point.x = (radius * cos(currentAngle)); + point.y = (radius * sin(currentAngle)); + + if (innerRoundedness == 0.0 && outerRoundedness == 0.0) { + vertices.push_back(CurveVertex::relative(point + position, Vector2D::Zero(), Vector2D::Zero())); + } else { + float cp1Theta = (atan2(previousPoint.y, previousPoint.x) - M_PI / 2.0); + float cp1Dx = cos(cp1Theta); + float cp1Dy = sin(cp1Theta); + + float cp2Theta = (atan2(point.y, point.x) - M_PI / 2.0); + float cp2Dx = cos(cp2Theta); + float cp2Dy = sin(cp2Theta); + + float cp1Roundedness = longSegment ? innerRoundedness : outerRoundedness; + float cp2Roundedness = longSegment ? outerRoundedness : innerRoundedness; + float cp1Radius = longSegment ? innerRadius : outerRadius; + float cp2Radius = longSegment ? outerRadius : innerRadius; + + Vector2D cp1( + cp1Radius * cp1Roundedness * StarNodePolystarConstant * cp1Dx, + cp1Radius * cp1Roundedness * StarNodePolystarConstant * cp1Dy + ); + Vector2D cp2( + cp2Radius * cp2Roundedness * StarNodePolystarConstant * cp2Dx, + cp2Radius * cp2Roundedness * StarNodePolystarConstant * cp2Dy + ); + if (partialPointAmount != 0.0) { + if (i == 0) { + cp1 = cp1 * partialPointAmount; + } else if (i == numPoints - 1) { + cp2 = cp2 * partialPointAmount; + } + } + auto previousVertex = vertices[vertices.size() - 1]; + vertices[vertices.size() - 1] = CurveVertex::absolute( + previousVertex.point, + previousVertex.inTangent, + previousVertex.point - cp1 + ); + vertices.push_back(CurveVertex::relative(point + position, cp2, Vector2D::Zero())); + } + currentAngle += dTheta; + longSegment = !longSegment; + } + + bool reverse = direction == PathDirection::CounterClockwise; + BezierPath path; + if (reverse) { + for (auto vertexIt = vertices.rbegin(); vertexIt != vertices.rend(); vertexIt++) { + path.addVertex((*vertexIt).reversed()); + } + } else { + for (auto vertexIt = vertices.begin(); vertexIt != vertices.end(); vertexIt++) { + path.addVertex(*vertexIt); + } + } + path.close(); + return path; +} + +CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, float start, float end, float offset, TrimType type) { + /// No need to trim, it's a full path + if (start == 0.0 && end == 1.0) { + return sourcePath; + } + + /// All paths are empty. + if (start == end) { + return CompoundBezierPath(); + } + + if (type == TrimType::Simultaneously) { + CompoundBezierPath result; + + for (BezierPath &path : sourcePath.paths) { + CompoundBezierPath tempPath; + tempPath.appendPath(path); + + auto subPaths = tempPath.trim(start, end, offset); + + for (const auto &subPath : subPaths->paths) { + result.appendPath(subPath); + } + } + + return result; + } + + /// Individual path trimming. + + /// Brace yourself for the below code. + + /// Normalize lengths with offset. + float startPosition = fmod(start + offset, 1.0); + float endPosition = fmod(end + offset, 1.0); + + if (startPosition < 0.0) { + startPosition = 1.0 + startPosition; + } + + if (endPosition < 0.0) { + endPosition = 1.0 + endPosition; + } + if (startPosition == 1.0) { + startPosition = 0.0; + } + if (endPosition == 0.0) { + endPosition = 1.0; + } + + /// First get the total length of all paths. + float totalLength = 0.0; + for (auto &upstreamPath : sourcePath.paths) { + totalLength += upstreamPath.length(); + } + + /// Now determine the start and end cut lengths + float startLength = startPosition * totalLength; + float endLength = endPosition * totalLength; + float pathStart = 0.0; + + CompoundBezierPath result; + + /// Now loop through all path containers + for (auto &pathContainer : sourcePath.paths) { + auto pathEnd = pathStart + pathContainer.length(); + + if (!isInRange(startLength, pathStart, pathEnd) && + isInRange(endLength, pathStart, pathEnd)) { + // pathStart|=======E----------------------|pathEnd + // Cut path components, removing after end. + + float pathCutLength = endLength - pathStart; + float subpathStart = 0.0; + float subpathEnd = subpathStart + pathContainer.length(); + if (pathCutLength < subpathEnd) { + /// This is the subpath that needs to be cut. + float cutLength = pathCutLength - subpathStart; + + CompoundBezierPath tempPath; + tempPath.appendPath(pathContainer); + auto newPaths = tempPath.trim(0, cutLength / pathContainer.length(), 0); + for (const auto &newPath : newPaths->paths) { + result.appendPath(newPath); + } + } else { + /// Add to container and move on + result.appendPath(pathContainer); + } + /*if (pathCutLength == subpathEnd) { + /// Right on the end. The next subpath is not included. Break. + break; + } + subpathStart = subpathEnd;*/ + } else if (!isInRange(endLength, pathStart, pathEnd) && + isInRange(startLength, pathStart, pathEnd)) { + // pathStart|-------S======================|pathEnd + // + + // Cut path components, removing before beginning. + float pathCutLength = startLength - pathStart; + // Clear paths from container + float subpathStart = 0.0; + float subpathEnd = subpathStart + pathContainer.length(); + + if (subpathStart < pathCutLength && pathCutLength < subpathEnd) { + /// This is the subpath that needs to be cut. + float cutLength = pathCutLength - subpathStart; + CompoundBezierPath tempPath; + tempPath.appendPath(pathContainer); + auto newPaths = tempPath.trim(cutLength / pathContainer.length(), 1, 0); + for (const auto &newPath : newPaths->paths) { + result.appendPath(newPath); + } + } else if (pathCutLength <= subpathStart) { + result.appendPath(pathContainer); + } + //subpathStart = subpathEnd; + } else if (isInRange(endLength, pathStart, pathEnd) && + isInRange(startLength, pathStart, pathEnd)) { + // pathStart|-------S============E---------|endLength + // pathStart|=====E----------------S=======|endLength + // trim from path beginning to endLength. + + // Cut path components, removing before beginnings. + float startCutLength = startLength - pathStart; + float endCutLength = endLength - pathStart; + + float subpathStart = 0.0; + + float subpathEnd = subpathStart + pathContainer.length(); + + if (!isInRange(startCutLength, subpathStart, subpathEnd) && + !isInRange(endCutLength, subpathStart, subpathEnd)) + { + // The whole path is included. Add + // S|==============================|E + result.appendPath(pathContainer); + } else if (isInRange(startCutLength, subpathStart, subpathEnd) && + !isInRange(endCutLength, subpathStart, subpathEnd)) { + /// The start of the path needs to be trimmed + // |-------S======================|E + float cutLength = startCutLength - subpathStart; + CompoundBezierPath tempPath; + tempPath.appendPath(pathContainer); + auto newPaths = tempPath.trim(cutLength / pathContainer.length(), 1, 0); + for (const auto &newPath : newPaths->paths) { + result.appendPath(newPath); + } + } else if (!isInRange(startCutLength, subpathStart, subpathEnd) && + isInRange(endCutLength, subpathStart, subpathEnd)) { + // S|=======E----------------------| + float cutLength = endCutLength - subpathStart; + CompoundBezierPath tempPath; + tempPath.appendPath(pathContainer); + auto newPaths = tempPath.trim(0, cutLength / pathContainer.length(), 0); + for (const auto &newPath : newPaths->paths) { + result.appendPath(newPath); + } + } else if (isInRange(startCutLength, subpathStart, subpathEnd) && + isInRange(endCutLength, subpathStart, subpathEnd)) { + // |-------S============E---------| + float cutFromLength = startCutLength - subpathStart; + float cutToLength = endCutLength - subpathStart; + CompoundBezierPath tempPath; + tempPath.appendPath(pathContainer); + auto newPaths = tempPath.trim( + cutFromLength / pathContainer.length(), + cutToLength / pathContainer.length(), + 0 + ); + for (const auto &newPath : newPaths->paths) { + result.appendPath(newPath); + } + } + } else if ((endLength <= pathStart && pathEnd <= startLength) || + (startLength <= pathStart && endLength <= pathStart) || + (pathEnd <= startLength && pathEnd <= endLength)) { + /// The Path needs to be cleared + } else { + result.appendPath(pathContainer); + } + + pathStart = pathEnd; + } + + return result; +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp new file mode 100644 index 00000000000..677dbe96bd1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp @@ -0,0 +1,39 @@ +#ifndef BezierPaths_h +#define BezierPaths_h + +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include +#include "Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp" +#include "Lottie/Private/Model/ShapeItems/Trim.hpp" + +namespace lottie { + +BezierPath makeEllipseBezierPath( + Vector2D const &size, + Vector2D const ¢er, + PathDirection direction +); + +BezierPath makeRectangleBezierPath( + Vector2D const &position, + Vector2D const &inputSize, + float cornerRadius, + PathDirection direction +); + +BezierPath makeStarBezierPath( + Vector2D const &position, + float outerRadius, + float innerRadius, + float inputOuterRoundedness, + float inputInnerRoundedness, + float numberOfPoints, + float rotation, + PathDirection direction +); + +CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, float start, float end, float offset, TrimType type); + +} + +#endif /* BezierPaths_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.cpp new file mode 100644 index 00000000000..5e773d361ba --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "TextCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp new file mode 100644 index 00000000000..98d7edbbc24 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp @@ -0,0 +1,81 @@ +#ifndef TextCompositionLayer_hpp +#define TextCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Layers/TextLayerModel.hpp" +#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp" +#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp" + +namespace lottie { + +class TextCompositionLayer: public CompositionLayer { +public: + TextCompositionLayer(std::shared_ptr const &textLayer, std::shared_ptr textProvider, std::shared_ptr fontProvider) : + CompositionLayer(textLayer, Vector2D::Zero()) { + std::shared_ptr rootNode; + for (const auto &animator : textLayer->animators) { + rootNode = std::make_shared(rootNode, animator); + } + _rootNode = rootNode; + _textDocument = std::make_shared>(textLayer->text.keyframes); + + _textProvider = textProvider; + _fontProvider = fontProvider; + + if (_rootNode) { + _childKeypaths.push_back(rootNode); + } + } + + std::shared_ptr const &textProvider() const { + return _textProvider; + } + void setTextProvider(std::shared_ptr const &textProvider) { + _textProvider = textProvider; + } + + std::shared_ptr const &fontProvider() const { + return _fontProvider; + } + void setFontProvider(std::shared_ptr const &fontProvider) { + _fontProvider = fontProvider; + } + + virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override { + if (!_textDocument) { + return; + } + + bool documentUpdate = _textDocument->hasUpdate(frame); + + bool animatorUpdate = false; + if (_rootNode) { + animatorUpdate = _rootNode->updateContents(frame, forceUpdates); + } + + if (!(documentUpdate || animatorUpdate)) { + return; + } + + if (_rootNode) { + _rootNode->rebuildOutputs(frame); + } + } + +public: + virtual bool isTextCompositionLayer() const override { + return true; + } + +private: + std::shared_ptr _rootNode; + std::shared_ptr> _textDocument; + + std::shared_ptr _textProvider; + std::shared_ptr _fontProvider; +}; + +} + +#endif /* TextCompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.cpp new file mode 100644 index 00000000000..a205f16eba9 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.cpp @@ -0,0 +1,5 @@ +#include "MainThreadAnimationLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp new file mode 100644 index 00000000000..b9e47a7a602 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp @@ -0,0 +1,279 @@ +#ifndef MainThreadAnimationLayer_hpp +#define MainThreadAnimationLayer_hpp + +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Public/ImageProvider/AnimationImageProvider.hpp" +#include "Lottie/Private/Model/Animation.hpp" +#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp" +#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp" +#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp" +#include "Lottie/Public/DynamicProperties/AnimationKeypath.hpp" + +namespace lottie { + +class BlankImageProvider: public AnimationImageProvider { +public: + virtual ~BlankImageProvider() = default; + + std::shared_ptr imageForAsset(ImageAsset const &asset) { + return nullptr; + } +}; + +class MainThreadAnimationLayer: public CALayer { +public: + MainThreadAnimationLayer( + Animation const &animation, + std::shared_ptr const &imageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider + ) { + if (animation.assetLibrary) { + _layerImageProvider = std::make_shared(imageProvider, animation.assetLibrary->imageAssets); + } else { + std::map> imageAssets; + _layerImageProvider = std::make_shared(imageProvider, imageAssets); + } + + _layerTextProvider = std::make_shared(textProvider); + _layerFontProvider = std::make_shared(fontProvider); + + setSize(Vector2D(animation.width, animation.height)); + + auto layers = initializeCompositionLayers( + animation.layers, + animation.assetLibrary, + _layerImageProvider, + textProvider, + fontProvider, + animation.framerate + ); + + std::vector> imageLayers; + std::vector> textLayers; + + std::shared_ptr mattedLayer; + + for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) { + std::shared_ptr const &layer = *layerIt; + layer->setSize(size()); + _animationLayers.push_back(layer); + + if (layer->isImageCompositionLayer()) { + imageLayers.push_back(std::static_pointer_cast(layer)); + } + if (layer->isTextCompositionLayer()) { + textLayers.push_back(std::static_pointer_cast(layer)); + } + + if (mattedLayer) { + /// The previous layer requires this layer to be its matte + mattedLayer->setMatteLayer(layer); + mattedLayer = nullptr; + continue; + } + if (layer->matteType().has_value() && (layer->matteType() == MatteType::Add || layer->matteType() == MatteType::Invert)) { + /// We have a layer that requires a matte. + mattedLayer = layer; + } + addSublayer(layer); + } + + _layerImageProvider->addImageLayers(imageLayers); + _layerImageProvider->reloadImages(); + _layerTextProvider->addTextLayers(textLayers); + _layerTextProvider->reloadTexts(); + _layerFontProvider->addTextLayers(textLayers); + _layerFontProvider->reloadTexts(); + + renderTreeNode(); + } + + void setRespectAnimationFrameRate(bool respectAnimationFrameRate) { + _respectAnimationFrameRate = respectAnimationFrameRate; + } + + void display() { + float newFrame = currentFrame(); + if (_respectAnimationFrameRate) { + newFrame = floor(newFrame); + } + for (const auto &layer : _animationLayers) { + layer->displayWithFrame(newFrame, false, _boundingBoxContext); + } + } + + std::vector> const &animationLayers() const { + return _animationLayers; + } + + void reloadImages() { + _layerImageProvider->reloadImages(); + } + + /// Forces the view to update its drawing. + void forceDisplayUpdate() { + for (const auto &layer : _animationLayers) { + layer->displayWithFrame(currentFrame(), true, _boundingBoxContext); + } + } + + void logHierarchyKeypaths() { + printf("Lottie: Logging Animation Keypaths\n"); + assert(false); + //animationLayers.forEach({ $0.logKeypaths(for: nil) }) + } + + void setValueProvider(std::shared_ptr const &valueProvider, AnimationKeypath const &keypath) { + /*for (const auto &layer : _animationLayers) { + assert(false); + if let foundProperties = layer.nodeProperties(for: keypath) { + for property in foundProperties { + property.setProvider(provider: valueProvider) + } + layer.displayWithFrame(frame: presentation()?.currentFrame ?? currentFrame, forceUpdates: true) + } + }*/ + } + + std::optional getValue(AnimationKeypath const &keypath, std::optional atFrame) { + /*for (const auto &layer : _animationLayers) { + assert(false); + if + let foundProperties = layer.nodeProperties(for: keypath), + let first = foundProperties.first + { + return first.valueProvider.value(frame: atFrame ?? currentFrame) + } + }*/ + return std::nullopt; + } + + std::optional getOriginalValue(AnimationKeypath const &keypath, std::optional atFrame) { + /*for (const auto &layer : _animationLayers) { + assert(false); + if + let foundProperties = layer.nodeProperties(for: keypath), + let first = foundProperties.first + { + return first.originalValueProvider.value(frame: atFrame ?? currentFrame) + } + }*/ + return std::nullopt; + } + + std::shared_ptr layerForKeypath(AnimationKeypath const &keyPath) { + assert(false); + /*for layer in animationLayers { + if let foundLayer = layer.layer(for: keypath) { + return foundLayer + } + }*/ + return nullptr; + } + + std::vector> animatorNodesForKeypath(AnimationKeypath const &keypath) { + std::vector> results; + /*for (const auto &layer : _animationLayers) { + if let nodes = layer.animatorNodes(for: keypath) { + results.append(contentsOf: nodes) + } + }*/ + return results; + } + + float currentFrame() const { + return _currentFrame; + } + void setCurrentFrame(float currentFrame) { + _currentFrame = currentFrame; + + for (size_t i = 0; i < _animationLayers.size(); i++) { + _animationLayers[i]->displayWithFrame(_currentFrame, false, _boundingBoxContext); + } + } + + std::shared_ptr imageProvider() const { + return _layerImageProvider->imageProvider(); + } + void setImageProvider(std::shared_ptr const &imageProvider) { + _layerImageProvider->setImageProvider(imageProvider); + } + + std::shared_ptr textProvider() const { + return _layerTextProvider->textProvider(); + } + void setTextProvider(std::shared_ptr const &textProvider) { + _layerTextProvider->setTextProvider(textProvider); + } + + std::shared_ptr fontProvider() const { + return _layerFontProvider->fontProvider(); + } + void setFontProvider(std::shared_ptr const &fontProvider) { + _layerFontProvider->setFontProvider(fontProvider); + } + + virtual std::shared_ptr renderTreeNode() { + if (!_renderTreeNode) { + std::vector> subnodes; + for (const auto &animationLayer : _animationLayers) { + bool found = false; + for (const auto &sublayer : sublayers()) { + if (animationLayer == sublayer) { + found = true; + break; + } + } + if (found) { + auto node = animationLayer->renderTreeNode(_boundingBoxContext); + if (node) { + subnodes.push_back(node); + } + } + } + _renderTreeNode = std::make_shared( + size(), + Transform2D::identity(), + 1.0, + false, + false, + subnodes, + nullptr, + false + ); + } + + return _renderTreeNode; + } + +private: + // MARK: Internal + + /// The animatable Current Frame Property + float _currentFrame = 0.0; + + std::shared_ptr _imageProvider; + std::shared_ptr _textProvider; + std::shared_ptr _fontProvider; + + bool _respectAnimationFrameRate = true; + + std::vector> _animationLayers; + + std::shared_ptr _layerImageProvider; + std::shared_ptr _layerTextProvider; + std::shared_ptr _layerFontProvider; + + std::shared_ptr _renderTreeNode; + + BezierPathsBoundingBoxContext _boundingBoxContext; +}; + +} + +#endif /* MainThreadAnimationLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp new file mode 100644 index 00000000000..8a98548bd3e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp @@ -0,0 +1,111 @@ +#include "CompositionLayersInitializer.hpp" + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp" + +namespace lottie { + +std::vector> initializeCompositionLayers( + std::vector> const &layers, + std::shared_ptr const &assetLibrary, + std::shared_ptr const &layerImageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider, + float frameRate +) { + std::vector> compositionLayers; + std::map> layerMap; + + std::vector> childLayers; + + for (const auto &layer : layers) { + if (layer->hidden) { + auto genericLayer = std::make_shared(layer); + compositionLayers.push_back(genericLayer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), genericLayer)); + } + } else if (layer->type == LayerType::Shape) { + auto shapeContainer = std::make_shared(std::static_pointer_cast(layer)); + compositionLayers.push_back(shapeContainer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), shapeContainer)); + } + } else if (layer->type == LayerType::Solid) { + auto shapeContainer = std::make_shared(std::static_pointer_cast(layer)); + compositionLayers.push_back(shapeContainer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), shapeContainer)); + } + } else if (layer->type == LayerType::Precomp && assetLibrary) { + auto precompLayer = std::static_pointer_cast(layer); + auto precompAssetIt = assetLibrary->precompAssets.find(precompLayer->referenceID); + if (precompAssetIt != assetLibrary->precompAssets.end()) { + auto precompContainer = std::make_shared( + precompLayer, + *(precompAssetIt->second), + layerImageProvider, + textProvider, + fontProvider, + assetLibrary, + frameRate + ); + compositionLayers.push_back(precompContainer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), precompContainer)); + } + } + } else if (layer->type == LayerType::Image && assetLibrary) { + auto imageLayer = std::static_pointer_cast(layer); + auto imageAssetIt = assetLibrary->imageAssets.find(imageLayer->referenceID); + if (imageAssetIt != assetLibrary->imageAssets.end()) { + auto imageContainer = std::make_shared( + imageLayer, + Vector2D((*imageAssetIt->second).width, (*imageAssetIt->second).height) + ); + compositionLayers.push_back(imageContainer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), imageContainer)); + } + } + } else if (layer->type == LayerType::Text) { + auto textContainer = std::make_shared(std::static_pointer_cast(layer), textProvider, fontProvider); + compositionLayers.push_back(textContainer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), textContainer)); + } + } else { + auto genericLayer = std::make_shared(layer); + compositionLayers.push_back(genericLayer); + if (layer->index) { + layerMap.insert(std::make_pair(layer->index.value(), genericLayer)); + } + } + if (layer->parent) { + childLayers.push_back(layer); + } + } + + /// Now link children with their parents + for (const auto &layerModel : childLayers) { + if (!layerModel->index.has_value()) { + continue; + } + if (const auto parentID = layerModel->parent) { + auto childLayerIt = layerMap.find(layerModel->index.value()); + if (childLayerIt != layerMap.end()) { + auto parentLayerIt = layerMap.find(parentID.value()); + if (parentLayerIt != layerMap.end()) { + childLayerIt->second->transformNode()->setParentNode(parentLayerIt->second->transformNode()); + } + } + } + } + + return compositionLayers; +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp new file mode 100644 index 00000000000..74df3529d34 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp @@ -0,0 +1,23 @@ +#ifndef CompositionLayersInitializer_hpp +#define CompositionLayersInitializer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Assets/AssetLibrary.hpp" +#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp" +#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp" +#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp" + +namespace lottie { + +std::vector> initializeCompositionLayers( + std::vector> const &layers, + std::shared_ptr const &assetLibrary, + std::shared_ptr const &layerImageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider, + float frameRate +); + +} + +#endif /* CompositionLayersInitializer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.cpp new file mode 100644 index 00000000000..d6123ee219d --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.cpp @@ -0,0 +1,5 @@ +#include "LayerFontProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp new file mode 100644 index 00000000000..ff9eaae19cf --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp @@ -0,0 +1,45 @@ +#ifndef LayerFontProvider_hpp +#define LayerFontProvider_hpp + +#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp" + +namespace lottie { + +/// Connects a LottieFontProvider to a group of text layers +class LayerFontProvider { +public: + LayerFontProvider(std::shared_ptr const &fontProvider) { + _fontProvider = fontProvider; + reloadTexts(); + } + + std::shared_ptr const &fontProvider() const { + return _fontProvider; + } + void setFontProvider(std::shared_ptr const &fontProvider) { + _fontProvider = fontProvider; + reloadTexts(); + } + + void addTextLayers(std::vector> const &layers) { + for (const auto &layer : layers) { + _textLayers.push_back(layer); + } + } + + void reloadTexts() { + for (const auto &layer : _textLayers) { + layer->setFontProvider(_fontProvider); + } + } + +private: + std::vector> _textLayers; + + std::shared_ptr _fontProvider; +}; + +} + +#endif /* LayerFontProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.cpp new file mode 100644 index 00000000000..536c0437493 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.cpp @@ -0,0 +1,5 @@ +#include "LayerImageProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp new file mode 100644 index 00000000000..d9affbd1cf1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp @@ -0,0 +1,58 @@ +#ifndef LayerImageProvider_hpp +#define LayerImageProvider_hpp + +#include "Lottie/Public/ImageProvider/AnimationImageProvider.hpp" +#include "Lottie/Private/Model/Assets/ImageAsset.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp" + +namespace lottie { + +/// Connects a LottieImageProvider to a group of image layers +class LayerImageProvider { +public: + LayerImageProvider(std::shared_ptr const &imageProvider, std::map> const &assets) : + _imageProvider(imageProvider), + _imageAssets(assets) { + reloadImages(); + } + + std::shared_ptr imageProvider() const { + return _imageProvider; + } + void setImageProvider(std::shared_ptr const &imageProvider) { + _imageProvider = imageProvider; + reloadImages(); + } + + std::vector> const &imageLayers() const { + return _imageLayers; + } + + void addImageLayers(std::vector> const &layers) { + for (const auto &layer : layers) { + auto it = _imageAssets.find(layer->imageReferenceID()); + if (it != _imageAssets.end()) { + _imageLayers.push_back(layer); + } + } + } + + void reloadImages() { + for (const auto &imageLayer : imageLayers()) { + auto it = _imageAssets.find(imageLayer->imageReferenceID()); + if (it != _imageAssets.end()) { + imageLayer->setImage(_imageProvider->imageForAsset(*it->second)); + } + } + } + +private: + std::shared_ptr _imageProvider; + std::vector> _imageLayers; + + std::map> _imageAssets; +}; + +} + +#endif /* LayerImageProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.cpp new file mode 100644 index 00000000000..22da907bd36 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.cpp @@ -0,0 +1,5 @@ +#include "LayerTextProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp new file mode 100644 index 00000000000..82c96ec8f39 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp @@ -0,0 +1,45 @@ +#ifndef LayerTextProvider_hpp +#define LayerTextProvider_hpp + +#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp" + +namespace lottie { + +/// Connects a LottieTextProvider to a group of text layers +class LayerTextProvider { +public: + LayerTextProvider(std::shared_ptr const &textProvider) { + _textProvider = textProvider; + reloadTexts(); + } + + std::shared_ptr const &textProvider() const { + return _textProvider; + } + void setTextProvider(std::shared_ptr const &textProvider) { + _textProvider = textProvider; + reloadTexts(); + } + + void addTextLayers(std::vector> const &layers) { + for (const auto &layer : layers) { + _textLayers.push_back(layer); + } + } + + void reloadTexts() { + for (const auto &layer : _textLayers) { + layer->setTextProvider(_textProvider); + } + } + +private: + std::vector> _textLayers; + + std::shared_ptr _textProvider; +}; + +} + +#endif /* LayerTextProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.cpp new file mode 100644 index 00000000000..8f751a571f3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.cpp @@ -0,0 +1,5 @@ +#include "LayerTransformNode.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp new file mode 100644 index 00000000000..459558a2bd3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp @@ -0,0 +1,205 @@ +#ifndef LayerTransformNode_hpp +#define LayerTransformNode_hpp + +#include "Lottie/Private/Model/Objects/Transform.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp" + +namespace lottie { + +class LayerTransformProperties: public KeypathSearchableNodePropertyMap { +public: + LayerTransformProperties(std::shared_ptr transform) { + _anchor = std::make_shared>(std::make_shared>(transform->anchorPoint().keyframes)); + _scale = std::make_shared>(std::make_shared>(transform->scale().keyframes)); + _rotation = std::make_shared>(std::make_shared>(transform->rotation().keyframes)); + _opacity = std::make_shared>(std::make_shared>(transform->opacity().keyframes)); + + std::map> propertyMap; + _keypathProperties.insert(std::make_pair("Anchor Point", _anchor)); + _keypathProperties.insert(std::make_pair("Scale", _scale)); + _keypathProperties.insert(std::make_pair("Rotation", _rotation)); + _keypathProperties.insert(std::make_pair("Opacity", _opacity)); + + if (transform->positionX().has_value() && transform->positionY().has_value()) { + auto xPosition = std::make_shared>(std::make_shared>(transform->positionX()->keyframes)); + auto yPosition = std::make_shared>(std::make_shared>(transform->positionY()->keyframes)); + _keypathProperties.insert(std::make_pair("X Position", xPosition)); + _keypathProperties.insert(std::make_pair("Y Position", yPosition)); + + _positionX = xPosition; + _positionY = yPosition; + _position = nullptr; + } else if (transform->position().has_value()) { + auto position = std::make_shared>(std::make_shared>(transform->position()->keyframes)); + _keypathProperties.insert(std::make_pair("Position", position)); + + _position = position; + _positionX = nullptr; + _positionY = nullptr; + } else { + _position = nullptr; + _positionX = nullptr; + _positionY = nullptr; + } + + for (const auto &it : _keypathProperties) { + _properties.push_back(it.second); + } + } + + virtual ~LayerTransformProperties() = default; + + virtual std::vector> &properties() override { + return _properties; + } + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + virtual std::string keypathName() const override { + return "Transform"; + } + + virtual std::map> keypathProperties() const override { + return _keypathProperties; + } + + virtual std::shared_ptr keypathLayer() const override { + return nullptr; + } + + std::shared_ptr> const &anchor() { + return _anchor; + } + + std::shared_ptr> const &scale() { + return _scale; + } + + std::shared_ptr> const &rotation() { + return _rotation; + } + + std::shared_ptr> const &position() { + return _position; + } + + std::shared_ptr> const &positionX() { + return _positionX; + } + + std::shared_ptr> const &positionY() { + return _positionY; + } + + std::shared_ptr> const &opacity() { + return _opacity; + } + +private: + std::map> _keypathProperties; + std::vector> _childKeypaths; + + std::vector> _properties; + + std::shared_ptr> _anchor; + std::shared_ptr> _scale; + std::shared_ptr> _rotation; + std::shared_ptr> _position; + std::shared_ptr> _positionX; + std::shared_ptr> _positionY; + std::shared_ptr> _opacity; +}; + +class LayerTransformNode: public AnimatorNode { +public: + LayerTransformNode(std::shared_ptr transform) : + AnimatorNode(nullptr), + _transformProperties(std::make_shared(transform)) { + _outputNode = std::make_shared(nullptr); + } + + virtual ~LayerTransformNode() = default; + + virtual std::shared_ptr outputNode() override { + return _outputNode; + } + + virtual std::shared_ptr propertyMap() const override { + return _transformProperties; + } + + virtual bool shouldRebuildOutputs(float frame) override { + return hasLocalUpdates() || hasUpstreamUpdates(); + } + + virtual void rebuildOutputs(float frame) override { + _opacity = ((float)_transformProperties->opacity()->value().value) * 0.01f; + + Vector2D position(0.0, 0.0); + if (_transformProperties->position()) { + auto position3d = _transformProperties->position()->value(); + position.x = position3d.x; + position.y = position3d.y; + } else if (_transformProperties->positionX() && _transformProperties->positionY()) { + position = Vector2D( + _transformProperties->positionX()->value().value, + _transformProperties->positionY()->value().value + ); + } + + Vector3D anchor = _transformProperties->anchor()->value(); + Vector3D scale = _transformProperties->scale()->value(); + _localTransform = Transform2D::makeTransform( + Vector2D(anchor.x, anchor.y), + position, + Vector2D(scale.x, scale.y), + _transformProperties->rotation()->value().value, + std::nullopt, + std::nullopt + ); + + if (parentNode() && parentNode()->asLayerTransformNode()) { + _globalTransform = _localTransform * parentNode()->asLayerTransformNode()->_globalTransform; + } else { + _globalTransform = _localTransform; + } + } + + std::shared_ptr const &transformProperties() { + return _transformProperties; + } + + float opacity() { + return _opacity; + } + + Transform2D const &globalTransform() { + return _globalTransform; + } + +private: + std::shared_ptr _outputNode; + + std::shared_ptr _transformProperties; + + float _opacity = 1.0; + Transform2D _localTransform = Transform2D::identity(); + Transform2D _globalTransform = Transform2D::identity(); + +public: + virtual LayerTransformNode *asLayerTransformNode() override { + return this; + } +}; + +} + +#endif /* LayerTransformNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.cpp new file mode 100644 index 00000000000..eb9e3a58374 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.cpp @@ -0,0 +1,5 @@ +#include "NodeProperty.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp new file mode 100644 index 00000000000..f77cdc470ba --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp @@ -0,0 +1,54 @@ +#ifndef NodeProperty_hpp +#define NodeProperty_hpp + +#include "Lottie/Public/Primitives/AnyValue.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp" +#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp" + +namespace lottie { + +/// A node property that holds a reference to a T ValueProvider and a T ValueContainer. +template +class NodeProperty: public AnyNodeProperty { +public: + NodeProperty(std::shared_ptr> provider) : + _typedContainer(provider->value(0.0)), + _valueProvider(provider) { + _typedContainer.setNeedsUpdate(); + } + +public: + virtual AnyValue::Type valueType() const override { + return AnyValueType::type(); + } + + virtual T value() { + return _typedContainer.outputValue(); + } + + virtual bool needsUpdate(float frame) const override { + return _typedContainer.needsUpdate() || _valueProvider->hasUpdate(frame); + } + + virtual void setProvider(std::shared_ptr provider) override { + /*if (provider->valueType() != valueType()) { + return; + } + _valueProvider = provider; + _typedContainer.setNeedsUpdate();*/ + } + + virtual void update(float frame) override { + _typedContainer.setValue(_valueProvider->value(frame), frame); + } + +private: + ValueContainer _typedContainer; + std::shared_ptr> _valueProvider; + //std::shared_ptr _originalValueProvider; +}; + +} + +#endif /* NodeProperty_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.cpp new file mode 100644 index 00000000000..8609641d496 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.cpp @@ -0,0 +1,5 @@ +#include "AnyNodeProperty.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp new file mode 100644 index 00000000000..ec682ceb932 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp @@ -0,0 +1,33 @@ +#ifndef AnyNodeProperty_hpp +#define AnyNodeProperty_hpp + +#include "Lottie/Public/Primitives/AnyValue.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp" +#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp" + +#include + +namespace lottie { + +/// A property of a node. The node property holds a provider and a container +class AnyNodeProperty { +public: + virtual ~AnyNodeProperty() = default; + +public: + /// Returns true if the property needs to recompute its stored value + virtual bool needsUpdate(float frame) const = 0; + + /// Updates the property for the frame + virtual void update(float frame) = 0; + + /// The Type of the value provider + virtual AnyValue::Type valueType() const = 0; + + /// Sets the value provider for the property. + virtual void setProvider(std::shared_ptr provider) = 0; +}; + +} + +#endif /* AnyNodeProperty_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.cpp new file mode 100644 index 00000000000..b186f2e2f49 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.cpp @@ -0,0 +1,5 @@ +#include "AnyValueContainer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp new file mode 100644 index 00000000000..1776bd13bc1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp @@ -0,0 +1,25 @@ +#ifndef AnyValueContainer_hpp +#define AnyValueContainer_hpp + +#include "Lottie/Public/Primitives/AnyValue.hpp" + +namespace lottie { + +class AnyValueContainer { +public: + /// The stored value of the container + virtual AnyValue value() const = 0; + + /// Notifies the provider that it should update its container + virtual void setNeedsUpdate() = 0; + + /// When true the container needs to have its value updated by its provider + virtual bool needsUpdate() const = 0; + + /// The frame time of the last provided update + virtual float lastUpdateFrame() const = 0; +}; + +} + +#endif /* AnyValueContainer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp new file mode 100644 index 00000000000..bc6a13261e2 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp @@ -0,0 +1,13 @@ +#ifndef HasRenderUpdates_hpp +#define HasRenderUpdates_hpp + +namespace lottie { + +class HasRenderUpdates { +public: + virtual bool hasRenderUpdates(float forFrame) = 0; +}; + +} + +#endif /* HasRenderUpdates_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp new file mode 100644 index 00000000000..f0c35b05308 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp @@ -0,0 +1,14 @@ +#ifndef HasUpdate_hpp +#define HasUpdate_hpp + +namespace lottie { + +class HasUpdate { +public: + /// The last frame in which this node was updated. + virtual bool hasUpdate() = 0; +}; + +} + +#endif /* HasUpdate_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.cpp new file mode 100644 index 00000000000..62c08facbad --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.cpp @@ -0,0 +1,5 @@ +#include "KeypathSearchable.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp new file mode 100644 index 00000000000..4586c7ddc24 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp @@ -0,0 +1,36 @@ +#ifndef KeypathSearchable_hpp +#define KeypathSearchable_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp" +#include "Lottie/Public/Primitives/CALayer.hpp" + +#include +#include +#include +#include + +namespace lottie { + +class KeypathSearchable; + +class HasChildKeypaths { +public: + /// Children Keypaths + virtual std::vector> const &childKeypaths() const = 0; +}; + +/// Protocol that provides keypath search functionality. Returns all node properties associated with a keypath. +class KeypathSearchable: virtual public HasChildKeypaths { +public: + /// The name of the Keypath + virtual std::string keypathName() const = 0; + + /// A list of properties belonging to the keypath. + virtual std::map> keypathProperties() const = 0; + + virtual std::shared_ptr keypathLayer() const = 0; +}; + +} + +#endif /* KeypathSearchable_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.cpp new file mode 100644 index 00000000000..100edba8df9 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.cpp @@ -0,0 +1,5 @@ +#include "NodePropertyMap.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp new file mode 100644 index 00000000000..0879d00c828 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp @@ -0,0 +1,41 @@ +#ifndef NodePropertyMap_hpp +#define NodePropertyMap_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp" +#include "Lottie/Public/Primitives/CALayer.hpp" + +#include + +namespace lottie { + +class NodePropertyMap: virtual public HasChildKeypaths { +public: + virtual std::vector> &properties() = 0; + + bool needsLocalUpdate(float frame) { + for (auto &property : properties()) { + if (property->needsUpdate(frame)) { + return true; + } + } + return false; + } + + void updateNodeProperties(float frame) { + for (auto &property : properties()) { + property->update(frame); + } + } +}; + +class KeypathSearchableNodePropertyMap: virtual public NodePropertyMap, virtual public KeypathSearchable { +public: + virtual std::shared_ptr keypathLayer() const override { + return nullptr; + } +}; + +} + +#endif /* NodePropertyMap_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.cpp new file mode 100644 index 00000000000..2ead9de1edc --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.cpp @@ -0,0 +1,5 @@ +#include "ValueContainer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp new file mode 100644 index 00000000000..563de16de5b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp @@ -0,0 +1,58 @@ +#ifndef ValueContainer_hpp +#define ValueContainer_hpp + +#include "Lottie/Public/Primitives/AnyValue.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp" + +namespace lottie { + +/// A container for a node value that is Typed to T. +template +class ValueContainer: public AnyValueContainer { +public: + ValueContainer(T value) : + _outputValue(value) { + } + +public: + float _lastUpdateFrame = std::numeric_limits::infinity(); + bool _needsUpdate = true; + + virtual AnyValue value() const override { + return AnyValue(_outputValue); + } + + virtual bool needsUpdate() const override { + return _needsUpdate; + } + + virtual float lastUpdateFrame() const override { + return _lastUpdateFrame; + } + + T _outputValue; + + T outputValue() { + return _outputValue; + } + void setOutputValue(T value) { + _outputValue = value; + _needsUpdate = false; + } + + void setValue(AnyValue value, float forFrame) { + if (value.type() == AnyValueType::type()) { + _needsUpdate = false; + _lastUpdateFrame = forFrame; + _outputValue = value.get(); + } + } + + virtual void setNeedsUpdate() override { + _needsUpdate = true; + } +}; + +} + +#endif /* ValueContainer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.cpp new file mode 100644 index 00000000000..b3f91f556b3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.cpp @@ -0,0 +1,5 @@ +#include "DashPatternInterpolator.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp new file mode 100644 index 00000000000..5e744386dd5 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp @@ -0,0 +1,48 @@ +#ifndef DashPatternInterpolator_hpp +#define DashPatternInterpolator_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" +#include "Lottie/Public/Primitives/DashPattern.hpp" + +namespace lottie { + +/// A value provider that produces an array of values from an array of Keyframe Interpolators +class DashPatternInterpolator: public ValueProvider, public std::enable_shared_from_this { +public: + /// Initialize with an array of array of keyframes. + DashPatternInterpolator(std::vector>> const &keyframeGroups) { + for (const auto &keyframeGroup : keyframeGroups) { + _keyframeInterpolators.push_back(std::make_shared>(keyframeGroup)); + } + } + + virtual ~DashPatternInterpolator() = default; + + virtual AnyValue::Type valueType() const override { + return AnyValueType::type(); + } + + virtual DashPattern value(AnimationFrameTime frame) override { + std::vector values; + for (const auto &interpolator : _keyframeInterpolators) { + values.push_back(interpolator->value(frame).value); + } + return DashPattern(std::move(values)); + } + + virtual bool hasUpdate(float frame) const override { + for (const auto &interpolator : _keyframeInterpolators) { + if (interpolator->hasUpdate(frame)) { + return true; + } + } + return false; + } + +private: + std::vector>> _keyframeInterpolators; +}; + +} + +#endif /* DashPatternInterpolator_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.cpp new file mode 100644 index 00000000000..8ec23647627 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.cpp @@ -0,0 +1,5 @@ +#include "KeyframeInterpolator.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp new file mode 100644 index 00000000000..de35d4887aa --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp @@ -0,0 +1,452 @@ +#ifndef KeyframeInterpolator_hpp +#define KeyframeInterpolator_hpp + +#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp" + +namespace lottie { + +/// A value provider that produces a value at Time from a group of keyframes +template +class KeyframeInterpolator: public ValueProvider, public std::enable_shared_from_this> { +public: + KeyframeInterpolator(std::vector> const &keyframes_) : + keyframes(keyframes_) { + assert(!keyframes.empty()); + } + + virtual ~KeyframeInterpolator() { + } + +public: + std::vector> keyframes; + + virtual AnyValue::Type valueType() const override { + return AnyValueType::type(); + } + + virtual T value(AnimationFrameTime frame) override { + // First set the keyframe span for the frame. + updateSpanIndices(frame); + lastUpdatedFrame = frame; + // If only one keyframe return its value + + if (leadingKeyframe.has_value() && + trailingKeyframe.has_value()) + { + /// We have leading and trailing keyframe. + auto progress = leadingKeyframe->interpolatedProgress(trailingKeyframe.value(), frame); + return leadingKeyframe->interpolate(trailingKeyframe.value(), progress); + } else if (leadingKeyframe.has_value()) { + return leadingKeyframe->value; + } else if (trailingKeyframe.has_value()) { + return trailingKeyframe->value; + } else { + /// Satisfy the compiler. + return keyframes[0].value; + } + } + + /// Returns true to trigger a frame update for this interpolator. + /// + /// An interpolator will be asked if it needs to update every frame. + /// If the interpolator needs updating it will be asked to compute its value for + /// the given frame. + /// + /// Cases a keyframe should not be updated: + /// - If time is in span and leading keyframe is hold + /// - If time is after the last keyframe. + /// - If time is before the first keyframe + /// + /// Cases for updating a keyframe: + /// - If time is in the span, and is not a hold + /// - If time is outside of the span, and there are more keyframes + /// - If a value delegate is set + /// - If leading and trailing are both nil. + virtual bool hasUpdate(float frame) const override { + if (!lastUpdatedFrame.has_value()) { + return true; + } + + if (leadingKeyframe.has_value() && + !trailingKeyframe.has_value() && + leadingKeyframe->time < frame) + { + /// Frame is after bounds of keyframes + return false; + } + if (trailingKeyframe.has_value() && + !leadingKeyframe.has_value() && + frame < trailingKeyframe->time) + { + /// Frame is before bounds of keyframes + return false; + } + if (leadingKeyframe.has_value() && + trailingKeyframe.has_value() && + leadingKeyframe->isHold && + leadingKeyframe->time < frame && + frame < trailingKeyframe->time) + { + return false; + } + return true; + } + + // MARK: Fileprivate + + std::optional lastUpdatedFrame; + + std::optional leadingIndex; + std::optional trailingIndex; + std::optional> leadingKeyframe; + std::optional> trailingKeyframe; + + /// Finds the appropriate Leading and Trailing keyframe index for the given time. + void updateSpanIndices(float frame) { + if (keyframes.empty()) { + leadingIndex = std::nullopt; + trailingIndex = std::nullopt; + leadingKeyframe = std::nullopt; + trailingKeyframe = std::nullopt; + return; + } + + // This function searches through the array to find the span of two keyframes + // that contain the current time. + // + // We could use Array.first(where:) but that would search through the entire array + // each frame. + // Instead we track the last used index and search either forwards or + // backwards from there. This reduces the iterations and complexity from + // + // O(n), where n is the length of the sequence to + // O(n), where n is the number of items after or before the last used index. + // + + if (keyframes.size() == 1) { + /// Only one keyframe. Set it as first and move on. + leadingIndex = 0; + trailingIndex = std::nullopt; + leadingKeyframe = keyframes[0]; + trailingKeyframe = std::nullopt; + return; + } + + /// Sets the initial keyframes. This is often only needed for the first check. + if + (!leadingIndex.has_value() && + !trailingIndex.has_value()) + { + if (frame < keyframes[0].time) { + /// Time is before the first keyframe. Set it as the trailing. + trailingIndex = 0; + } else { + /// Time is after the first keyframe. Set the keyframe and the trailing. + leadingIndex = 0; + trailingIndex = 1; + } + } + + if + (trailingIndex.has_value() && + keyframes[trailingIndex.value()].time <= frame) + { + /// Time is after the current span. Iterate forward. + auto newLeading = trailingIndex.value(); + bool keyframeFound = false; + while (!keyframeFound) { + leadingIndex = newLeading; + if (newLeading + 1 >= 0 && newLeading + 1 < keyframes.size()) { + trailingIndex = newLeading + 1; + } else { + trailingIndex = std::nullopt; + } + + if (!trailingIndex.has_value()) { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true; + continue; + } + + if (frame < keyframes[trailingIndex.value()].time) { + /// Keyframe in current span. + keyframeFound = true; + continue; + } + /// Advance the array. + newLeading = trailingIndex.value(); + } + + } else if + (leadingIndex.has_value() && + frame < keyframes[leadingIndex.value()].time) + { + + /// Time is before the current span. Iterate backwards + auto newTrailing = leadingIndex.value(); + + bool keyframeFound = false; + while (!keyframeFound) { + if (newTrailing - 1 >= 0 && newTrailing - 1 < keyframes.size()) { + leadingIndex = newTrailing - 1; + } else { + leadingIndex = std::nullopt; + } + trailingIndex = newTrailing; + + if (!leadingIndex.has_value()) { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true; + continue; + } + if (keyframes[leadingIndex.value()].time <= frame) { + /// Keyframe in current span. + keyframeFound = true; + continue; + } + /// Step back + newTrailing = leadingIndex.value(); + } + } + if (const auto keyFrame = leadingIndex) { + leadingKeyframe = keyframes[keyFrame.value()]; + } else { + leadingKeyframe = std::nullopt; + } + + if (const auto keyFrame = trailingIndex) { + trailingKeyframe = keyframes[keyFrame.value()]; + } else { + trailingKeyframe = std::nullopt; + } + } +}; + +class BezierPathKeyframeInterpolator { +public: + BezierPathKeyframeInterpolator(std::vector> const &keyframes_) : + keyframes(keyframes_) { + assert(!keyframes.empty()); + } + +public: + std::vector> keyframes; + + void update(AnimationFrameTime frame, BezierPath &outPath) { + // First set the keyframe span for the frame. + updateSpanIndices(frame); + lastUpdatedFrame = frame; + // If only one keyframe return its value + + if (leadingKeyframe.has_value() && + trailingKeyframe.has_value()) + { + /// We have leading and trailing keyframe. + auto progress = leadingKeyframe->interpolatedProgress(trailingKeyframe.value(), frame); + interpolateInplace(leadingKeyframe.value(), trailingKeyframe.value(), progress, outPath); + } else if (leadingKeyframe.has_value()) { + setInplace(leadingKeyframe.value(), outPath); + } else if (trailingKeyframe.has_value()) { + setInplace(trailingKeyframe.value(), outPath); + } else { + /// Satisfy the compiler. + setInplace(keyframes[0], outPath); + } + } + + /// Returns true to trigger a frame update for this interpolator. + /// + /// An interpolator will be asked if it needs to update every frame. + /// If the interpolator needs updating it will be asked to compute its value for + /// the given frame. + /// + /// Cases a keyframe should not be updated: + /// - If time is in span and leading keyframe is hold + /// - If time is after the last keyframe. + /// - If time is before the first keyframe + /// + /// Cases for updating a keyframe: + /// - If time is in the span, and is not a hold + /// - If time is outside of the span, and there are more keyframes + /// - If a value delegate is set + /// - If leading and trailing are both nil. + bool hasUpdate(float frame) const { + if (!lastUpdatedFrame.has_value()) { + return true; + } + + if (leadingKeyframe.has_value() && + !trailingKeyframe.has_value() && + leadingKeyframe->time < frame) + { + /// Frame is after bounds of keyframes + return false; + } + if (trailingKeyframe.has_value() && + !leadingKeyframe.has_value() && + frame < trailingKeyframe->time) + { + /// Frame is before bounds of keyframes + return false; + } + if (leadingKeyframe.has_value() && + trailingKeyframe.has_value() && + leadingKeyframe->isHold && + leadingKeyframe->time < frame && + frame < trailingKeyframe->time) + { + return false; + } + return true; + } + + // MARK: Fileprivate + + std::optional lastUpdatedFrame; + + std::optional leadingIndex; + std::optional trailingIndex; + std::optional> leadingKeyframe; + std::optional> trailingKeyframe; + + /// Finds the appropriate Leading and Trailing keyframe index for the given time. + void updateSpanIndices(float frame) { + if (keyframes.empty()) { + leadingIndex = std::nullopt; + trailingIndex = std::nullopt; + leadingKeyframe = std::nullopt; + trailingKeyframe = std::nullopt; + return; + } + + // This function searches through the array to find the span of two keyframes + // that contain the current time. + // + // We could use Array.first(where:) but that would search through the entire array + // each frame. + // Instead we track the last used index and search either forwards or + // backwards from there. This reduces the iterations and complexity from + // + // O(n), where n is the length of the sequence to + // O(n), where n is the number of items after or before the last used index. + // + + if (keyframes.size() == 1) { + /// Only one keyframe. Set it as first and move on. + leadingIndex = 0; + trailingIndex = std::nullopt; + leadingKeyframe = keyframes[0]; + trailingKeyframe = std::nullopt; + return; + } + + /// Sets the initial keyframes. This is often only needed for the first check. + if + (!leadingIndex.has_value() && + !trailingIndex.has_value()) + { + if (frame < keyframes[0].time) { + /// Time is before the first keyframe. Set it as the trailing. + trailingIndex = 0; + } else { + /// Time is after the first keyframe. Set the keyframe and the trailing. + leadingIndex = 0; + trailingIndex = 1; + } + } + + if + (trailingIndex.has_value() && + keyframes[trailingIndex.value()].time <= frame) + { + /// Time is after the current span. Iterate forward. + auto newLeading = trailingIndex.value(); + bool keyframeFound = false; + while (!keyframeFound) { + leadingIndex = newLeading; + if (newLeading + 1 >= 0 && newLeading + 1 < keyframes.size()) { + trailingIndex = newLeading + 1; + } else { + trailingIndex = std::nullopt; + } + + if (!trailingIndex.has_value()) { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true; + continue; + } + + if (frame < keyframes[trailingIndex.value()].time) { + /// Keyframe in current span. + keyframeFound = true; + continue; + } + /// Advance the array. + newLeading = trailingIndex.value(); + } + + } else if + (leadingIndex.has_value() && + frame < keyframes[leadingIndex.value()].time) + { + + /// Time is before the current span. Iterate backwards + auto newTrailing = leadingIndex.value(); + + bool keyframeFound = false; + while (!keyframeFound) { + if (newTrailing - 1 >= 0 && newTrailing - 1 < keyframes.size()) { + leadingIndex = newTrailing - 1; + } else { + leadingIndex = std::nullopt; + } + trailingIndex = newTrailing; + + if (!leadingIndex.has_value()) { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true; + continue; + } + if (keyframes[leadingIndex.value()].time <= frame) { + /// Keyframe in current span. + keyframeFound = true; + continue; + } + /// Step back + newTrailing = leadingIndex.value(); + } + } + if (const auto keyFrame = leadingIndex) { + leadingKeyframe = keyframes[keyFrame.value()]; + } else { + leadingKeyframe = std::nullopt; + } + + if (const auto keyFrame = trailingIndex) { + trailingKeyframe = keyframes[keyFrame.value()]; + } else { + trailingKeyframe = std::nullopt; + } + } + +private: + void setInplace(Keyframe const &from, BezierPath &outPath) { + ValueInterpolator::setInplace(from.value, outPath); + } + + void interpolateInplace(Keyframe const &from, Keyframe const &to, float progress, BezierPath &outPath) { + std::optional spatialOutTangent2d; + if (from.spatialOutTangent) { + spatialOutTangent2d = Vector2D(from.spatialOutTangent->x, from.spatialOutTangent->y); + } + std::optional spatialInTangent2d; + if (to.spatialInTangent) { + spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y); + } + ValueInterpolator::interpolateInplace(from.value, to.value, progress, spatialOutTangent2d, spatialInTangent2d, outPath); + } +}; + +} + +#endif /* KeyframeInterpolator_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.cpp new file mode 100644 index 00000000000..face3214996 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.cpp @@ -0,0 +1,5 @@ +#include "SingleValueProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp new file mode 100644 index 00000000000..b6c3fe6a1f8 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp @@ -0,0 +1,42 @@ +#ifndef SingleValueProvider_hpp +#define SingleValueProvider_hpp + +#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp" + +namespace lottie { + +/// Returns a value for every frame. +template +class SingleValueProvider: public ValueProvider { +public: + SingleValueProvider(T const &value) : + _value(value) { + } + + virtual ~SingleValueProvider() = default; + + void setValue(T const &value) { + _value = value; + _hasUpdate = true; + } + + virtual T value(AnimationFrameTime frame) override { + return _value; + } + + virtual AnyValue::Type valueType() const override { + return AnyValueType::type(); + } + + virtual bool hasUpdate(float frame) const override { + return _hasUpdate; + } + +private: + T _value; + bool _hasUpdate = true; +}; + +} + +#endif /* SingleValueProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp new file mode 100644 index 00000000000..f7b73a550df --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp @@ -0,0 +1,72 @@ +#ifndef PassThroughOutputNode_hpp +#define PassThroughOutputNode_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" + +namespace lottie { + +class PassThroughOutputNode: virtual public NodeOutput, virtual public HasRenderUpdates, virtual public HasUpdate { +public: + PassThroughOutputNode(std::shared_ptr parent) : + _parent(parent) { + } + + virtual ~PassThroughOutputNode() = default; + + virtual std::shared_ptr parent() override { + return _parent; + } + + virtual bool isEnabled() const override { + return _isEnabled; + } + virtual void setIsEnabled(bool isEnabled) override { + _isEnabled = isEnabled; + } + + virtual bool hasUpdate() override { + return _hasUpdate; + } + void setHasUpdate(bool hasUpdate) { + _hasUpdate = hasUpdate; + } + + virtual std::shared_ptr outputPath() override { + if (_parent) { + return _parent->outputPath(); + } + return nullptr; + } + + virtual bool hasOutputUpdates(float forFrame) override { + /// Changes to this node do not affect downstream nodes. + bool parentUpdate = false; + if (_parent) { + parentUpdate = _parent->hasOutputUpdates(forFrame); + } + /// Changes to upstream nodes do, however, affect this nodes state. + _hasUpdate = _hasUpdate || parentUpdate; + return parentUpdate; + } + + virtual bool hasRenderUpdates(float forFrame) override { + /// Return true if there are upstream updates or if this node has updates + bool upstreamUpdates = false; + if (_parent) { + upstreamUpdates = _parent->hasOutputUpdates(forFrame); + } + _hasUpdate = _hasUpdate || upstreamUpdates; + return _hasUpdate; + } + +private: + std::shared_ptr _parent; + bool _hasUpdate = false; + bool _isEnabled = true; +}; + +} + +#endif /* PassThroughOutputNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp new file mode 100644 index 00000000000..731a81149c2 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp @@ -0,0 +1,36 @@ +#ifndef StrokeNode_hpp +#define StrokeNode_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp" +#include "Lottie/Private/Model/ShapeItems/Stroke.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp" + +namespace lottie { + +class StrokeShapeDashConfiguration { +public: + StrokeShapeDashConfiguration(std::vector const &elements) { + /// Converts the `[DashElement]` data model into `lineDashPattern` and `lineDashPhase` + /// representations usable in a `CAShapeLayer` + for (const auto &dash : elements) { + if (dash.type == DashElementType::Offset) { + dashPhase = dash.value.keyframes; + } else { + dashPatterns.push_back(dash.value.keyframes); + } + } + } + +public: + std::vector>> dashPatterns; + std::vector> dashPhase; +}; + +} + +#endif /* StrokeNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp new file mode 100644 index 00000000000..6e8b534b484 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp @@ -0,0 +1,367 @@ +#ifndef TextAnimatorNode_hpp +#define TextAnimatorNode_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp" +#include "Lottie/Private/Model/Text/TextAnimator.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" + +namespace lottie { + +class TextAnimatorNodeProperties: public KeypathSearchableNodePropertyMap { +public: + TextAnimatorNodeProperties(std::shared_ptr const &textAnimator) { + _keypathName = textAnimator->name.value_or(""); + + if (textAnimator->anchor) { + _anchor = std::make_shared>(std::make_shared>(textAnimator->anchor->keyframes)); + _keypathProperties.insert(std::make_pair("Anchor", _anchor)); + } + + if (textAnimator->position) { + _position = std::make_shared>(std::make_shared>(textAnimator->position->keyframes)); + _keypathProperties.insert(std::make_pair("Position", _position)); + } + + if (textAnimator->scale) { + _scale = std::make_shared>(std::make_shared>(textAnimator->scale->keyframes)); + _keypathProperties.insert(std::make_pair("Scale", _scale)); + } + + if (textAnimator->skew) { + _skew = std::make_shared>(std::make_shared>(textAnimator->skew->keyframes)); + _keypathProperties.insert(std::make_pair("Skew", _skew)); + } + + if (textAnimator->skewAxis) { + _skewAxis = std::make_shared>(std::make_shared>(textAnimator->skewAxis->keyframes)); + _keypathProperties.insert(std::make_pair("Skew Axis", _skewAxis)); + } + + if (textAnimator->rotation) { + _rotation = std::make_shared>(std::make_shared>(textAnimator->rotation->keyframes)); + _keypathProperties.insert(std::make_pair("Rotation", _rotation)); + } + + if (textAnimator->rotation) { + _opacity = std::make_shared>(std::make_shared>(textAnimator->opacity->keyframes)); + _keypathProperties.insert(std::make_pair("Opacity", _opacity)); + } + + if (textAnimator->strokeColor) { + _strokeColor = std::make_shared>(std::make_shared>(textAnimator->strokeColor->keyframes)); + _keypathProperties.insert(std::make_pair("Stroke Color", _strokeColor)); + } + + if (textAnimator->fillColor) { + _fillColor = std::make_shared>(std::make_shared>(textAnimator->fillColor->keyframes)); + _keypathProperties.insert(std::make_pair("Fill Color", _fillColor)); + } + + if (textAnimator->strokeWidth) { + _strokeWidth = std::make_shared>(std::make_shared>(textAnimator->strokeWidth->keyframes)); + _keypathProperties.insert(std::make_pair("Stroke Width", _strokeWidth)); + } + + if (textAnimator->tracking) { + _tracking = std::make_shared>(std::make_shared>(textAnimator->tracking->keyframes)); + _keypathProperties.insert(std::make_pair("Tracking", _tracking)); + } + + for (const auto &it : _keypathProperties) { + _properties.push_back(it.second); + } + } + + virtual ~TextAnimatorNodeProperties() = default; + + virtual std::string keypathName() const override { + return _keypathName; + } + + virtual std::map> keypathProperties() const override { + return _keypathProperties; + } + + virtual std::vector> &properties() override { + return _properties; + } + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + Transform2D caTransform() { + Vector2D anchor = Vector2D::Zero(); + if (_anchor) { + auto anchor3d = _anchor->value(); + anchor = Vector2D(anchor3d.x, anchor3d.y); + } + + Vector2D position = Vector2D::Zero(); + if (_position) { + auto position3d = _position->value(); + position = Vector2D(position3d.x, position3d.y); + } + + Vector2D scale = Vector2D(100.0, 100.0); + if (_scale) { + auto scale3d = _scale->value(); + scale = Vector2D(scale3d.x, scale3d.y); + } + + float rotation = 0.0; + if (_rotation) { + rotation = _rotation->value().value; + } + + std::optional skew; + if (_skew) { + skew = _skew->value().value; + } + std::optional skewAxis; + if (_skewAxis) { + skewAxis = _skewAxis->value().value; + } + + return Transform2D::makeTransform( + anchor, + position, + scale, + rotation, + skew, + skewAxis + ); + } + + virtual std::shared_ptr keypathLayer() const override { + return nullptr; + } + + float opacity() { + if (_opacity) { + return _opacity->value().value; + } else { + return 100.0; + } + } + + std::optional strokeColor() { + if (_strokeColor) { + return _strokeColor->value(); + } else { + return std::nullopt; + } + } + + std::optional fillColor() { + if (_fillColor) { + return _fillColor->value(); + } else { + return std::nullopt; + } + } + + float tracking() { + if (_tracking) { + return _tracking->value().value; + } else { + return 1.0; + } + } + + float strokeWidth() { + if (_strokeWidth) { + return _strokeWidth->value().value; + } else { + return 0.0; + } + } + +private: + std::string _keypathName; + + std::shared_ptr> _anchor; + std::shared_ptr> _position; + std::shared_ptr> _scale; + std::shared_ptr> _skew; + std::shared_ptr> _skewAxis; + std::shared_ptr> _rotation; + std::shared_ptr> _opacity; + std::shared_ptr> _strokeColor; + std::shared_ptr> _fillColor; + std::shared_ptr> _strokeWidth; + std::shared_ptr> _tracking; + + std::map> _keypathProperties; + std::vector> _childKeypaths; + std::vector> _properties; +}; + +class TextOutputNode: virtual public NodeOutput { +public: + TextOutputNode(std::shared_ptr parent) : + _parentTextNode(parent) { + } + + virtual ~TextOutputNode() = default; + + virtual std::shared_ptr parent() override { + return _parentTextNode; + } + + Transform2D xform() { + if (_xform.has_value()) { + return _xform.value(); + } else if (_parentTextNode) { + return _parentTextNode->xform(); + } else { + return Transform2D::identity(); + } + } + void setXform(Transform2D const &xform) { + _xform = xform; + } + + float opacity() { + if (_opacity.has_value()) { + return _opacity.value(); + } else if (_parentTextNode) { + return _parentTextNode->opacity(); + } else { + return 1.0; + } + } + void setOpacity(float opacity) { + _opacity = opacity; + } + + std::optional strokeColor() { + if (_strokeColor.has_value()) { + return _strokeColor.value(); + } else if (_parentTextNode) { + return _parentTextNode->strokeColor(); + } else { + return std::nullopt; + } + } + void setStrokeColor(std::optional strokeColor) { + _strokeColor = strokeColor; + } + + std::optional fillColor() { + if (_fillColor.has_value()) { + return _fillColor.value(); + } else if (_parentTextNode) { + return _parentTextNode->fillColor(); + } else { + return std::nullopt; + } + } + void setFillColor(std::optional fillColor) { + _fillColor = fillColor; + } + + float tracking() { + if (_tracking.has_value()) { + return _tracking.value(); + } else if (_parentTextNode) { + return _parentTextNode->tracking(); + } else { + return 0.0; + } + } + void setTracking(float tracking) { + _tracking = tracking; + } + + float strokeWidth() { + if (_strokeWidth.has_value()) { + return _strokeWidth.value(); + } else if (_parentTextNode) { + return _parentTextNode->strokeWidth(); + } else { + return 0.0; + } + } + void setStrokeWidth(float strokeWidth) { + _strokeWidth = strokeWidth; + } + + virtual bool hasOutputUpdates(float frame) override { + // TODO Fix This + return true; + } + + virtual std::shared_ptr outputPath() override { + return _outputPath; + } + + virtual bool isEnabled() const override { + return _isEnabled; + } + virtual void setIsEnabled(bool isEnabled) override { + _isEnabled = isEnabled; + } + +private: + std::shared_ptr _parentTextNode; + bool _isEnabled = true; + + std::shared_ptr _outputPath; + + std::optional _xform; + std::optional _opacity; + std::optional _strokeColor; + std::optional _fillColor; + std::optional _tracking; + std::optional _strokeWidth; +}; + +class TextAnimatorNode: public AnimatorNode { +public: + TextAnimatorNode(std::shared_ptr const &parentNode, std::shared_ptr const &textAnimator) : + AnimatorNode(parentNode) { + std::shared_ptr parentOutputNode; + if (parentNode) { + parentOutputNode = parentNode->_textOutputNode; + } + _textOutputNode = std::make_shared(parentOutputNode); + + _textAnimatorProperties = std::make_shared(textAnimator); + } + + virtual ~TextAnimatorNode() = default; + + virtual std::shared_ptr outputNode() override { + return _textOutputNode; + } + + virtual std::shared_ptr propertyMap() const override { + return _textAnimatorProperties; + } + + virtual bool localUpdatesPermeateDownstream() override { + return true; + } + + virtual void rebuildOutputs(float frame) override { + _textOutputNode->setXform(_textAnimatorProperties->caTransform()); + _textOutputNode->setOpacity(((float)_textAnimatorProperties->opacity()) * 0.01f); + _textOutputNode->setStrokeColor(_textAnimatorProperties->strokeColor()); + _textOutputNode->setFillColor(_textAnimatorProperties->fillColor()); + _textOutputNode->setTracking(_textAnimatorProperties->tracking()); + _textOutputNode->setStrokeWidth(_textAnimatorProperties->strokeWidth()); + } + +private: + std::shared_ptr _textOutputNode; + + std::shared_ptr _textAnimatorProperties; +}; + +} + +#endif /* TextAnimatorNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp new file mode 100644 index 00000000000..249c0556656 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp @@ -0,0 +1,238 @@ +#ifndef AnimatorNode_hpp +#define AnimatorNode_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" + +#include +#include + +namespace lottie { + +class LayerTransformNode; +class PathNode; +class RenderNode; + +/// The Animator Node is the base node in the render system tree. +/// +/// It defines a single node that has an output path and option input node. +/// At animation time the root animation node is asked to update its contents for +/// the current frame. +/// The node reaches up its chain of nodes until the first node that does not need +/// updating is found. Then each node updates its contents down the render pipeline. +/// Each node adds its local path to its input path and passes it forward. +/// +/// An animator node holds a group of interpolators. These interpolators determine +/// if the node needs an update for the current frame. +/// +class AnimatorNode: public KeypathSearchable { +public: + AnimatorNode(std::shared_ptr const &parentNode) : + _parentNode(parentNode) { + } + + AnimatorNode(const AnimatorNode&) = delete; + AnimatorNode& operator=(AnimatorNode&) = delete; + + /// The available properties of the Node. + /// + /// These properties are automatically updated each frame. + /// These properties are also settable and gettable through the dynamic + /// property system. + /// + virtual std::shared_ptr propertyMap() const = 0; + + /// The upstream input node + std::shared_ptr parentNode() { + return _parentNode; + } + void setParentNode(std::shared_ptr const &parentNode) { + _parentNode = parentNode; + } + + /// The output of the node. + virtual std::shared_ptr outputNode() = 0; + + /// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true. + virtual void rebuildOutputs(float frame) = 0; + + /// Setters for marking current node state. + bool isEnabled() { + return _isEnabled; + } + virtual void setIsEnabled(bool isEnabled) { + _isEnabled = isEnabled; + } + + bool hasLocalUpdates() { + return _hasLocalUpdates; + } + virtual void setHasLocalUpdates(bool hasLocalUpdates) { + _hasLocalUpdates = hasLocalUpdates; + } + + bool hasUpstreamUpdates() { + return _hasUpstreamUpdates; + } + virtual void setHasUpstreamUpdates(bool hasUpstreamUpdates) { + _hasUpstreamUpdates = hasUpstreamUpdates; + } + + std::optional lastUpdateFrame() { + return _lastUpdateFrame; + } + virtual void setLastUpdateFrame(std::optional lastUpdateFrame) { + _lastUpdateFrame = lastUpdateFrame; + } + + /// Marks if updates to this node affect nodes downstream. + virtual bool localUpdatesPermeateDownstream() { + /// Optional override + return true; + } + virtual bool forceUpstreamOutputUpdates() { + /// Optional + return false; + } + + /// Called at the end of this nodes update cycle. Always called. Optional. + virtual bool performAdditionalLocalUpdates(float frame, bool forceLocalUpdate) { + /// Optional + return forceLocalUpdate; + } + virtual void performAdditionalOutputUpdates(float frame, bool forceOutputUpdate) { + /// Optional + } + + /// The default simply returns `hasLocalUpdates` + virtual bool shouldRebuildOutputs(float frame) { + return hasLocalUpdates(); + } + + virtual bool updateOutputs(float frame, bool forceOutputUpdate) { + if (!isEnabled()) { + setLastUpdateFrame(frame); + if (const auto parentNodeValue = parentNode()) { + return parentNodeValue->updateOutputs(frame, forceOutputUpdate); + } else { + return false; + } + } + + if (!forceOutputUpdate && lastUpdateFrame().has_value() && lastUpdateFrame().value() == frame) { + /// This node has already updated for this frame. Go ahead and return the results. + return hasUpstreamUpdates() || hasLocalUpdates(); + } + + /// Ask if this node should force output updates upstream. + bool forceUpstreamUpdates = forceOutputUpdate || forceUpstreamOutputUpdates(); + + /// Perform upstream output updates. Optionally mark upstream updates if any. + if (const auto parentNodeValue = parentNode()) { + setHasUpstreamUpdates(parentNodeValue->updateOutputs(frame, forceUpstreamUpdates) || hasUpstreamUpdates()); + } else { + setHasUpstreamUpdates(hasUpstreamUpdates()); + } + + /// Perform additional local output updates + performAdditionalOutputUpdates(frame, forceUpstreamUpdates); + + /// If there are local updates, or if updates have been force, rebuild outputs + if (forceUpstreamUpdates || shouldRebuildOutputs(frame)) { + setLastUpdateFrame(frame); + rebuildOutputs(frame); + } + return hasUpstreamUpdates() || hasLocalUpdates(); + } + + /// Rebuilds the content of this node, and upstream nodes if necessary. + virtual bool updateContents(float frame, bool forceLocalUpdate) { + if (!isEnabled()) { + // Disabled node, pass through. + if (const auto parentNodeValue = parentNode()) { + return parentNodeValue->updateContents(frame, forceLocalUpdate); + } else { + return false; + } + } + + if (forceLocalUpdate == false && lastUpdateFrame().has_value() && lastUpdateFrame().value() == frame) { + /// This node has already updated for this frame. Go ahead and return the results. + return localUpdatesPermeateDownstream() ? hasUpstreamUpdates() || hasLocalUpdates() : hasUpstreamUpdates(); + } + + /// Are there local updates? If so mark the node. + setHasLocalUpdates(forceLocalUpdate ? forceLocalUpdate : propertyMap()->needsLocalUpdate(frame)); + + /// Were there upstream updates? If so mark the node + if (const auto parentNodeValue = parentNode()) { + setHasUpstreamUpdates(parentNodeValue->updateContents(frame, forceLocalUpdate)); + } else { + setHasUpstreamUpdates(false); + } + + /// Perform property updates if necessary. + if (hasLocalUpdates()) { + /// Rebuild local properties + propertyMap()->updateNodeProperties(frame); + } + + /// Ask the node to perform any other updates it might have. + setHasUpstreamUpdates(performAdditionalLocalUpdates(frame, forceLocalUpdate) || hasUpstreamUpdates()); + + /// If the node can update nodes downstream, notify them, otherwise pass on any upstream updates downstream. + return localUpdatesPermeateDownstream() ? hasUpstreamUpdates() || hasLocalUpdates() : hasUpstreamUpdates(); + } + + bool updateTree(float frame, bool forceUpdates) { + if (updateContents(frame, forceUpdates)) { + return updateOutputs(frame, forceUpdates); + } else { + return false; + } + } + + /// The name of the Keypath + virtual std::string keypathName() const override { + return propertyMap()->keypathName(); + } + + /// A list of properties belonging to the keypath. + virtual std::map> keypathProperties() const override { + return propertyMap()->keypathProperties(); + } + + /// Children Keypaths + virtual std::vector> const &childKeypaths() const override { + return propertyMap()->childKeypaths(); + } + + virtual std::shared_ptr keypathLayer() const override { + return nullptr; + } + +public: + virtual LayerTransformNode *asLayerTransformNode() { + return nullptr; + } + + virtual PathNode *asPathNode() { + return nullptr; + } + + virtual RenderNode *asRenderNode() { + return nullptr; + } + +private: + std::shared_ptr _parentNode; + bool _isEnabled = true; + bool _hasLocalUpdates = false; + bool _hasUpstreamUpdates = false; + std::optional _lastUpdateFrame; +}; + +} + +#endif /* AnimatorNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp new file mode 100644 index 00000000000..69d5dd81d5a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp @@ -0,0 +1,28 @@ +#ifndef NodeOutput_hpp +#define NodeOutput_hpp + +#include + +#include + +namespace lottie { + +/// Defines the basic outputs of an animator node. +/// +class NodeOutput { +public: + /// The parent node. + virtual std::shared_ptr parent() = 0; + + /// Returns true if there are any updates upstream. OutputPath must be built before returning. + virtual bool hasOutputUpdates(float forFrame) = 0; + + virtual std::shared_ptr outputPath() = 0; + + virtual bool isEnabled() const = 0; + virtual void setIsEnabled(bool isEnabled) = 0; +}; + +} + +#endif /* NodeOutput_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp new file mode 100644 index 00000000000..5db444d408c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp @@ -0,0 +1,73 @@ +#ifndef RenderNode_hpp +#define RenderNode_hpp + +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp" + +namespace lottie { + +class StrokeRenderer; +class FillRenderer; +class GradientStrokeRenderer; +class GradientFillRenderer; + +/// A protocol that defines anything with render instructions +class Renderable: virtual public HasRenderUpdates, virtual public HasUpdate { +public: + enum RenderableType { + Fill, + Stroke, + GradientFill, + GradientStroke + }; + +public: + /// Determines if the renderer requires a custom context for drawing. + /// If yes the shape layer will perform a custom drawing pass. + /// If no the shape layer will be a standard CAShapeLayer + virtual bool shouldRenderInContext() = 0; + + /// Passes in the CAShapeLayer to update + virtual void updateShapeLayer(std::shared_ptr const &layer) = 0; + + /// Asks the renderer what the renderable bounds is for the given box. + virtual CGRect renderBoundsFor(CGRect const &boundingBox) { + /// Optional + return boundingBox; + } + + /// Opportunity for renderers to inject sublayers + virtual void setupSublayers(std::shared_ptr const &layer) = 0; + + virtual RenderableType renderableType() const = 0; + + virtual StrokeRenderer *asStrokeRenderer() { + return nullptr; + } + + virtual FillRenderer *asFillRenderer() { + return nullptr; + } + + virtual GradientStrokeRenderer *asGradientStrokeRenderer() { + return nullptr; + } + + virtual GradientFillRenderer *asGradientFillRenderer() { + return nullptr; + } +}; + +/// A protocol that defines a node that holds render instructions +class RenderNode { +public: + virtual std::shared_ptr renderer() = 0; + virtual std::shared_ptr nodeOutput() = 0; +}; + +} + +#endif /* RenderNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp new file mode 100644 index 00000000000..a1ec1b2bed6 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp @@ -0,0 +1,99 @@ +#include "GetGradientParameters.hpp" + +namespace lottie { + +void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector &outColors, std::vector &outLocations) { + std::vector alphaColors; + std::vector alphaValues; + std::vector alphaLocations; + + std::vector gradientColors; + std::vector colorLocations; + + for (int i = 0; i < numberOfColors; i++) { + int ix = i * 4; + if (colors.colors.size() > ix) { + Color color( + colors.colors[ix + 1], + colors.colors[ix + 2], + colors.colors[ix + 3], + 1 + ); + gradientColors.push_back(color); + colorLocations.push_back(colors.colors[ix]); + } + } + + bool drawMask = false; + for (int i = numberOfColors * 4; i < (int)colors.colors.size(); i += 2) { + float alpha = colors.colors[i + 1]; + if (alpha < 1.0) { + drawMask = true; + } + alphaLocations.push_back(colors.colors[i]); + alphaColors.push_back(Color(alpha, alpha, alpha, 1.0)); + alphaValues.push_back(alpha); + } + + if (drawMask) { + std::vector locations; + for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()); i++) { + if (std::find(locations.begin(), locations.end(), colorLocations[i]) == locations.end()) { + locations.push_back(colorLocations[i]); + } + } + for (size_t i = 0; i < std::min(alphaValues.size(), alphaLocations.size()); i++) { + if (std::find(locations.begin(), locations.end(), alphaLocations[i]) == locations.end()) { + locations.push_back(alphaLocations[i]); + } + } + + std::sort(locations.begin(), locations.end()); + if (locations[0] != 0.0) { + locations.insert(locations.begin(), 0.0); + } + if (locations[locations.size() - 1] != 1.0) { + locations.push_back(1.0); + } + + std::vector colors; + + for (const auto location : locations) { + Color color = gradientColors[0]; + for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()) - 1; i++) { + if (location >= colorLocations[i] && location <= colorLocations[i + 1]) { + float localLocation = 0.0; + if (colorLocations[i] != colorLocations[i + 1]) { + localLocation = remapFloat(location, colorLocations[i], colorLocations[i + 1], 0.0, 1.0); + } + color = ValueInterpolator::interpolate(gradientColors[i], gradientColors[i + 1], localLocation, std::nullopt, std::nullopt); + break; + } + } + + float alpha = 1.0; + for (size_t i = 0; i < std::min(alphaValues.size(), alphaLocations.size()) - 1; i++) { + if (location >= alphaLocations[i] && location <= alphaLocations[i + 1]) { + float localLocation = 0.0; + if (alphaLocations[i] != alphaLocations[i + 1]) { + localLocation = remapFloat(location, alphaLocations[i], alphaLocations[i + 1], 0.0, 1.0); + } + alpha = ValueInterpolator::interpolate(alphaValues[i], alphaValues[i + 1], localLocation, std::nullopt, std::nullopt); + break; + } + } + + color.a = alpha; + + colors.push_back(color); + } + + gradientColors = colors; + colorLocations = locations; + } + + outColors = gradientColors; + outLocations = colorLocations; +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp new file mode 100644 index 00000000000..8319b8505bc --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp @@ -0,0 +1,13 @@ +#ifndef ShapeRenderLayer_hpp +#define ShapeRenderLayer_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" + +namespace lottie { + +void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector &outColors, std::vector &outLocations); + +} + +#endif /* ShapeRenderLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.cpp new file mode 100644 index 00000000000..577a56be354 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.cpp @@ -0,0 +1,5 @@ +#include "Animation.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp new file mode 100644 index 00000000000..ffeb67964bf --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp @@ -0,0 +1,314 @@ +#ifndef Animation_hpp +#define Animation_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Private/Utility/Primitives/CoordinateSpace.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Text/Glyph.hpp" +#include "Lottie/Private/Model/Text/Font.hpp" +#include "Lottie/Private/Model/Objects/Marker.hpp" +#include "Lottie/Private/Model/Assets/AssetLibrary.hpp" +#include "Lottie/Private/Model/Objects/FitzModifier.hpp" + +#include +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Private/Model/Layers/LayerModelSerialization.hpp" + +#include +#include +#include +#include + +namespace lottie { + +/// The `Animation` model is the top level model object in Lottie. +/// +/// An `Animation` holds all of the animation data backing a Lottie Animation. +/// Codable, see JSON schema [here](https://github.com/airbnb/lottie-web/tree/master/docs/json). +class Animation { +public: + Animation( + std::optional name_, + std::optional tgs_, + AnimationFrameTime startFrame_, + AnimationFrameTime endFrame_, + float framerate_, + std::string const &version_, + std::optional type_, + int width_, + int height_, + std::vector> const &layers_, + std::optional>> glyphs_, + std::optional> fonts_, + std::shared_ptr assetLibrary_, + std::optional> markers_, + std::optional> fitzModifiers_, + std::optional meta_, + std::optional comps_ + ) : + startFrame(startFrame_), + endFrame(endFrame_), + framerate(framerate_), + name(name_), + version(version_), + tgs(tgs_), + type(type_), + width(width_), + height(height_), + layers(layers_), + glyphs(glyphs_), + fonts(fonts_), + assetLibrary(assetLibrary_), + markers(markers_), + fitzModifiers(fitzModifiers_), + meta(meta_), + comps(comps_) { + if (markers) { + std::map parsedMarkerMap; + for (const auto &marker : markers.value()) { + parsedMarkerMap.insert(std::make_pair(marker.name, marker)); + } + markerMap = std::move(parsedMarkerMap); + } + } + + Animation(const Animation&) = delete; + Animation& operator=(Animation&) = delete; + + static std::shared_ptr fromJson(lottiejson11::Json::object const &json) noexcept(false) { + auto name = getOptionalString(json, "nm"); + auto version = getString(json, "v"); + + auto tgs = getOptionalInt(json, "tgs"); + + std::optional type; + if (const auto typeRawValue = getOptionalInt(json, "ddd")) { + if (typeRawValue.value() == 0) { + type = CoordinateSpace::Type2d; + } else { + type = CoordinateSpace::Type3d; + } + } + + AnimationFrameTime startFrame = (float)getDouble(json, "ip"); + AnimationFrameTime endFrame = (float)getDouble(json, "op"); + + float framerate = (float)getDouble(json, "fr"); + + int width = getInt(json, "w"); + int height = getInt(json, "h"); + + auto layerDictionaries = getObjectArray(json, "layers"); + std::vector> layers; + for (size_t i = 0; i < layerDictionaries.size(); i++) { + try { + auto layer = parseLayerModel(layerDictionaries[i]); + layers.push_back(layer); + } catch(...) { + throw LottieParsingException(); + } + } + + std::optional>> glyphs; + if (const auto glyphDictionaries = getOptionalObjectArray(json, "chars")) { + glyphs = std::vector>(); + for (const auto &glyphDictionary : glyphDictionaries.value()) { + glyphs->push_back(std::make_shared(glyphDictionary)); + } + } else { + glyphs = std::nullopt; + } + + std::optional> fonts; + if (const auto fontsDictionary = getOptionalObject(json, "fonts")) { + fonts = std::make_shared(fontsDictionary.value()); + } + + std::shared_ptr assetLibrary; + if (const auto assetLibraryData = getOptionalAny(json, "assets")) { + assetLibrary = std::make_shared(assetLibraryData.value()); + } + + std::optional> markers; + if (const auto markerDictionaries = getOptionalObjectArray(json, "markers")) { + markers = std::vector(); + for (const auto &markerDictionary : markerDictionaries.value()) { + markers->push_back(Marker(markerDictionary)); + } + } + std::optional> fitzModifiers; + if (const auto fitzModifierDictionaries = getOptionalObjectArray(json, "fitz")) { + fitzModifiers = std::vector(); + for (const auto &fitzModifierDictionary : fitzModifierDictionaries.value()) { + fitzModifiers->push_back(FitzModifier(fitzModifierDictionary)); + } + } + + auto meta = getOptionalAny(json, "meta"); + auto comps = getOptionalAny(json, "comps"); + + return std::make_shared( + name, + tgs, + startFrame, + endFrame, + framerate, + version, + type, + width, + height, + std::move(layers), + std::move(glyphs), + std::move(fonts), + assetLibrary, + std::move(markers), + fitzModifiers, + meta, + comps + ); + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + + result.insert(std::make_pair("v", lottiejson11::Json(version))); + + if (tgs.has_value()) { + result.insert(std::make_pair("tgs", tgs.value())); + } + + if (type.has_value()) { + switch (type.value()) { + case CoordinateSpace::Type2d: + result.insert(std::make_pair("ddd", lottiejson11::Json(0))); + break; + case CoordinateSpace::Type3d: + result.insert(std::make_pair("ddd", lottiejson11::Json(1))); + break; + } + } + + result.insert(std::make_pair("ip", lottiejson11::Json(startFrame))); + result.insert(std::make_pair("op", lottiejson11::Json(endFrame))); + result.insert(std::make_pair("fr", lottiejson11::Json(framerate))); + result.insert(std::make_pair("w", lottiejson11::Json(width))); + result.insert(std::make_pair("h", lottiejson11::Json(height))); + + lottiejson11::Json::array layersArray; + for (const auto &layer : layers) { + lottiejson11::Json::object layerJson; + layer->toJson(layerJson); + layersArray.push_back(layerJson); + } + result.insert(std::make_pair("layers", lottiejson11::Json(layersArray))); + + if (glyphs.has_value()) { + lottiejson11::Json::array glyphArray; + for (const auto &glyph : glyphs.value()) { + glyphArray.push_back(glyph->toJson()); + } + result.insert(std::make_pair("chars", lottiejson11::Json(glyphArray))); + } + + if (fonts.has_value()) { + result.insert(std::make_pair("fonts", fonts.value()->toJson())); + } + + if (assetLibrary) { + result.insert(std::make_pair("assets", assetLibrary->toJson())); + } + + if (markers.has_value()) { + lottiejson11::Json::array markerArray; + for (const auto &marker : markers.value()) { + markerArray.push_back(marker.toJson()); + } + result.insert(std::make_pair("markers", lottiejson11::Json(markerArray))); + } + + if (fitzModifiers.has_value()) { + lottiejson11::Json::array fitzModifierArray; + for (const auto &fitzModifier : fitzModifiers.value()) { + fitzModifierArray.push_back(fitzModifier.toJson()); + } + result.insert(std::make_pair("fitz", lottiejson11::Json(fitzModifierArray))); + } + + if (meta.has_value()) { + result.insert(std::make_pair("meta", meta.value())); + } + if (comps.has_value()) { + result.insert(std::make_pair("comps", comps.value())); + } + + return result; + } + +public: + /// The start time of the composition in frameTime. + AnimationFrameTime startFrame; + + /// The end time of the composition in frameTime. + AnimationFrameTime endFrame; + + /// The frame rate of the composition. + float framerate; + + /// Return all marker names, in order, or an empty list if none are specified + std::vector markerNames() { + if (!markers.has_value()) { + return {}; + } + std::vector result; + for (const auto &marker : markers.value()) { + result.push_back(marker.name); + } + return result; + } + + /// Animation name + std::optional name; + + /// The version of the JSON Schema. + std::string version; + + std::optional tgs; + + /// The coordinate space of the composition. + std::optional type; + + /// The height of the composition in points. + int width; + + /// The width of the composition in points. + int height; + + /// The list of animation layers + std::vector> layers; + + /// The list of glyphs used for text rendering + std::optional>> glyphs; + + /// The list of fonts used for text rendering + std::optional> fonts; + + /// Asset Library + std::shared_ptr assetLibrary; + + /// Markers + std::optional> markers; + std::optional> markerMap; + + std::optional> fitzModifiers; + + std::optional meta; + std::optional comps; +}; + +} + +#endif /* Animation_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.cpp new file mode 100644 index 00000000000..12f67cdd8aa --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.cpp @@ -0,0 +1,5 @@ +#include "Asset.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.hpp new file mode 100644 index 00000000000..dabec9756cb --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.hpp @@ -0,0 +1,50 @@ +#ifndef Asset_hpp +#define Asset_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class Asset { +public: + Asset(std::string id_) : + id(id_) { + } + + explicit Asset(lottiejson11::Json::object const &json) noexcept(false) { + auto idData = getAny(json, "id"); + if (idData.is_string()) { + id = idData.string_value(); + } else if (idData.is_number()) { + std::ostringstream idString; + idString << idData.int_value(); + id = idString.str(); + } + + objectName = getOptionalString(json, "nm"); + } + + Asset(const Asset&) = delete; + Asset& operator=(Asset&) = delete; + + virtual void toJson(lottiejson11::Json::object &json) const { + json.insert(std::make_pair("id", id)); + + if (objectName.has_value()) { + json.insert(std::make_pair("nm", objectName.value())); + } + } + +public: + /// The ID of the asset + std::string id; + + std::optional objectName; +}; + +} + +#endif /* Asset_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.cpp new file mode 100644 index 00000000000..7feff3231eb --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.cpp @@ -0,0 +1,5 @@ +#include "AssetLibrary.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.hpp new file mode 100644 index 00000000000..5e7b893d025 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.hpp @@ -0,0 +1,71 @@ +#ifndef AssetLibrary_hpp +#define AssetLibrary_hpp + +#include "Lottie/Private/Model/Assets/Asset.hpp" +#include "Lottie/Private/Model/Assets/ImageAsset.hpp" +#include "Lottie/Private/Model/Assets/PrecompAsset.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class AssetLibrary { +public: + AssetLibrary( + std::map> const &assets_, + std::map> const &imageAssets_, + std::map> const &precompAssets_ + ) : + assets(assets_), + imageAssets(imageAssets_), + precompAssets(precompAssets_) { + } + + explicit AssetLibrary(lottiejson11::Json const &json) noexcept(false) { + if (!json.is_array()) { + throw LottieParsingException(); + } + + for (const auto &item : json.array_items()) { + if (!item.is_object()) { + throw LottieParsingException(); + } + if (item.object_items().find("layers") != item.object_items().end()) { + auto asset = std::make_shared(item.object_items()); + assets.insert(std::make_pair(asset->id, asset)); + assetList.push_back(asset); + precompAssets.insert(std::make_pair(asset->id, asset)); + } else { + auto asset = std::make_shared(item.object_items()); + assets.insert(std::make_pair(asset->id, asset)); + assetList.push_back(asset); + imageAssets.insert(std::make_pair(asset->id, asset)); + } + } + } + + lottiejson11::Json::array toJson() const { + lottiejson11::Json::array result; + + for (const auto &asset : assetList) { + lottiejson11::Json::object assetJson; + asset->toJson(assetJson); + result.push_back(assetJson); + } + + return result; + } + +public: + /// The Assets + std::vector> assetList; + std::map> assets; + + std::map> imageAssets; + std::map> precompAssets; +}; + +} + +#endif /* AssetLibrary_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.cpp new file mode 100644 index 00000000000..b5a2e57d524 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.cpp @@ -0,0 +1,5 @@ +#include "ImageAsset.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp new file mode 100644 index 00000000000..5eea900309e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp @@ -0,0 +1,125 @@ +#ifndef ImageAsset_hpp +#define ImageAsset_hpp + +#include "Lottie/Private/Model/Assets/Asset.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +class Image { +public: + Image() { + } +}; + +class ImageAsset: public Asset { +public: + ImageAsset( + std::string id_, + std::string name_, + std::string directory_, + float width_, + float height_ + ) : Asset(id_), + name(name_), + directory(directory_), + width(width_), + height(height_) { + } + + virtual ~ImageAsset() = default; + + explicit ImageAsset(lottiejson11::Json::object const &json) noexcept(false) : + Asset(json) { + name = getString(json, "p"); + directory = getString(json, "u"); + width = (float)getDouble(json, "w"); + height = (float)getDouble(json, "h"); + + _e = getOptionalInt(json, "e"); + _t = getOptionalString(json, "t"); + } + + virtual void toJson(lottiejson11::Json::object &json) const override { + Asset::toJson(json); + + json.insert(std::make_pair("p", name)); + json.insert(std::make_pair("u", directory)); + json.insert(std::make_pair("w", width)); + json.insert(std::make_pair("h", height)); + + if (_e.has_value()) { + json.insert(std::make_pair("e", _e.value())); + } + if (_t.has_value()) { + json.insert(std::make_pair("t", _t.value())); + } + } + +public: + /// Image name + std::string name; + + /// Image Directory + std::string directory; + + /// Image Size + float width; + float height; + + std::optional _e; + std::optional _t; +}; + +/*extension Data { + + // MARK: Lifecycle + + /// Initializes `Data` from an `ImageAsset`. + /// + /// Returns nil when the input is not recognized as valid Data URL. + /// - parameter imageAsset: The image asset that contains Data URL. + internal init?(imageAsset: ImageAsset) { + self.init(dataString: imageAsset.name) + } + + /// Initializes `Data` from a [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) String. + /// + /// Returns nil when the input is not recognized as valid Data URL. + /// - parameter dataString: The data string to parse. + /// - parameter options: Options for the string parsing. Default value is `[]`. + internal init?(dataString: String, options: DataURLReadOptions = []) { + guard + dataString.hasPrefix("data:"), + let url = URL(string: dataString) + else { + return nil + } + // The code below is needed because Data(contentsOf:) floods logs + // with messages since url doesn't have a host. This only fixes flooding logs + // when data inside Data URL is base64 encoded. + if + let base64Range = dataString.range(of: ";base64,"), + !options.contains(DataURLReadOptions.legacy) + { + let encodedString = String(dataString[base64Range.upperBound...]) + self.init(base64Encoded: encodedString) + } else { + try? self.init(contentsOf: url) + } + } + + // MARK: Internal + + internal struct DataURLReadOptions: OptionSet { + let rawValue: Int + + /// Will read Data URL using Data(contentsOf:) + static let legacy = DataURLReadOptions(rawValue: 1 << 0) + } + +};*/ + +} + +#endif /* ImageAsset_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.cpp new file mode 100644 index 00000000000..976044565aa --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.cpp @@ -0,0 +1,5 @@ +#include "PrecompAsset.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp new file mode 100644 index 00000000000..af169474a7b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp @@ -0,0 +1,66 @@ +#ifndef PrecompAsset_hpp +#define PrecompAsset_hpp + +#include "Lottie/Private/Model/Assets/Asset.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Layers/LayerModelSerialization.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class PrecompAsset: public Asset { +public: + PrecompAsset( + std::string const &id_, + std::vector> const &layers_ + ) : Asset(id_), + layers(layers_) { + } + + virtual ~PrecompAsset() = default; + + explicit PrecompAsset(lottiejson11::Json::object const &json) noexcept(false) : + Asset(json) { + if (const auto frameRateValue = getOptionalDouble(json, "fr")) { + frameRate = (float)frameRateValue.value(); + } + + auto layerDictionaries = getObjectArray(json, "layers"); + for (size_t i = 0; i < layerDictionaries.size(); i++) { + try { + auto layer = parseLayerModel(layerDictionaries[i]); + layers.push_back(layer); + } catch(...) { + throw LottieParsingException(); + } + } + } + + virtual void toJson(lottiejson11::Json::object &json) const override { + Asset::toJson(json); + + lottiejson11::Json::array layerArray; + for (const auto &layer : layers) { + lottiejson11::Json::object layerJson; + layer->toJson(layerJson); + layerArray.push_back(layerJson); + } + json.insert(std::make_pair("layers", layerArray)); + + if (frameRate.has_value()) { + json.insert(std::make_pair("fr", frameRate.value())); + } + } + +public: + /// Layers of the precomp + std::vector> layers; + + std::optional frameRate; +}; + +} + +#endif /* PrecompAsset_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.cpp new file mode 100644 index 00000000000..2f0c1a788a7 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.cpp @@ -0,0 +1,5 @@ +#include "KeyframeGroup.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.hpp new file mode 100644 index 00000000000..50549a1121c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.hpp @@ -0,0 +1,150 @@ +#ifndef KeyframeGroup_hpp +#define KeyframeGroup_hpp + +#include "Lottie/Public/Keyframes/Keyframe.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// Used for coding/decoding a group of Keyframes by type. +/// +/// Keyframe data is wrapped in a dictionary { "k" : KeyframeData }. +/// The keyframe data can either be an array of keyframes or, if no animation is present, the raw value. +/// This helper object is needed to properly decode the json. + +template +class KeyframeGroup { +public: + KeyframeGroup(std::vector> &&keyframes_) : + keyframes(std::move(keyframes_)), + isSingle(false) { + } + + KeyframeGroup(T const &value_) : + keyframes({ Keyframe(value_, std::nullopt, std::nullopt) }), + isSingle(false) { + } + + KeyframeGroup(lottiejson11::Json::object const &json) noexcept(false) { + isAnimated = getOptionalInt(json, "a"); + expression = getOptionalAny(json, "x"); + expressionIndex = getOptionalInt(json, "ix"); + _extraL = getOptionalInt(json, "l"); + + auto containerData = getAny(json, "k"); + + try { + LottieParsingException::Guard expectedException; + T keyframeData = T(containerData); + keyframes.push_back(Keyframe(keyframeData, std::nullopt, std::nullopt)); + isSingle = true; + } catch(...) { + // Decode and array of keyframes. + // + // Body Movin and Lottie deal with keyframes in different ways. + // + // A keyframe object in Body movin defines a span of time with a START + // and an END, from the current keyframe time to the next keyframe time. + // + // A keyframe object in Lottie defines a singular point in time/space. + // This point has an in-tangent and an out-tangent. + // + // To properly decode this we must iterate through keyframes while holding + // reference to the previous keyframe. + + if (!containerData.is_array()) { + throw LottieParsingException(); + } + + std::optional> previousKeyframeData; + for (const auto &containerItem : containerData.array_items()) { + // Ensure that Time and Value are present. + auto keyframeData = KeyframeData(containerItem); + rawKeyframeData.push_back(keyframeData); + + std::optional value; + if (keyframeData.startValue.has_value()) { + value = keyframeData.startValue; + } else if (previousKeyframeData.has_value()) { + value = previousKeyframeData->endValue; + } + if (!value.has_value()) { + throw LottieParsingException(); + } + if (!keyframeData.time.has_value()) { + throw LottieParsingException(); + } + + std::optional inTangent; + std::optional spatialInTangent; + if (previousKeyframeData.has_value()) { + inTangent = previousKeyframeData->inTangent; + spatialInTangent = previousKeyframeData->spatialInTangent; + } + + keyframes.emplace_back( + value.value(), + keyframeData.time.value(), + keyframeData.isHold(), + inTangent, + keyframeData.outTangent, + spatialInTangent, + keyframeData.spatialOutTangent + ); + + previousKeyframeData = keyframeData; + } + + isSingle = false; + } + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + assert(!keyframes.empty()); + + if (keyframes.size() == 1 && isSingle) { + result.insert(std::make_pair("k", keyframes[0].value.toJson())); + } else { + lottiejson11::Json::array containerData; + + for (const auto &keyframe : rawKeyframeData) { + containerData.push_back(keyframe.toJson()); + } + + result.insert(std::make_pair("k", containerData)); + } + + if (isAnimated.has_value()) { + result.insert(std::make_pair("a", isAnimated.value())); + } + if (expression.has_value()) { + result.insert(std::make_pair("x", expression.value())); + } + if (expressionIndex.has_value()) { + result.insert(std::make_pair("ix", expressionIndex.value())); + } + if (_extraL.has_value()) { + result.insert(std::make_pair("l", _extraL.value())); + } + + return result; + } + +public: + std::vector> keyframes; + std::optional isAnimated; + + std::optional expression; + std::optional expressionIndex; + std::vector> rawKeyframeData; + bool isSingle = false; + std::optional _extraL; +}; + +} + +#endif /* KeyframeGroup_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.cpp new file mode 100644 index 00000000000..4251973760c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.cpp @@ -0,0 +1,5 @@ +#include "ImageLayerModel.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.hpp new file mode 100644 index 00000000000..23349f73904 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.hpp @@ -0,0 +1,40 @@ +#ifndef ImageLayerModel_hpp +#define ImageLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// A layer that holds an image. +class ImageLayerModel: public LayerModel { +public: + explicit ImageLayerModel(lottiejson11::Json::object const &json) noexcept(false) : + LayerModel(json) { + referenceID = getString(json, "refId"); + + _sc = getOptionalString(json, "sc"); + } + + virtual ~ImageLayerModel() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + LayerModel::toJson(json); + + json.insert(std::make_pair("refId", referenceID)); + + if (_sc.has_value()) { + json.insert(std::make_pair("sc", _sc.value())); + } + } + +public: + /// The reference ID of the image. + std::string referenceID; + + std::optional _sc; +}; + +} + +#endif /* ImageLayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.cpp new file mode 100644 index 00000000000..f14f1df9b8c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.cpp @@ -0,0 +1,45 @@ +#include "LayerModel.hpp" + +namespace lottie { + +LayerType parseLayerType(lottiejson11::Json::object const &json, std::string const &key) { + if (const auto layerTypeValue = getOptionalInt(json, "ty")) { + switch (layerTypeValue.value()) { + case 0: + return LayerType::Precomp; + case 1: + return LayerType::Solid; + case 2: + return LayerType::Image; + case 3: + return LayerType::Null; + case 4: + return LayerType::Shape; + case 5: + return LayerType::Text; + default: + return LayerType::Null; + } + } else { + return LayerType::Null; + } +} + +int serializeLayerType(LayerType value) { + switch (value) { + case LayerType::Precomp: + return 0; + case LayerType::Solid: + return 1; + case LayerType::Image: + return 2; + case LayerType::Null: + return 3; + case LayerType::Shape: + return 4; + case LayerType::Text: + return 5; + } +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp new file mode 100644 index 00000000000..16f84d0f10f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp @@ -0,0 +1,318 @@ +#ifndef LayerModel_hpp +#define LayerModel_hpp + +#include "Lottie/Private/Model/Objects/Transform.hpp" +#include "Lottie/Private/Model/Objects/Mask.hpp" +#include "Lottie/Private/Utility/Primitives/CoordinateSpace.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include +#include + +namespace lottie { + +enum class LayerType { + Precomp, + Solid, + Image, + Null, + Shape, + Text +}; + +LayerType parseLayerType(lottiejson11::Json::object const &json, std::string const &key); +int serializeLayerType(LayerType value); + +enum class MatteType: int { + None = 0, + Add = 1, + Invert = 2, + Unknown = 3 +}; + +enum class BlendMode: int { + Normal = 0, + Multiply = 1, + Screen = 2, + Overlay = 3, + Darken = 4, + Lighten = 5, + ColorDodge = 6, + ColorBurn = 7, + HardLight = 8, + SoftLight = 9, + Difference = 10, + Exclusion = 11, + Hue = 12, + Saturation = 13, + Color = 14, + Luminosity = 15 +}; + +/// A base top container for shapes, images, and other view objects. +class LayerModel { +public: + explicit LayerModel(lottiejson11::Json::object const &json) noexcept(false) { + name = getOptionalString(json, "nm"); + index = getOptionalInt(json, "ind"); + + type = parseLayerType(json, "ty"); + + autoOrient = getOptionalInt(json, "ao"); + + if (const auto typeRawValue = getOptionalInt(json, "ddd")) { + if (typeRawValue.value() == 0) { + coordinateSpace = CoordinateSpace::Type2d; + } else { + coordinateSpace = CoordinateSpace::Type3d; + } + } else { + coordinateSpace = std::nullopt; + } + + inFrame = (float)getDouble(json, "ip"); + outFrame = (float)getDouble(json, "op"); + startTime = (float)getDouble(json, "st"); + + transform = std::make_shared(getObject(json, "ks")); + parent = getOptionalInt(json, "parent"); + + if (const auto blendModeRawValue = getOptionalInt(json, "bm")) { + switch (blendModeRawValue.value()) { + case 0: + blendMode = BlendMode::Normal; + break; + case 1: + blendMode = BlendMode::Multiply; + break; + case 2: + blendMode = BlendMode::Screen; + break; + case 3: + blendMode = BlendMode::Overlay; + break; + case 4: + blendMode = BlendMode::Darken; + break; + case 5: + blendMode = BlendMode::Lighten; + break; + case 6: + blendMode = BlendMode::ColorDodge; + break; + case 7: + blendMode = BlendMode::ColorBurn; + break; + case 8: + blendMode = BlendMode::HardLight; + break; + case 9: + blendMode = BlendMode::SoftLight; + break; + case 10: + blendMode = BlendMode::Difference; + break; + case 11: + blendMode = BlendMode::Exclusion; + break; + case 12: + blendMode = BlendMode::Hue; + break; + case 13: + blendMode = BlendMode::Saturation; + break; + case 14: + blendMode = BlendMode::Color; + break; + case 15: + blendMode = BlendMode::Luminosity; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto maskDictionaries = getOptionalObjectArray(json, "masksProperties")) { + masks = std::vector>(); + for (const auto &maskDictionary : maskDictionaries.value()) { + masks->push_back(std::make_shared(maskDictionary)); + } + } + + if (const auto timeStretchData = getOptionalDouble(json, "sr")) { + _timeStretch = (float)timeStretchData.value(); + } + + if (const auto matteRawValue = getOptionalInt(json, "tt")) { + switch (matteRawValue.value()) { + case 0: + matte = MatteType::None; + break; + case 1: + matte = MatteType::Add; + break; + case 2: + matte = MatteType::Invert; + break; + case 3: + matte = MatteType::Unknown; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto hiddenData = getOptionalBool(json, "hd")) { + hidden = hiddenData.value(); + } + + hasMask = getOptionalBool(json, "hasMask"); + td = getOptionalInt(json, "td"); + effectsData = getOptionalAny(json, "ef"); + layerClass = getOptionalString(json, "cl"); + _extraHidden = getOptionalAny(json, "hidden"); + } + + LayerModel(const LayerModel&) = delete; + LayerModel& operator=(LayerModel&) = delete; + + virtual ~LayerModel() = default; + + virtual void toJson(lottiejson11::Json::object &json) const { + if (name.has_value()) { + json.insert(std::make_pair("nm", name.value())); + } + if (index.has_value()) { + json.insert(std::make_pair("ind", index.value())); + } + + if (autoOrient.has_value()) { + json.insert(std::make_pair("ao", autoOrient.value())); + } + + json.insert(std::make_pair("ty", serializeLayerType(type))); + + if (coordinateSpace.has_value()) { + switch (coordinateSpace.value()) { + case CoordinateSpace::Type2d: + json.insert(std::make_pair("ddd", 0)); + break; + case CoordinateSpace::Type3d: + json.insert(std::make_pair("ddd", 1)); + break; + } + } + + json.insert(std::make_pair("ip", inFrame)); + json.insert(std::make_pair("op", outFrame)); + json.insert(std::make_pair("st", startTime)); + + json.insert(std::make_pair("ks", transform->toJson())); + + if (parent.has_value()) { + json.insert(std::make_pair("parent", parent.value())); + } + + if (blendMode.has_value()) { + json.insert(std::make_pair("bm", (int)blendMode.value())); + } + + if (masks.has_value()) { + lottiejson11::Json::array maskArray; + for (const auto &mask : masks.value()) { + maskArray.push_back(mask->toJson()); + } + json.insert(std::make_pair("masksProperties", maskArray)); + } + + if (_timeStretch.has_value()) { + json.insert(std::make_pair("sr", _timeStretch.value())); + } + + if (matte.has_value()) { + json.insert(std::make_pair("tt", (int)matte.value())); + } + + if (hidden.has_value()) { + json.insert(std::make_pair("hd", hidden.value())); + } + + if (hasMask.has_value()) { + json.insert(std::make_pair("hasMask", hasMask.value())); + } + if (td.has_value()) { + json.insert(std::make_pair("td", td.value())); + } + if (effectsData.has_value()) { + json.insert(std::make_pair("ef", effectsData.value())); + } + if (layerClass.has_value()) { + json.insert(std::make_pair("cl", layerClass.value())); + } + if (_extraHidden.has_value()) { + json.insert(std::make_pair("hidden", _extraHidden.value())); + } + } + + float timeStretch() { + if (_timeStretch.has_value()) { + return _timeStretch.value(); + } else { + return 1.0; + } + } + +public: + /// The readable name of the layer + std::optional name; + + /// The index of the layer + std::optional index; + + /// The type of the layer. + LayerType type; + + std::optional autoOrient; + + /// The coordinate space + std::optional coordinateSpace; + + /// The in time of the layer in frames. + float inFrame; + /// The out time of the layer in frames. + float outFrame; + + /// The start time of the layer in frames. + float startTime; + + /// The transform of the layer + std::shared_ptr transform; + + /// The index of the parent layer, if applicable. + std::optional parent; + + /// The blending mode for the layer + std::optional blendMode; + + /// An array of masks for the layer. + std::optional>> masks; + + /// A number that stretches time by a multiplier + std::optional _timeStretch; + + /// The type of matte if any. + std::optional matte; + + std::optional hidden; + + std::optional hasMask; + std::optional td; + std::optional effectsData; + std::optional layerClass; + std::optional _extraHidden; +}; + +} + +#endif /* LayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.cpp new file mode 100644 index 00000000000..47bb5b1467a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.cpp @@ -0,0 +1,34 @@ +#include "LayerModelSerialization.hpp" + +#include "Lottie/Private/Model/Layers/PreCompLayerModel.hpp" +#include "Lottie/Private/Model/Layers/SolidLayerModel.hpp" +#include "Lottie/Private/Model/Layers/ImageLayerModel.hpp" +#include "Lottie/Private/Model/Layers/ShapeLayerModel.hpp" +#include "Lottie/Private/Model/Layers/TextLayerModel.hpp" + +namespace lottie { + +std::shared_ptr parseLayerModel(lottiejson11::Json::object const &json) noexcept(false) { + LayerType layerType = parseLayerType(json, "ty"); + + switch (layerType) { + case LayerType::Precomp: + return std::make_shared(json); + case LayerType::Solid: + return std::make_shared(json); + case LayerType::Image: + return std::make_shared(json); + case LayerType::Null: + return std::make_shared(json); + case LayerType::Shape: + try { + return std::make_shared(json); + } catch(...) { + throw LottieParsingException(); + } + case LayerType::Text: + return std::make_shared(json); + } +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.hpp new file mode 100644 index 00000000000..cc2f9ee012e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.hpp @@ -0,0 +1,14 @@ +#ifndef LayerModelSerialization_hpp +#define LayerModelSerialization_hpp + +#include +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" + +namespace lottie { + +std::shared_ptr parseLayerModel(lottiejson11::Json::object const &json) noexcept(false); + +} + +#endif /* LayerModelSerialization_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.cpp new file mode 100644 index 00000000000..2a4c7b1363a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.cpp @@ -0,0 +1,5 @@ +#include "PreCompLayerModel.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp new file mode 100644 index 00000000000..16a2195a0ba --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp @@ -0,0 +1,57 @@ +#ifndef PreCompLayerModel_hpp +#define PreCompLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// A layer that holds another animation composition. +class PreCompLayerModel: public LayerModel { +public: + PreCompLayerModel(lottiejson11::Json::object const &json) : + LayerModel(json) { + referenceID = getString(json, "refId"); + if (const auto timeRemappingData = getOptionalObject(json, "tm")) { + timeRemapping = KeyframeGroup(timeRemappingData.value()); + } + width = (float)getDouble(json, "w"); + height = (float)getDouble(json, "h"); + } + + virtual ~PreCompLayerModel() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + LayerModel::toJson(json); + + json.insert(std::make_pair("refId", referenceID)); + + if (timeRemapping.has_value()) { + json.insert(std::make_pair("tm", timeRemapping->toJson())); + } + + json.insert(std::make_pair("w", width)); + json.insert(std::make_pair("h", height)); + } + +public: + /// The reference ID of the precomp. + std::string referenceID; + + /// A value that remaps time over time. + std::optional> timeRemapping; + + /// Precomp Width + float width; + + /// Precomp Height + float height; +}; + +} + +#endif /* PreCompLayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.cpp new file mode 100644 index 00000000000..608dca567bf --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.cpp @@ -0,0 +1,5 @@ +#include "ShapeLayerModel.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.hpp new file mode 100644 index 00000000000..61f4329bbaa --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.hpp @@ -0,0 +1,45 @@ +#ifndef ShapeLayerModel_hpp +#define ShapeLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// A layer that holds vector shape objects. +class ShapeLayerModel: public LayerModel { +public: + ShapeLayerModel(lottiejson11::Json::object const &json) noexcept(false) : + LayerModel(json) { + auto shapeItemsData = getObjectArray(json, "shapes"); + for (const auto &shapeItemData : shapeItemsData) { + items.push_back(parseShapeItem(shapeItemData)); + } + } + + virtual ~ShapeLayerModel() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + LayerModel::toJson(json); + + lottiejson11::Json::array shapeItemArray; + for (const auto &item : items) { + lottiejson11::Json::object itemJson; + item->toJson(itemJson); + shapeItemArray.push_back(itemJson); + } + + json.insert(std::make_pair("shapes", shapeItemArray)); + } + +public: + /// A list of shape items. + std::vector> items; +}; + +} + +#endif /* ShapeLayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.cpp new file mode 100644 index 00000000000..153c2839532 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.cpp @@ -0,0 +1,5 @@ +#include "SolidLayerModel.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp new file mode 100644 index 00000000000..cea0671b49d --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp @@ -0,0 +1,42 @@ +#ifndef SolidLayerModel_hpp +#define SolidLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// A layer that holds a solid color. +class SolidLayerModel: public LayerModel { +public: + explicit SolidLayerModel(lottiejson11::Json::object const &json) noexcept(false) : + LayerModel(json) { + colorHex = getString(json, "sc"); + width = (float)getDouble(json, "sw"); + height = (float)getDouble(json, "sh"); + } + + virtual ~SolidLayerModel() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + LayerModel::toJson(json); + + json.insert(std::make_pair("sc", colorHex)); + json.insert(std::make_pair("sw", width)); + json.insert(std::make_pair("sh", height)); + } + +public: + /// The color of the solid in Hex // Change to value provider. + std::string colorHex; + + /// The Width of the color layer + float width; + + /// The height of the color layer + float height; +}; + +} + +#endif /* SolidLayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.cpp new file mode 100644 index 00000000000..9e2ad034b15 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.cpp @@ -0,0 +1,5 @@ +#include "TextLayerModel.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.hpp new file mode 100644 index 00000000000..37fb374ddf6 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.hpp @@ -0,0 +1,83 @@ +#ifndef TextLayerModel_hpp +#define TextLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/Text/TextDocument.hpp" +#include "Lottie/Private/Model/Text/TextAnimator.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// A layer that holds text. +class TextLayerModel: public LayerModel { +public: + TextLayerModel(lottiejson11::Json::object const &json) : + LayerModel(json), + text(KeyframeGroup(TextDocument( + "", + 0.0, + "", + TextJustification::Left, + 0, + 0.0, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt + ))) { + auto textContainer = getObject(json, "t"); + + auto textData = getObject(textContainer, "d"); + text = KeyframeGroup(textData); + + if (auto animatorsData = getOptionalObjectArray(textContainer, "a")) { + for (const auto &animatorData : animatorsData.value()) { + animators.push_back(std::make_shared(animatorData)); + } + } + + _extraM = getOptionalAny(textContainer, "m"); + _extraP = getOptionalAny(textContainer, "p"); + } + + virtual ~TextLayerModel() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + LayerModel::toJson(json); + + lottiejson11::Json::object textContainer; + textContainer.insert(std::make_pair("d", text.toJson())); + if (_extraM.has_value()) { + textContainer.insert(std::make_pair("m", _extraM.value())); + } + if (_extraP.has_value()) { + textContainer.insert(std::make_pair("p", _extraP.value())); + } + lottiejson11::Json::array animatorArray; + for (const auto &animator : animators) { + animatorArray.push_back(animator->toJson()); + } + textContainer.insert(std::make_pair("a", animatorArray)); + + json.insert(std::make_pair("t", textContainer)); + } + +public: + /// The text for the layer + KeyframeGroup text; + + /// Text animators + std::vector> animators; + + std::optional _extraM; + std::optional _extraP; + std::optional _extraA; +}; + +} + +#endif /* TextLayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.cpp new file mode 100644 index 00000000000..a28df197702 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.cpp @@ -0,0 +1,5 @@ +#include "DashElement.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.hpp new file mode 100644 index 00000000000..87379ea7224 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.hpp @@ -0,0 +1,78 @@ +#ifndef DashElement_hpp +#define DashElement_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Primitives/DashPattern.hpp" + +namespace lottie { + +enum class DashElementType { + Offset, + Dash, + Gap +}; + +class DashElement { +public: + DashElement( + DashElementType type_, + KeyframeGroup const &value_ + ) : + type(type_), + value(value_) { + } + + explicit DashElement(lottiejson11::Json::object const &json) noexcept(false) : + type(DashElementType::Offset), + value(KeyframeGroup(Vector1D(0.0))) { + auto typeRawValue = getString(json, "n"); + if (typeRawValue == "o") { + type = DashElementType::Offset; + } else if (typeRawValue == "d") { + type = DashElementType::Dash; + } else if (typeRawValue == "g") { + type = DashElementType::Gap; + } else { + throw LottieParsingException(); + } + + value = KeyframeGroup(getObject(json, "v")); + + name = getOptionalString(json, "nm"); + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + switch (type) { + case DashElementType::Offset: + result.insert(std::make_pair("n", "o")); + break; + case DashElementType::Dash: + result.insert(std::make_pair("n", "d")); + break; + case DashElementType::Gap: + result.insert(std::make_pair("n", "g")); + break; + } + + result.insert(std::make_pair("v", value.toJson())); + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + + return result; + } + +public: + DashElementType type; + KeyframeGroup value; + + std::optional name; +}; + +} + +#endif /* DashElement_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.cpp new file mode 100644 index 00000000000..7b18e01e4a3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.cpp @@ -0,0 +1,5 @@ +#include "FitzModifier.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp new file mode 100644 index 00000000000..632a2374f93 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp @@ -0,0 +1,53 @@ +#ifndef FitzModifier_hpp +#define FitzModifier_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +class FitzModifier { +public: + explicit FitzModifier(lottiejson11::Json::object const &json) noexcept(false) { + original = getInt(json, "o"); + type12 = getOptionalInt(json, "f12"); + type3 = getOptionalInt(json, "f3"); + type4 = getOptionalInt(json, "f4"); + type5 = getOptionalInt(json, "f5"); + type6 = getOptionalInt(json, "f6"); + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + result.insert(std::make_pair("o", (float)original)); + if (type12.has_value()) { + result.insert(std::make_pair("f12", (float)type12.value())); + } + if (type3.has_value()) { + result.insert(std::make_pair("f3", (float)type3.value())); + } + if (type4.has_value()) { + result.insert(std::make_pair("f4", (float)type4.value())); + } + if (type5.has_value()) { + result.insert(std::make_pair("f5", (float)type5.value())); + } + if (type6.has_value()) { + result.insert(std::make_pair("f6", (float)type6.value())); + } + + return result; + } + +public: + float original; + std::optional type12; + std::optional type3; + std::optional type4; + std::optional type5; + std::optional type6; +}; + +} + +#endif /* FitzModifier_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.cpp new file mode 100644 index 00000000000..ea2664d4162 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.cpp @@ -0,0 +1,5 @@ +#include "Marker.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp new file mode 100644 index 00000000000..bd1e5584dd3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp @@ -0,0 +1,52 @@ +#ifndef Marker_hpp +#define Marker_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class Marker { +public: + Marker( + std::string const &name_, + AnimationFrameTime frameTime_ + ) : + name(name_), + frameTime(frameTime_) { + } + + explicit Marker(lottiejson11::Json::object const &json) noexcept(false) { + name = getString(json, "cm"); + frameTime = (float)getDouble(json, "tm"); + dr = getOptionalInt(json, "dr"); + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + result.insert(std::make_pair("cm", name)); + result.insert(std::make_pair("tm", frameTime)); + + if (dr.has_value()) { + result.insert(std::make_pair("dr", dr.value())); + } + + return result; + } + +public: + /// The Marker Name + std::string name; + + /// The Frame time of the marker + AnimationFrameTime frameTime; + + std::optional dr; +}; + +} + +#endif /* Marker_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.cpp new file mode 100644 index 00000000000..8cb64d67095 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.cpp @@ -0,0 +1,5 @@ +#include "Mask.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp new file mode 100644 index 00000000000..0a4d790684f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp @@ -0,0 +1,139 @@ +#ifndef Mask_hpp +#define Mask_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class MaskMode { + Add, + Subtract, + Intersect, + Lighten, + Darken, + Difference, + None +}; + +class Mask { +public: + explicit Mask(lottiejson11::Json::object const &json) noexcept(false) : + opacity(KeyframeGroup(Vector1D(100.0))), + shape(KeyframeGroup(BezierPath())), + inverted(false), + expansion(KeyframeGroup(Vector1D(0.0))) { + if (const auto modeRawValue = getOptionalString(json, "mode")) { + if (modeRawValue.value() == "a") { + _mode = MaskMode::Add; + } else if (modeRawValue.value() == "s") { + _mode = MaskMode::Subtract; + } else if (modeRawValue.value() == "i") { + _mode = MaskMode::Intersect; + } else if (modeRawValue.value() == "l") { + _mode = MaskMode::Lighten; + } else if (modeRawValue.value() == "d") { + _mode = MaskMode::Darken; + } else if (modeRawValue.value() == "f") { + _mode = MaskMode::Difference; + } else if (modeRawValue.value() == "n") { + _mode = MaskMode::None; + } else { + throw LottieParsingException(); + } + } + + if (const auto opacityData = getOptionalObject(json, "o")) { + opacity = KeyframeGroup(opacityData.value()); + } + + shape = KeyframeGroup(getObject(json, "pt")); + + if (const auto invertedData = getOptionalBool(json, "inv")) { + inverted = invertedData.value(); + } + + if (const auto expansionData = getOptionalObject(json, "x")) { + expansion = KeyframeGroup(expansionData.value()); + } + + name = getOptionalString(json, "nm"); + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + if (_mode.has_value()) { + switch (_mode.value()) { + case MaskMode::Add: + result.insert(std::make_pair("mode", "a")); + break; + case MaskMode::Subtract: + result.insert(std::make_pair("mode", "s")); + break; + case MaskMode::Intersect: + result.insert(std::make_pair("mode", "i")); + break; + case MaskMode::Lighten: + result.insert(std::make_pair("mode", "l")); + break; + case MaskMode::Darken: + result.insert(std::make_pair("mode", "d")); + break; + case MaskMode::Difference: + result.insert(std::make_pair("mode", "f")); + break; + case MaskMode::None: + result.insert(std::make_pair("mode", "n")); + break; + } + } + + if (opacity.has_value()) { + result.insert(std::make_pair("o", opacity->toJson())); + } + + result.insert(std::make_pair("pt", shape.toJson())); + + if (inverted.has_value()) { + result.insert(std::make_pair("inv", inverted.value())); + } + + if (expansion.has_value()) { + result.insert(std::make_pair("x", expansion->toJson())); + } + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + + return result; + } + +public: + MaskMode mode() const { + if (_mode.has_value()) { + return _mode.value(); + } else { + return MaskMode::Add; + } + } + +public: + std::optional _mode; + + std::optional> opacity; + + KeyframeGroup shape; + + std::optional inverted; + + std::optional> expansion; + + std::optional name; +}; + +} + +#endif /* Mask_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.cpp new file mode 100644 index 00000000000..e55154304fc --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.cpp @@ -0,0 +1,5 @@ +#include "Transform.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.hpp new file mode 100644 index 00000000000..19cfc92da83 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.hpp @@ -0,0 +1,267 @@ +#ifndef Transform_hpp +#define Transform_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class Transform { +public: + enum class PositionInternalRepresentation { + None, + TopLevelXY, + TopLevelCombined, + NestedXY + }; + + enum class RotationZInternalRepresentation { + RZ, + R + }; + +public: + Transform( + std::optional> anchorPoint_, + std::optional> position_, + std::optional> positionX_, + std::optional> positionY_, + std::optional> scale_, + std::optional> rotation_, + std::optional> &opacity_, + std::optional> rotationZ_ + ) : + _anchorPoint(anchorPoint_), + _position(position_), + _positionX(positionX_), + _positionY(positionY_), + _scale(scale_), + _rotation(rotation_), + _opacity(opacity_), + _rotationZ(rotationZ_) { + } + + explicit Transform(lottiejson11::Json::object const &json) noexcept(false) { + // AnchorPoint + if (const auto anchorPointDictionary = getOptionalObject(json, "a")) { + _anchorPoint = KeyframeGroup(anchorPointDictionary.value()); + } + + try { + auto xDictionary = getOptionalObject(json, "px"); + auto yDictionary = getOptionalObject(json, "py"); + if (xDictionary.has_value() && yDictionary.has_value()) { + _positionX = KeyframeGroup(xDictionary.value()); + _positionY = KeyframeGroup(yDictionary.value()); + _position = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::TopLevelXY; + } else if (const auto positionData = getOptionalObject(json, "p")) { + try { + LottieParsingException::Guard expectedGuard; + + _position = KeyframeGroup(positionData.value()); + _positionX = std::nullopt; + _positionX = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::TopLevelCombined; + } catch(...) { + auto xData = getObject(positionData.value(), "x"); + auto yData = getObject(positionData.value(), "y"); + _positionX = KeyframeGroup(xData); + _positionY = KeyframeGroup(yData); + _position = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::NestedXY; + _extra_positionS = getOptionalBool(positionData.value(), "s"); + } + } else { + _position = std::nullopt; + _positionX = std::nullopt; + _positionY = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::None; + } + } catch(...) { + throw LottieParsingException(); + } + + try { + // Scale + if (const auto scaleData = getOptionalObject(json, "s")) { + _scale = KeyframeGroup(scaleData.value()); + } + + // Rotation + if (const auto rotationZData = getOptionalObject(json, "rz")) { + _rotationZ = KeyframeGroup(rotationZData.value()); + _rotationZInternalRepresentation = RotationZInternalRepresentation::RZ; + } else if (const auto rotationData = getOptionalObject(json, "r")) { + _rotation = KeyframeGroup(rotationData.value()); + _rotationZInternalRepresentation = RotationZInternalRepresentation::R; + } + + // Opacity + if (const auto opacityData = getOptionalObject(json, "o")) { + _opacity = KeyframeGroup(opacityData.value()); + } + } catch(...) { + throw LottieParsingException(); + } + + _extraTy = getOptionalString(json, "ty"); + _extraSa = getOptionalAny(json, "sa"); + _extraSk = getOptionalAny(json, "sk"); + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + if (_anchorPoint.has_value()) { + result.insert(std::make_pair("a", _anchorPoint->toJson())); + } + + switch (_positionInternalRepresentation) { + case PositionInternalRepresentation::None: + assert(!_positionX.has_value()); + assert(!_positionY.has_value()); + assert(!_position.has_value()); + break; + case PositionInternalRepresentation::TopLevelXY: + assert(_positionX.has_value()); + assert(_positionY.has_value()); + assert(!_position.has_value()); + result.insert(std::make_pair("x", _positionX->toJson())); + result.insert(std::make_pair("y", _positionY->toJson())); + break; + case PositionInternalRepresentation::TopLevelCombined: + assert(_position.has_value()); + assert(!_positionX.has_value()); + assert(!_positionY.has_value()); + result.insert(std::make_pair("p", _position->toJson())); + break; + case PositionInternalRepresentation::NestedXY: + lottiejson11::Json::object nestedPosition; + assert(_positionX.has_value()); + assert(_positionY.has_value()); + assert(!_position.has_value()); + nestedPosition.insert(std::make_pair("x", _positionX->toJson())); + nestedPosition.insert(std::make_pair("y", _positionY->toJson())); + if (_extra_positionS.has_value()) { + nestedPosition.insert(std::make_pair("s", _extra_positionS.value())); + } + result.insert(std::make_pair("p", nestedPosition)); + break; + } + + if (_scale.has_value()) { + result.insert(std::make_pair("s", _scale->toJson())); + } + + if (_rotation.has_value()) { + switch (_rotationZInternalRepresentation) { + case RotationZInternalRepresentation::RZ: + result.insert(std::make_pair("rz", _rotation->toJson())); + break; + case RotationZInternalRepresentation::R: + result.insert(std::make_pair("r", _rotation->toJson())); + break; + } + } + + if (_opacity.has_value()) { + result.insert(std::make_pair("o", _opacity->toJson())); + } + + if (_extraTy.has_value()) { + result.insert(std::make_pair("ty", _extraTy.value())); + } + if (_extraSa.has_value()) { + result.insert(std::make_pair("sa", _extraSa.value())); + } + if (_extraSk.has_value()) { + result.insert(std::make_pair("sk", _extraSk.value())); + } + + return result; + } + + KeyframeGroup anchorPoint() { + if (_anchorPoint.has_value()) { + return _anchorPoint.value(); + } else { + return KeyframeGroup(Vector3D(0.0, 0.0, 0.0)); + } + } + + KeyframeGroup scale() const { + if (_scale) { + return _scale.value(); + } else { + return KeyframeGroup(Vector3D(100.0, 100.0, 100.0)); + } + } + + KeyframeGroup rotation() const { + if (_rotation) { + return _rotation.value(); + } else { + return KeyframeGroup(Vector1D(0.0)); + } + } + + KeyframeGroup opacity() const { + if (_opacity) { + return _opacity.value(); + } else { + return KeyframeGroup(Vector1D(100.0)); + } + } + + std::optional> const &position() const { + return _position; + } + + std::optional> const &positionX() const { + return _positionX; + } + + std::optional> const &positionY() const { + return _positionY; + } + +private: + /// The anchor point of the transform. + std::optional> _anchorPoint; + + /// The position of the transform. This is nil if the position data was split. + std::optional> _position; + + /// The positionX of the transform. This is nil if the position property is set. + std::optional> _positionX; + + /// The positionY of the transform. This is nil if the position property is set. + std::optional> _positionY; + + PositionInternalRepresentation _positionInternalRepresentation = PositionInternalRepresentation::None; + + /// The scale of the transform + std::optional> _scale; + + /// The rotation of the transform. Note: This is single dimensional rotation. + std::optional> _rotation; + + /// The opacity of the transform. + std::optional> _opacity; + + /// Should always be nil. + std::optional> _rotationZ; + RotationZInternalRepresentation _rotationZInternalRepresentation; + + std::optional _extra_positionS; + std::optional _extraTy; + std::optional _extraSa; + std::optional _extraSk; +}; + +} + +#endif /* Transform_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.cpp new file mode 100644 index 00000000000..9378276f24f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.cpp @@ -0,0 +1,5 @@ +#include "Ellipse.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.hpp new file mode 100644 index 00000000000..e1aff804dd4 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.hpp @@ -0,0 +1,64 @@ +#ifndef Ellipse_hpp +#define Ellipse_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class PathDirection: int { + Clockwise = 1, + UserSetClockwise = 2, + CounterClockwise = 3 +}; + +/// An item that define an ellipse shape +class Ellipse: public ShapeItem { +public: + explicit Ellipse(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + size(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + position = KeyframeGroup(getObject(json, "p")); + size = KeyframeGroup(getObject(json, "s")); + } + + virtual ~Ellipse() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + json.insert(std::make_pair("p", position.toJson())); + json.insert(std::make_pair("s", size.toJson())); + } + +public: + std::optional direction; + KeyframeGroup position; + KeyframeGroup size; +}; + +} + +#endif /* Ellipse_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.cpp new file mode 100644 index 00000000000..992a25a5be2 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.cpp @@ -0,0 +1,6 @@ +#include "Fill.hpp" + +namespace lottie { + +} + diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp new file mode 100644 index 00000000000..f8139628a66 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp @@ -0,0 +1,71 @@ +#ifndef Fill_hpp +#define Fill_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#import +#include +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include + +namespace lottie { + +class Fill: public ShapeItem { +public: + explicit Fill(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(0.0))), + color(KeyframeGroup(Color(0.0, 0.0, 0.0, 0.0))) { + opacity = KeyframeGroup(getObject(json, "o")); + color = KeyframeGroup(getObject(json, "c")); + + if (const auto fillRuleRawValue = getOptionalInt(json, "r")) { + switch (fillRuleRawValue.value()) { + case 0: + fillRule = FillRule::None; + break; + case 1: + fillRule = FillRule::NonZeroWinding; + break; + case 2: + fillRule = FillRule::EvenOdd; + break; + default: + throw LottieParsingException(); + } + } + + fillEnabled = getOptionalBool(json, "fillEnabled"); + } + + virtual ~Fill() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("c", color.toJson())); + + if (fillRule.has_value()) { + json.insert(std::make_pair("r", (int)fillRule.value())); + } + + if (fillEnabled.has_value()) { + json.insert(std::make_pair("fillEnabled", fillEnabled.value())); + } + } + +public: + KeyframeGroup opacity; + + /// The color keyframes for the fill + KeyframeGroup color; + + std::optional fillRule; + + std::optional fillEnabled; +}; + +} + +#endif /* Fill_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.cpp new file mode 100644 index 00000000000..f2ed96d1b9e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.cpp @@ -0,0 +1,5 @@ +#include "GradientFill.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp new file mode 100644 index 00000000000..c0fecb7bafb --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp @@ -0,0 +1,113 @@ +#ifndef GradientFill_hpp +#define GradientFill_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Primitives/GradientColorSet.hpp" +#include + +namespace lottie { + +/// An item that define a gradient fill +class GradientFill: public ShapeItem { +public: + explicit GradientFill(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(100.0))), + startPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + endPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + gradientType(GradientType::None), + numberOfColors(0), + colors(KeyframeGroup(GradientColorSet())) { + opacity = KeyframeGroup(getObject(json, "o")); + startPoint = KeyframeGroup(getObject(json, "s")); + endPoint = KeyframeGroup(getObject(json, "e")); + + auto gradientTypeRawValue = getInt(json, "t"); + switch (gradientTypeRawValue) { + case 0: + gradientType = GradientType::None; + break; + case 1: + gradientType = GradientType::Linear; + break; + case 2: + gradientType = GradientType::Radial; + break; + default: + throw LottieParsingException(); + } + + if (const auto highlightLengthData = getOptionalObject(json, "h")) { + highlightLength = KeyframeGroup(highlightLengthData.value()); + } + if (const auto highlightAngleData = getOptionalObject(json, "a")) { + highlightAngle = KeyframeGroup(highlightAngleData.value()); + } + + auto colorsContainer = getObject(json, "g"); + numberOfColors = getInt(colorsContainer, "p"); + colors = KeyframeGroup(getObject(colorsContainer, "k")); + + rValue = getOptionalInt(json, "r"); + } + + virtual ~GradientFill() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("s", startPoint.toJson())); + json.insert(std::make_pair("e", endPoint.toJson())); + json.insert(std::make_pair("t", (int)gradientType)); + + if (highlightLength.has_value()) { + json.insert(std::make_pair("h", highlightLength->toJson())); + } + if (highlightAngle.has_value()) { + json.insert(std::make_pair("a", highlightAngle->toJson())); + } + + lottiejson11::Json::object colorsContainer; + colorsContainer.insert(std::make_pair("p", numberOfColors)); + colorsContainer.insert(std::make_pair("k", colors.toJson())); + json.insert(std::make_pair("g", colorsContainer)); + + if (rValue.has_value()) { + json.insert(std::make_pair("r", rValue.value())); + } + } + +public: + /// The opacity of the fill + KeyframeGroup opacity; + + /// The start of the gradient + KeyframeGroup startPoint; + + /// The end of the gradient + KeyframeGroup endPoint; + + /// The type of gradient + GradientType gradientType; + + /// Gradient Highlight Length. Only if type is Radial + std::optional> highlightLength; + + /// Highlight Angle. Only if type is Radial + std::optional> highlightAngle; + + /// The number of color points in the gradient + int numberOfColors; + + /// The Colors of the gradient. + KeyframeGroup colors; + + std::optional rValue; +}; + +} + +#endif /* GradientFill_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.cpp new file mode 100644 index 00000000000..465b20fde88 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.cpp @@ -0,0 +1,5 @@ +#include "GradientStroke.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp new file mode 100644 index 00000000000..95bed62b519 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp @@ -0,0 +1,193 @@ +#ifndef GradientStroke_hpp +#define GradientStroke_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/Objects/DashElement.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include + +namespace lottie { + +/// An item that define a gradient stroke +class GradientStroke: public ShapeItem { +public: + explicit GradientStroke(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(100.0))), + startPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + endPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + gradientType(GradientType::None), + numberOfColors(0), + colors(KeyframeGroup(GradientColorSet())), + width(KeyframeGroup(Vector1D(0.0))), + lineCap(LineCap::Round), + lineJoin(LineJoin::Round) { + opacity = KeyframeGroup(getObject(json, "o")); + startPoint = KeyframeGroup(getObject(json, "s")); + endPoint = KeyframeGroup(getObject(json, "e")); + + auto gradientTypeRawValue = getInt(json, "t"); + switch (gradientTypeRawValue) { + case 0: + gradientType = GradientType::None; + break; + case 1: + gradientType = GradientType::Linear; + break; + case 2: + gradientType = GradientType::Radial; + break; + default: + throw LottieParsingException(); + } + + if (const auto highlightLengthData = getOptionalObject(json, "h")) { + highlightLength = KeyframeGroup(highlightLengthData.value()); + } + if (const auto highlightAngleData = getOptionalObject(json, "a")) { + highlightAngle = KeyframeGroup(highlightAngleData.value()); + } + + width = KeyframeGroup(getObject(json, "w")); + + if (const auto lineCapRawValue = getOptionalInt(json, "lc")) { + switch (lineCapRawValue.value()) { + case 0: + lineCap = LineCap::None; + break; + case 1: + lineCap = LineCap::Butt; + break; + case 2: + lineCap = LineCap::Round; + break; + case 3: + lineCap = LineCap::Square; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto lineJoinRawValue = getOptionalInt(json, "lj")) { + switch (lineJoinRawValue.value()) { + case 0: + lineJoin = LineJoin::None; + break; + case 1: + lineJoin = LineJoin::Miter; + break; + case 2: + lineJoin = LineJoin::Round; + break; + case 3: + lineJoin = LineJoin::Bevel; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto miterLimitData = getOptionalDouble(json, "ml")) { + miterLimit = (float)miterLimitData.value(); + } + + auto colorsContainer = getObject(json, "g"); + numberOfColors = getInt(colorsContainer, "p"); + auto colorsData = getObject(colorsContainer, "k"); + colors = KeyframeGroup(colorsData); + + if (const auto dashElementsData = getOptionalObjectArray(json, "d")) { + dashPattern = std::vector(); + for (const auto &dashElementData : dashElementsData.value()) { + dashPattern->push_back(DashElement(dashElementData)); + } + } + } + + virtual ~GradientStroke() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("s", startPoint.toJson())); + json.insert(std::make_pair("e", endPoint.toJson())); + json.insert(std::make_pair("t", (int)gradientType)); + + if (highlightLength.has_value()) { + json.insert(std::make_pair("h", highlightLength->toJson())); + } + if (highlightAngle.has_value()) { + json.insert(std::make_pair("a", highlightAngle->toJson())); + } + + json.insert(std::make_pair("w", width.toJson())); + + json.insert(std::make_pair("lc", (int)lineCap)); + json.insert(std::make_pair("lj", (int)lineJoin)); + + if (miterLimit.has_value()) { + json.insert(std::make_pair("ml", miterLimit.value())); + } + + lottiejson11::Json::object colorsContainer; + colorsContainer.insert(std::make_pair("p", numberOfColors)); + colorsContainer.insert(std::make_pair("k", colors.toJson())); + json.insert(std::make_pair("g", colorsContainer)); + + if (dashPattern.has_value()) { + lottiejson11::Json::array dashElements; + for (const auto &dashElement : dashPattern.value()) { + dashElements.push_back(dashElement.toJson()); + } + json.insert(std::make_pair("d", dashElements)); + } + } + +public: + /// The opacity of the fill + KeyframeGroup opacity; + + /// The start of the gradient + KeyframeGroup startPoint; + + /// The end of the gradient + KeyframeGroup endPoint; + + /// The type of gradient + GradientType gradientType; + + /// Gradient Highlight Length. Only if type is Radial + std::optional> highlightLength; + + /// Highlight Angle. Only if type is Radial + std::optional> highlightAngle; + + /// The number of color points in the gradient + int numberOfColors; + + /// The Colors of the gradient. + KeyframeGroup colors; + + /// The width of the stroke + KeyframeGroup width; + + /// Line Cap + LineCap lineCap; + + /// Line Join + LineJoin lineJoin; + + /// Miter Limit + std::optional miterLimit; + + /// The dash pattern of the stroke + std::optional> dashPattern; +}; + +} + +#endif /* GradientStroke_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.cpp new file mode 100644 index 00000000000..89ea7daea73 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.cpp @@ -0,0 +1,5 @@ +#include "Group.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.hpp new file mode 100644 index 00000000000..7bc2eb4d10b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.hpp @@ -0,0 +1,53 @@ +#ifndef Group_hpp +#define Group_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +/// An item that define an ellipse shape +class Group: public ShapeItem { +public: + explicit Group(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json) { + auto itemsData = getObjectArray(json, "it"); + for (const auto &itemData : itemsData) { + items.push_back(parseShapeItem(itemData)); + } + + numberOfProperties = getOptionalInt(json, "np"); + } + + virtual ~Group() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + lottiejson11::Json::array itemArray; + for (const auto &item : items) { + lottiejson11::Json::object itemJson; + item->toJson(itemJson); + itemArray.push_back(itemJson); + } + + json.insert(std::make_pair("it", itemArray)); + + if (numberOfProperties.has_value()) { + json.insert(std::make_pair("np", numberOfProperties.value())); + } + } + +public: + /// A list of shape items. + std::vector> items; + + std::optional numberOfProperties; +}; + +} + +#endif /* Group_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.cpp new file mode 100644 index 00000000000..b22b05d1f6d --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.cpp @@ -0,0 +1,5 @@ +#include "Merge.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.hpp new file mode 100644 index 00000000000..4f6fcc48818 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.hpp @@ -0,0 +1,64 @@ +#ifndef Merge_hpp +#define Merge_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class MergeMode: int { + None = 0, + Merge = 1, + Add = 2, + Subtract = 3, + Intersect = 4, + Exclude = 5 +}; + +/// An item that define an ellipse shape +class Merge: public ShapeItem { +public: + explicit Merge(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + mode(MergeMode::None) { + auto modeRawValue = getInt(json, "mm"); + switch (modeRawValue) { + case 0: + mode = MergeMode::None; + break; + case 1: + mode = MergeMode::Merge; + break; + case 2: + mode = MergeMode::Add; + break; + case 3: + mode = MergeMode::Subtract; + break; + case 4: + mode = MergeMode::Intersect; + break; + case 5: + mode = MergeMode::Exclude; + break; + default: + throw LottieParsingException(); + } + } + + virtual ~Merge() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("mm", (int)mode)); + } + +public: + /// The mode of the merge path + MergeMode mode; +}; + +} + +#endif /* Merge_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.cpp new file mode 100644 index 00000000000..c686130aa9e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.cpp @@ -0,0 +1,5 @@ +#include "Rectangle.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.hpp new file mode 100644 index 00000000000..67926f2e291 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.hpp @@ -0,0 +1,101 @@ +#ifndef Rectangle_hpp +#define Rectangle_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define an ellipse shape +class Rectangle: public ShapeItem { +public: + explicit Rectangle(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + size(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + cornerRadius(KeyframeGroup(Vector1D(0.0))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + position = KeyframeGroup(getObject(json, "p")); + size = KeyframeGroup(getObject(json, "s")); + cornerRadius = KeyframeGroup(getObject(json, "r")); + } + + virtual ~Rectangle() = default; + + explicit Rectangle( + std::optional name_, + std::optional matchName_, + std::optional expressionIndex_, + std::optional cix_, + std::optional _hidden_, + std::optional index_, + std::optional blendMode_, + std::optional layerClass_, + std::optional direction_, + KeyframeGroup position_, + KeyframeGroup size_, + KeyframeGroup cornerRadius_ + ) : + ShapeItem( + name_, + matchName_, + expressionIndex_, + cix_, + ShapeType::Rectangle, + _hidden_, + index_, + blendMode_, + layerClass_ + ), + direction(direction_), + position(position_), + size(size_), + cornerRadius(cornerRadius_) { + } + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + + json.insert(std::make_pair("p", position.toJson())); + json.insert(std::make_pair("s", size.toJson())); + json.insert(std::make_pair("r", cornerRadius.toJson())); + } + +public: + /// The direction of the rect. + std::optional direction; + + /// The position + KeyframeGroup position; + + /// The size + KeyframeGroup size; + + /// The Corner radius of the rectangle + KeyframeGroup cornerRadius; +}; + +} + +#endif /* Rectangle_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.cpp new file mode 100644 index 00000000000..70477e31af8 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.cpp @@ -0,0 +1,5 @@ +#include "Repeater.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.hpp new file mode 100644 index 00000000000..8bb25a1c375 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.hpp @@ -0,0 +1,100 @@ +#ifndef Repeater_hpp +#define Repeater_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define a repeater +class Repeater: public ShapeItem { +public: + explicit Repeater(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json) { + if (const auto copiesData = getOptionalObject(json, "c")) { + copies = KeyframeGroup(copiesData.value()); + } + if (const auto offsetData = getOptionalObject(json, "o")) { + offset = KeyframeGroup(offsetData.value()); + } + + auto transformContainer = getObject(json, "tr"); + if (const auto startOpacityData = getOptionalObject(transformContainer, "so")) { + startOpacity = KeyframeGroup(startOpacityData.value()); + } + if (const auto endOpacityData = getOptionalObject(transformContainer, "eo")) { + endOpacity = KeyframeGroup(endOpacityData.value()); + } + if (const auto rotationData = getOptionalObject(transformContainer, "r")) { + rotation = KeyframeGroup(rotationData.value()); + } + if (const auto positionData = getOptionalObject(transformContainer, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto scaleData = getOptionalObject(transformContainer, "s")) { + scale = KeyframeGroup(scaleData.value()); + } + } + + virtual ~Repeater() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (copies.has_value()) { + json.insert(std::make_pair("c", copies->toJson())); + } + if (offset.has_value()) { + json.insert(std::make_pair("o", offset->toJson())); + } + + lottiejson11::Json::object transformContainer; + if (startOpacity.has_value()) { + json.insert(std::make_pair("so", startOpacity->toJson())); + } + if (endOpacity.has_value()) { + json.insert(std::make_pair("eo", endOpacity->toJson())); + } + if (rotation.has_value()) { + json.insert(std::make_pair("r", rotation->toJson())); + } + if (position.has_value()) { + json.insert(std::make_pair("p", position->toJson())); + } + if (scale.has_value()) { + json.insert(std::make_pair("s", scale->toJson())); + } + + json.insert(std::make_pair("tr", transformContainer)); + } + +public: + /// The number of copies to repeat + std::optional> copies; + + /// The offset of each copy + std::optional> offset; + + /// Start Opacity + std::optional> startOpacity; + + /// End opacity + std::optional> endOpacity; + + /// The rotation + std::optional> rotation; + + /// Anchor Point + std::optional> anchorPoint; + + /// Position + std::optional> position; + + /// Scale + std::optional> scale; +}; + +} + +#endif /* Repeater_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.cpp new file mode 100644 index 00000000000..64cba093630 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.cpp @@ -0,0 +1,5 @@ +#include "RoundedRectangle.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.hpp new file mode 100644 index 00000000000..548d23a32d8 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.hpp @@ -0,0 +1,77 @@ +#ifndef RoundedRectangle_hpp +#define RoundedRectangle_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define an ellipse shape +class RoundedRectangle: public ShapeItem { +public: + explicit RoundedRectangle(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + cornerRadius(KeyframeGroup(Vector1D(0.0))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto positionData = getOptionalObject(json, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto sizeData = getOptionalObject(json, "s")) { + size = KeyframeGroup(sizeData.value()); + } + + cornerRadius = KeyframeGroup(getObject(json, "r")); + } + + virtual ~RoundedRectangle() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + if (position.has_value()) { + json.insert(std::make_pair("p", position->toJson())); + } + if (size.has_value()) { + json.insert(std::make_pair("s", size->toJson())); + } + + json.insert(std::make_pair("r", cornerRadius.toJson())); + } + +public: + /// The direction of the rect. + std::optional direction; + + /// The position + std::optional> position; + + /// The size + std::optional> size; + + /// The Corner radius of the rectangle + KeyframeGroup cornerRadius; +}; + +} + +#endif /* RoundedRectangle_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.cpp new file mode 100644 index 00000000000..bf5a96876c4 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.cpp @@ -0,0 +1,5 @@ +#include "Shape.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp new file mode 100644 index 00000000000..66a5a36f85d --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp @@ -0,0 +1,55 @@ +#ifndef Shape_hpp +#define Shape_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// An item that defines an custom shape +class Shape: public ShapeItem { +public: + explicit Shape(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + path(KeyframeGroup(getObject(json, "ks"))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + } + + virtual ~Shape() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("ks", path.toJson())); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + } + +public: + KeyframeGroup path; + std::optional direction; +}; + +} + +#endif /* Shape_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.cpp new file mode 100644 index 00000000000..a47b348c29c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.cpp @@ -0,0 +1,55 @@ +#include "ShapeItem.hpp" + +#include "Ellipse.hpp" +#include "Fill.hpp" +#include "GradientFill.hpp" +#include "Group.hpp" +#include "GradientStroke.hpp" +#include "Merge.hpp" +#include "Rectangle.hpp" +#include "RoundedRectangle.hpp" +#include "Repeater.hpp" +#include "Shape.hpp" +#include "Star.hpp" +#include "Stroke.hpp" +#include "Trim.hpp" +#include "ShapeTransform.hpp" + +namespace lottie { + +std::shared_ptr parseShapeItem(lottiejson11::Json::object const &json) noexcept(false) { + auto typeRawValue = getString(json, "ty"); + if (typeRawValue == "el") { + return std::make_shared(json); + } else if (typeRawValue == "fl") { + return std::make_shared(json); + } else if (typeRawValue == "gf") { + return std::make_shared(json); + } else if (typeRawValue == "gr") { + return std::make_shared(json); + } else if (typeRawValue == "gs") { + return std::make_shared(json); + } else if (typeRawValue == "mm") { + return std::make_shared(json); + } else if (typeRawValue == "rc") { + return std::make_shared(json); + } else if (typeRawValue == "rp") { + return std::make_shared(json); + } else if (typeRawValue == "sh") { + return std::make_shared(json); + } else if (typeRawValue == "sr") { + return std::make_shared(json); + } else if (typeRawValue == "st") { + return std::make_shared(json); + } else if (typeRawValue == "tm") { + return std::make_shared(json); + } else if (typeRawValue == "tr") { + return std::make_shared(json); + } else if (typeRawValue == "rd") { + return std::make_shared(json); + } else { + throw LottieParsingException(); + } +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.hpp new file mode 100644 index 00000000000..892eba1db2c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.hpp @@ -0,0 +1,209 @@ +#ifndef ShapeItem_hpp +#define ShapeItem_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +enum class ShapeType { + Ellipse, + Fill, + GradientFill, + Group, + GradientStroke, + Merge, + Rectangle, + Repeater, + Shape, + Star, + Stroke, + Trim, + Transform, + RoundedRectangle +}; + +/// An item belonging to a Shape Layer +class ShapeItem { +public: + ShapeItem(lottiejson11::Json const &jsonAny) noexcept(false) : + type(ShapeType::Ellipse) { + if (!jsonAny.is_object()) { + throw LottieParsingException(); + } + + lottiejson11::Json::object const &json = jsonAny.object_items(); + + name = getOptionalString(json, "nm"); + matchName = getOptionalString(json, "mn"); + expressionIndex = getOptionalInt(json, "ix"); + cix = getOptionalInt(json, "cix"); + + index = getOptionalInt(json, "ind"); + blendMode = getOptionalInt(json, "bm"); + + auto typeRawValue = getString(json, "ty"); + if (typeRawValue == "el") { + type = ShapeType::Ellipse; + } else if (typeRawValue == "fl") { + type = ShapeType::Fill; + } else if (typeRawValue == "gf") { + type = ShapeType::GradientFill; + } else if (typeRawValue == "gr") { + type = ShapeType::Group; + } else if (typeRawValue == "gs") { + type = ShapeType::GradientStroke; + } else if (typeRawValue == "mm") { + type = ShapeType::Merge; + } else if (typeRawValue == "rc") { + type = ShapeType::Rectangle; + } else if (typeRawValue == "rp") { + type = ShapeType::Repeater; + } else if (typeRawValue == "sh") { + type = ShapeType::Shape; + } else if (typeRawValue == "sr") { + type = ShapeType::Star; + } else if (typeRawValue == "st") { + type = ShapeType::Stroke; + } else if (typeRawValue == "tm") { + type = ShapeType::Trim; + } else if (typeRawValue == "tr") { + type = ShapeType::Transform; + } else if (typeRawValue == "rd") { + type = ShapeType::RoundedRectangle; + } else { + throw LottieParsingException(); + } + + _hidden = getOptionalBool(json, "hd"); + + layerClass = getOptionalString(json, "cl"); + } + + ShapeItem( + std::optional name_, + std::optional matchName_, + std::optional expressionIndex_, + std::optional cix_, + ShapeType type_, + std::optional _hidden_, + std::optional index_, + std::optional blendMode_, + std::optional layerClass_ + ) : + name(name_), + matchName(matchName_), + expressionIndex(expressionIndex_), + cix(cix_), + type(type_), + _hidden(_hidden_), + index(index_), + blendMode(blendMode_), + layerClass(layerClass_) { + } + + ShapeItem(const ShapeItem&) = delete; + ShapeItem& operator=(ShapeItem&) = delete; + + virtual void toJson(lottiejson11::Json::object &json) const { + if (name.has_value()) { + json.insert(std::make_pair("nm", name.value())); + } + if (matchName.has_value()) { + json.insert(std::make_pair("mn", matchName.value())); + } + if (expressionIndex.has_value()) { + json.insert(std::make_pair("ix", expressionIndex.value())); + } + if (cix.has_value()) { + json.insert(std::make_pair("cix", cix.value())); + } + + if (index.has_value()) { + json.insert(std::make_pair("ind", index.value())); + } + if (blendMode.has_value()) { + json.insert(std::make_pair("bm", blendMode.value())); + } + + switch (type) { + case ShapeType::Ellipse: + json.insert(std::make_pair("ty", "el")); + break; + case ShapeType::Fill: + json.insert(std::make_pair("ty", "fl")); + break; + case ShapeType::GradientFill: + json.insert(std::make_pair("ty", "gf")); + break; + case ShapeType::Group: + json.insert(std::make_pair("ty", "gr")); + break; + case ShapeType::GradientStroke: + json.insert(std::make_pair("ty", "gs")); + break; + case ShapeType::Merge: + json.insert(std::make_pair("ty", "mm")); + break; + case ShapeType::Rectangle: + json.insert(std::make_pair("ty", "rc")); + break; + case ShapeType::RoundedRectangle: + json.insert(std::make_pair("ty", "rd")); + break; + case ShapeType::Repeater: + json.insert(std::make_pair("ty", "rp")); + break; + case ShapeType::Shape: + json.insert(std::make_pair("ty", "sh")); + break; + case ShapeType::Star: + json.insert(std::make_pair("ty", "sr")); + break; + case ShapeType::Stroke: + json.insert(std::make_pair("ty", "st")); + break; + case ShapeType::Trim: + json.insert(std::make_pair("ty", "tm")); + break; + case ShapeType::Transform: + json.insert(std::make_pair("ty", "tr")); + break; + } + + if (_hidden.has_value()) { + json.insert(std::make_pair("hd", _hidden.value())); + } + + if (layerClass.has_value()) { + json.insert(std::make_pair("cl", layerClass.value())); + } + } + + bool hidden() const { + if (_hidden.has_value()) { + return _hidden.value(); + } else { + return false; + } + } + +public: + std::optional name; + std::optional matchName; + std::optional expressionIndex; + std::optional cix; + ShapeType type; + std::optional _hidden; + std::optional index; + std::optional blendMode; + + std::optional layerClass; +}; + +std::shared_ptr parseShapeItem(lottiejson11::Json::object const &json) noexcept(false); + +} + +#endif /* ShapeItem_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.cpp new file mode 100644 index 00000000000..1df07218bc0 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.cpp @@ -0,0 +1,5 @@ +#include "ShapeTransform.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.hpp new file mode 100644 index 00000000000..fcfd28a2b8f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.hpp @@ -0,0 +1,91 @@ +#ifndef ShapeTransform_hpp +#define ShapeTransform_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define a shape transform +class ShapeTransform: public ShapeItem { +public: + explicit ShapeTransform(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json) { + if (const auto anchorData = getOptionalObject(json, "a")) { + anchor = KeyframeGroup(anchorData.value()); + } + if (const auto positionData = getOptionalObject(json, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto scaleData = getOptionalObject(json, "s")) { + scale = KeyframeGroup(scaleData.value()); + } + if (const auto rotationData = getOptionalObject(json, "r")) { + rotation = KeyframeGroup(rotationData.value()); + } + if (const auto opacityData = getOptionalObject(json, "o")) { + opacity = KeyframeGroup(opacityData.value()); + } + if (const auto skewData = getOptionalObject(json, "sk")) { + skew = KeyframeGroup(skewData.value()); + } + if (const auto skewAxisData = getOptionalObject(json, "sa")) { + skewAxis = KeyframeGroup(skewAxisData.value()); + } + } + + virtual ~ShapeTransform() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (anchor.has_value()) { + json.insert(std::make_pair("a", anchor->toJson())); + } + if (position.has_value()) { + json.insert(std::make_pair("p", position->toJson())); + } + if (scale.has_value()) { + json.insert(std::make_pair("s", scale->toJson())); + } + if (rotation.has_value()) { + json.insert(std::make_pair("r", rotation->toJson())); + } + if (opacity.has_value()) { + json.insert(std::make_pair("o", opacity->toJson())); + } + if (skew.has_value()) { + json.insert(std::make_pair("sk", skew->toJson())); + } + if (skewAxis.has_value()) { + json.insert(std::make_pair("sa", skewAxis->toJson())); + } + } + +public: + /// Anchor Point + std::optional> anchor; + + /// Position + std::optional> position; + + /// Scale + std::optional> scale; + + /// Rotation + std::optional> rotation; + + /// opacity + std::optional> opacity; + + /// Skew + std::optional> skew; + + /// Skew Axis + std::optional> skewAxis; +}; + +} + +#endif /* ShapeTransform_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.cpp new file mode 100644 index 00000000000..c5f38f5946d --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.cpp @@ -0,0 +1,5 @@ +#include "Star.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.hpp new file mode 100644 index 00000000000..c9f711526a6 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.hpp @@ -0,0 +1,133 @@ +#ifndef Star_hpp +#define Star_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +enum class StarType: int { + None = 0, + Star = 1, + Polygon = 2 +}; + +/// An item that define a star shape +class Star: public ShapeItem { +public: + explicit Star(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + outerRadius(KeyframeGroup(Vector1D(0.0))), + outerRoundness(KeyframeGroup(Vector1D(0.0))), + rotation(KeyframeGroup(Vector1D(0.0))), + points(KeyframeGroup(Vector1D(0.0))), + starType(StarType::None) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + position = KeyframeGroup(getObject(json, "p")); + outerRadius = KeyframeGroup(getObject(json, "or")); + outerRoundness = KeyframeGroup(getObject(json, "os")); + + if (const auto innerRadiusData = getOptionalObject(json, "ir")) { + innerRadius = KeyframeGroup(innerRadiusData.value()); + } + if (const auto innerRoundnessData = getOptionalObject(json, "is")) { + innerRoundness = KeyframeGroup(innerRoundnessData.value()); + } + + rotation = KeyframeGroup(getObject(json, "r")); + points = KeyframeGroup(getObject(json, "pt")); + + auto starTypeRawValue = getInt(json, "sy"); + switch (starTypeRawValue) { + case 0: + starType = StarType::None; + break; + case 1: + starType = StarType::Star; + break; + case 2: + starType = StarType::Polygon; + break; + default: + throw LottieParsingException(); + } + } + + virtual ~Star() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + + json.insert(std::make_pair("p", position.toJson())); + json.insert(std::make_pair("or", outerRadius.toJson())); + json.insert(std::make_pair("os", outerRoundness.toJson())); + + if (innerRadius.has_value()) { + json.insert(std::make_pair("ir", innerRadius->toJson())); + } + if (innerRoundness.has_value()) { + json.insert(std::make_pair("is", innerRoundness->toJson())); + } + + json.insert(std::make_pair("r", rotation.toJson())); + json.insert(std::make_pair("pt", points.toJson())); + + json.insert(std::make_pair("sy", (int)starType)); + } + +public: + /// The direction of the star. + std::optional direction; + + /// The position of the star + KeyframeGroup position; + + /// The outer radius of the star + KeyframeGroup outerRadius; + + /// The outer roundness of the star + KeyframeGroup outerRoundness; + + /// The outer radius of the star + std::optional> innerRadius; + + /// The outer roundness of the star + std::optional> innerRoundness; + + /// The rotation of the star + KeyframeGroup rotation; + + /// The number of points on the star + KeyframeGroup points; + + /// The type of star + StarType starType; +}; + +} + +#endif /* Star_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.cpp new file mode 100644 index 00000000000..0c858f24425 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.cpp @@ -0,0 +1,5 @@ +#include "Stroke.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp new file mode 100644 index 00000000000..e306fcd6a6a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp @@ -0,0 +1,142 @@ +#ifndef Stroke_hpp +#define Stroke_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientStroke.hpp" +#import +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/Objects/DashElement.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// An item that define an ellipse shape +class Stroke: public ShapeItem { +public: + explicit Stroke(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(100.0))), + color(KeyframeGroup(Color(0.0, 0.0, 0.0, 0.0))), + width(KeyframeGroup(Vector1D(0.0))), + lineCap(LineCap::Round), + lineJoin(LineJoin::Round) { + opacity = KeyframeGroup(getObject(json, "o")); + color = KeyframeGroup(getObject(json, "c")); + width = KeyframeGroup(getObject(json, "w")); + + if (const auto lineCapRawValue = getOptionalInt(json, "lc")) { + switch (lineCapRawValue.value()) { + case 0: + lineCap = LineCap::None; + break; + case 1: + lineCap = LineCap::Butt; + break; + case 2: + lineCap = LineCap::Round; + break; + case 3: + lineCap = LineCap::Square; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto lineJoinRawValue = getOptionalInt(json, "lj")) { + switch (lineJoinRawValue.value()) { + case 0: + lineJoin = LineJoin::None; + break; + case 1: + lineJoin = LineJoin::Miter; + break; + case 2: + lineJoin = LineJoin::Round; + break; + case 3: + lineJoin = LineJoin::Bevel; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto miterLimitData = getOptionalDouble(json, "ml")) { + miterLimit = (float)miterLimitData.value(); + } + + if (const auto dashElementsData = getOptionalObjectArray(json, "d")) { + dashPattern = std::vector(); + for (const auto &dashElementData : dashElementsData.value()) { + dashPattern->push_back(DashElement(dashElementData)); + } + } + + fillEnabled = getOptionalBool(json, "fillEnabled"); + ml2 = getOptionalAny(json, "ml2"); + } + + virtual ~Stroke() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("c", color.toJson())); + json.insert(std::make_pair("w", width.toJson())); + + json.insert(std::make_pair("lc", (int)lineCap)); + json.insert(std::make_pair("lj", (int)lineJoin)); + + if (miterLimit.has_value()) { + json.insert(std::make_pair("ml", miterLimit.value())); + } + + if (dashPattern.has_value()) { + lottiejson11::Json::array dashElements; + for (const auto &dashElement : dashPattern.value()) { + dashElements.push_back(dashElement.toJson()); + } + json.insert(std::make_pair("d", dashElements)); + } + + if (fillEnabled.has_value()) { + json.insert(std::make_pair("fillEnabled", fillEnabled.value())); + } + if (ml2.has_value()) { + json.insert(std::make_pair("ml2", ml2.value())); + } + } + +public: + /// The opacity of the stroke + KeyframeGroup opacity; + + /// The Color of the stroke + KeyframeGroup color; + + /// The width of the stroke + KeyframeGroup width; + + /// Line Cap + LineCap lineCap; + + /// Line Join + LineJoin lineJoin; + + /// Miter Limit + std::optional miterLimit; + + /// The dash pattern of the stroke + std::optional> dashPattern; + + std::optional fillEnabled; + std::optional ml2; +}; + +} + +#endif /* Stroke_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.cpp new file mode 100644 index 00000000000..85f95d8b0cf --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.cpp @@ -0,0 +1,5 @@ +#include "Trim.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp new file mode 100644 index 00000000000..e4628d604a1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp @@ -0,0 +1,57 @@ +#ifndef Trim_hpp +#define Trim_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that defines trim +class Trim: public ShapeItem { +public: + explicit Trim(lottiejson11::Json::object const &json) noexcept(false) : + ShapeItem(json), + start(KeyframeGroup(Vector1D(0.0))), + end(KeyframeGroup(Vector1D(0.0))), + offset(KeyframeGroup(Vector1D(0.0))), + trimType(TrimType::Simultaneously) { + start = KeyframeGroup(getObject(json, "s")); + end = KeyframeGroup(getObject(json, "e")); + offset = KeyframeGroup(getObject(json, "o")); + + auto trimTypeRawValue = getInt(json, "m"); + switch (trimTypeRawValue) { + case 1: + trimType = TrimType::Simultaneously; + break; + case 2: + trimType = TrimType::Individually; + break; + default: + throw LottieParsingException(); + } + } + + virtual ~Trim() = default; + + virtual void toJson(lottiejson11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("s", start.toJson())); + json.insert(std::make_pair("e", end.toJson())); + json.insert(std::make_pair("o", offset.toJson())); + json.insert(std::make_pair("m", (int)trimType)); + } + +public: + KeyframeGroup start; + KeyframeGroup end; + KeyframeGroup offset; + TrimType trimType; +}; + +} + +#endif /* Trim_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.cpp new file mode 100644 index 00000000000..7dfd91b3cc3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.cpp @@ -0,0 +1,5 @@ +#include "Font.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp new file mode 100644 index 00000000000..b321983e714 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp @@ -0,0 +1,103 @@ +#ifndef Font_hpp +#define Font_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class Font { +public: + Font( + std::string const &name_, + std::string const &familyName_, + std::string const &style_, + float ascent_ + ) : + name(name_), + familyName(familyName_), + style(style_), + ascent(ascent_) { + } + + explicit Font(lottiejson11::Json::object const &json) noexcept(false) { + name = getString(json, "fName"); + familyName = getString(json, "fFamily"); + path = getOptionalString(json, "fPath"); + weight = getOptionalString(json, "fWeight"); + fontClass = getOptionalString(json, "fClass"); + style = getString(json, "fStyle"); + ascent = (float)getDouble(json, "ascent"); + origin = getOptionalInt(json, "origin"); + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + result.insert(std::make_pair("fName", name)); + result.insert(std::make_pair("fFamily", familyName)); + if (path.has_value()) { + result.insert(std::make_pair("fPath", path.value())); + } + if (weight.has_value()) { + result.insert(std::make_pair("fWeight", weight.value())); + } + if (fontClass.has_value()) { + result.insert(std::make_pair("fClass", fontClass.value())); + } + result.insert(std::make_pair("fStyle", style)); + result.insert(std::make_pair("ascent", ascent)); + if (origin.has_value()) { + result.insert(std::make_pair("origin", origin.value())); + } + + return result; + } + +public: + std::string name; + std::string familyName; + std::optional path; + std::optional weight; + std::optional fontClass; + std::string style; + float ascent; + std::optional origin; +}; + +/// A list of fonts +class FontList { +public: + FontList(std::vector const &fonts_) : + fonts(fonts_) { + } + + explicit FontList(lottiejson11::Json::object const &json) noexcept(false) { + if (const auto fontsData = getOptionalObjectArray(json, "list")) { + for (const auto &fontData : fontsData.value()) { + fonts.emplace_back(fontData); + } + } + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::array fontArray; + + for (const auto &font : fonts) { + fontArray.push_back(font.toJson()); + } + + lottiejson11::Json::object result; + result.insert(std::make_pair("list", fontArray)); + return result; + } + +public: + std::vector fonts; +}; + +} + +#endif /* Font_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.cpp new file mode 100644 index 00000000000..071050db7a7 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.cpp @@ -0,0 +1,5 @@ +#include "Glyph.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp new file mode 100644 index 00000000000..d5c15228507 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp @@ -0,0 +1,108 @@ +#ifndef Glyph_hpp +#define Glyph_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class Glyph { +public: + Glyph( + std::string const &character_, + float fontSize_, + std::string const &fontFamily_, + std::string const &fontStyle_, + float width_, + std::optional>> shapes_ + ) : + character(character_), + fontSize(fontSize_), + fontFamily(fontFamily_), + fontStyle(fontStyle_), + width(width_), + shapes(shapes_) { + } + + explicit Glyph(lottiejson11::Json::object const &json) noexcept(false) : + character(""), + fontSize(0.0), + fontFamily(""), + fontStyle(""), + width(0.0) { + character = getString(json, "ch"); + fontSize = (float)getDouble(json, "size"); + fontFamily = getString(json, "fFamily"); + fontStyle = getString(json, "style"); + width = (float)getDouble(json, "w"); + + if (const auto shapeContainer = getOptionalObject(json, "data")) { + internalHasData = true; + + if (const auto shapesData = getOptionalObjectArray(shapeContainer.value(), "shapes")) { + shapes = std::vector>(); + + for (const auto &shapeData : shapesData.value()) { + shapes->push_back(parseShapeItem(shapeData)); + } + } + } + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + result.insert(std::make_pair("ch", character)); + result.insert(std::make_pair("size", fontSize)); + result.insert(std::make_pair("fFamily", fontFamily)); + result.insert(std::make_pair("style", fontStyle)); + result.insert(std::make_pair("w", width)); + + if (internalHasData || shapes.has_value()) { + lottiejson11::Json::object shapeContainer; + + if (shapes.has_value()) { + lottiejson11::Json::array shapeArray; + + for (const auto &shape : shapes.value()) { + lottiejson11::Json::object shapeJson; + shape->toJson(shapeJson); + shapeArray.push_back(shapeJson); + } + + shapeContainer.insert(std::make_pair("shapes", shapeArray)); + } + result.insert(std::make_pair("data", shapeContainer)); + } + + return result; + } + +public: + /// The character + std::string character; + + /// The font size of the character + float fontSize; + + /// The font family of the character + std::string fontFamily; + + /// The Style of the character + std::string fontStyle; + + /// The Width of the character + float width; + + /// The Shape Data of the Character + std::optional>> shapes; + + bool internalHasData = false; +}; + +} + +#endif /* Glyph_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.cpp new file mode 100644 index 00000000000..7cd9b257158 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.cpp @@ -0,0 +1,5 @@ +#include "TextAnimator.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp new file mode 100644 index 00000000000..6724c7e3fae --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp @@ -0,0 +1,183 @@ +#ifndef TextAnimator_hpp +#define TextAnimator_hpp + +#include +#import +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class TextAnimator { +public: + TextAnimator( + std::optional &name_, + std::optional> anchor_, + std::optional> position_, + std::optional> scale_, + std::optional> skew_, + std::optional> skewAxis_, + std::optional> rotation_, + std::optional> opacity_, + std::optional> strokeColor_, + std::optional> fillColor_, + std::optional> strokeWidth_, + std::optional> tracking_ + ) : + name(name_), + anchor(anchor_), + position(position_), + scale(scale_), + skew(skew_), + skewAxis(skewAxis_), + rotation(rotation_), + opacity(opacity_), + strokeColor(strokeColor_), + fillColor(fillColor_), + strokeWidth(strokeWidth_), + tracking(tracking_) { + } + + explicit TextAnimator(lottiejson11::Json const &jsonAny) { + if (!jsonAny.is_object()) { + throw LottieParsingException(); + } + lottiejson11::Json::object const &json = jsonAny.object_items(); + + if (const auto nameData = getOptionalString(json, "nm")) { + name = nameData.value(); + } + _extraS = getOptionalAny(json, "s"); + + lottiejson11::Json::object const &animatorContainer = getObject(json, "a"); + + if (const auto fillColorData = getOptionalObject(animatorContainer, "fc")) { + fillColor = KeyframeGroup(fillColorData.value()); + } + if (const auto strokeColorData = getOptionalObject(animatorContainer, "sc")) { + strokeColor = KeyframeGroup(strokeColorData.value()); + } + if (const auto strokeWidthData = getOptionalObject(animatorContainer, "sw")) { + strokeWidth = KeyframeGroup(strokeWidthData.value()); + } + if (const auto trackingData = getOptionalObject(animatorContainer, "t")) { + tracking = KeyframeGroup(trackingData.value()); + } + if (const auto anchorData = getOptionalObject(animatorContainer, "a")) { + anchor = KeyframeGroup(anchorData.value()); + } + if (const auto positionData = getOptionalObject(animatorContainer, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto scaleData = getOptionalObject(animatorContainer, "s")) { + scale = KeyframeGroup(scaleData.value()); + } + if (const auto skewData = getOptionalObject(animatorContainer, "sk")) { + skew = KeyframeGroup(skewData.value()); + } + if (const auto skewAxisData = getOptionalObject(animatorContainer, "sa")) { + skewAxis = KeyframeGroup(skewAxisData.value()); + } + if (const auto rotationData = getOptionalObject(animatorContainer, "r")) { + rotation = KeyframeGroup(rotationData.value()); + } + if (const auto opacityData = getOptionalObject(animatorContainer, "o")) { + opacity = KeyframeGroup(opacityData.value()); + } + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object animatorContainer; + + if (fillColor.has_value()) { + animatorContainer.insert(std::make_pair("fc", fillColor->toJson())); + } + if (strokeColor.has_value()) { + animatorContainer.insert(std::make_pair("sc", strokeColor->toJson())); + } + if (strokeWidth.has_value()) { + animatorContainer.insert(std::make_pair("sw", strokeWidth->toJson())); + } + if (tracking.has_value()) { + animatorContainer.insert(std::make_pair("t", tracking->toJson())); + } + if (anchor.has_value()) { + animatorContainer.insert(std::make_pair("a", anchor->toJson())); + } + if (position.has_value()) { + animatorContainer.insert(std::make_pair("p", position->toJson())); + } + if (scale.has_value()) { + animatorContainer.insert(std::make_pair("s", scale->toJson())); + } + if (skew.has_value()) { + animatorContainer.insert(std::make_pair("sk", skew->toJson())); + } + if (skewAxis.has_value()) { + animatorContainer.insert(std::make_pair("sa", skewAxis->toJson())); + } + if (rotation.has_value()) { + animatorContainer.insert(std::make_pair("r", rotation->toJson())); + } + if (opacity.has_value()) { + animatorContainer.insert(std::make_pair("o", opacity->toJson())); + } + + lottiejson11::Json::object result; + result.insert(std::make_pair("a", animatorContainer)); + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + if (_extraS.has_value()) { + result.insert(std::make_pair("s", _extraS.value())); + } + + return result; + } + +public: + std::optional name; + + /// Anchor + std::optional> anchor; + + /// Position + std::optional> position; + + /// Scale + std::optional> scale; + + /// Skew + std::optional> skew; + + /// Skew Axis + std::optional> skewAxis; + + /// Rotation + std::optional> rotation; + + /// Opacity + std::optional> opacity; + + /// Stroke Color + std::optional> strokeColor; + + /// Fill Color + std::optional> fillColor; + + /// Stroke Width + std::optional> strokeWidth; + + /// Tracking + std::optional> tracking; + + std::optional _extraS; +}; + +} + +#endif /* TextAnimator_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.cpp new file mode 100644 index 00000000000..95c3be2b676 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.cpp @@ -0,0 +1,5 @@ +#include "TextDocument.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp new file mode 100644 index 00000000000..83ea39ebcc0 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp @@ -0,0 +1,190 @@ +#ifndef TextDocument_hpp +#define TextDocument_hpp + +#include +#import +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +enum class TextJustification: int { + Left = 0, + Right = 1, + Center = 2 +}; + +class TextDocument { +public: + TextDocument( + std::string const &text_, + float fontSize_, + std::string const &fontFamily_, + TextJustification justification_, + int tracking_, + float lineHeight_, + std::optional baseline_, + std::optional fillColorData_, + std::optional strokeColorData_, + std::optional strokeWidth_, + std::optional strokeOverFill_, + std::optional textFramePosition_, + std::optional textFrameSize_ + ) : + text(text_), + fontSize(fontSize_), + fontFamily(fontFamily_), + justification(justification_), + tracking(tracking_), + lineHeight(lineHeight_), + baseline(baseline_), + fillColorData(fillColorData_), + strokeColorData(strokeColorData_), + strokeWidth(strokeWidth_), + strokeOverFill(strokeOverFill_), + textFramePosition(textFramePosition_), + textFrameSize(textFrameSize_) { + } + + explicit TextDocument(lottiejson11::Json const &jsonAny) noexcept(false) : + text(""), + fontSize(0.0), + fontFamily(""), + justification(TextJustification::Left), + tracking(0), + lineHeight(0.0) { + if (!jsonAny.is_object()) { + throw LottieParsingException(); + } + + lottiejson11::Json::object const &json = jsonAny.object_items(); + + text = getString(json, "t"); + fontSize = (float)getDouble(json, "s"); + fontFamily = getString(json, "f"); + + auto justificationRawValue = getInt(json, "j"); + switch (justificationRawValue) { + case 0: + justification = TextJustification::Left; + break; + case 1: + justification = TextJustification::Right; + break; + case 2: + justification = TextJustification::Center; + break; + default: + throw LottieParsingException(); + } + + tracking = getInt(json, "tr"); + lineHeight = (float)getDouble(json, "lh"); + if (const auto baselineValue = getOptionalDouble(json, "ls")) { + baseline = (float)baselineValue.value(); + } + + if (const auto fillColorDataValue = getOptionalAny(json, "fc")) { + fillColorData = Color(fillColorDataValue.value()); + } + + if (const auto strokeColorDataValue = getOptionalAny(json, "sc")) { + strokeColorData = Color(strokeColorDataValue.value()); + } + + if (const auto strokeWidthValue = getOptionalDouble(json, "sw")) { + strokeWidth = (float)strokeWidthValue.value(); + } + if (const auto strokeOverFillValue = getOptionalBool(json, "of")) { + strokeOverFill = (float)strokeOverFillValue.value(); + } + + if (const auto textFramePositionData = getOptionalAny(json, "ps")) { + textFramePosition = Vector3D(textFramePositionData.value()); + } + if (const auto textFrameSizeData = getOptionalAny(json, "sz")) { + textFrameSize = Vector3D(textFrameSizeData.value()); + } + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + result.insert(std::make_pair("t", text)); + result.insert(std::make_pair("s", fontSize)); + result.insert(std::make_pair("f", fontFamily)); + result.insert(std::make_pair("j", (int)justification)); + result.insert(std::make_pair("tr", tracking)); + result.insert(std::make_pair("lh", lineHeight)); + + if (baseline.has_value()) { + result.insert(std::make_pair("ls", baseline.value())); + } + + if (fillColorData.has_value()) { + result.insert(std::make_pair("fc", fillColorData->toJson())); + } + if (strokeColorData.has_value()) { + result.insert(std::make_pair("sc", strokeColorData->toJson())); + } + + if (strokeWidth.has_value()) { + result.insert(std::make_pair("sw", strokeWidth.value())); + } + if (strokeOverFill.has_value()) { + result.insert(std::make_pair("of", strokeOverFill.value())); + } + if (textFramePosition.has_value()) { + result.insert(std::make_pair("ps", textFramePosition->toJson())); + } + if (textFrameSize.has_value()) { + result.insert(std::make_pair("sz", textFrameSize->toJson())); + } + + return result; + } + +public: + /// The Text + std::string text; + + /// The Font size + float fontSize; + + /// The Font Family + std::string fontFamily; + + /// Justification + TextJustification justification; + + /// Tracking + int tracking; + + /// Line Height + float lineHeight; + + /// Baseline + std::optional baseline; + + /// Fill Color data + std::optional fillColorData; + + /// Scroke Color data + std::optional strokeColorData; + + /// Stroke Width + std::optional strokeWidth; + + /// Stroke Over Fill + std::optional strokeOverFill; + + std::optional textFramePosition; + + std::optional textFrameSize; +}; + +} + +#endif /* TextDocument_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.cpp new file mode 100644 index 00000000000..f72968be599 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.cpp @@ -0,0 +1,219 @@ +#include "JsonParsing.hpp" + +#include + +namespace lottie { + +thread_local int isExceptionExpectedLevel = 0; + +LottieParsingException::Guard::Guard() { + assert(isExceptionExpectedLevel >= 0); + isExceptionExpectedLevel++; +} + +LottieParsingException::Guard::~Guard() { + assert(isExceptionExpectedLevel - 1 >= 0); + isExceptionExpectedLevel--; +} + +LottieParsingException::LottieParsingException() { + if (isExceptionExpectedLevel == 0) { + assert(true); + } +} + +const char* LottieParsingException::what() const throw() { + return "Lottie parsing exception"; +} + +lottiejson11::Json getAny(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + return value->second; +} + +std::optional getOptionalAny(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + return value->second; +} + +lottiejson11::Json::object getObject(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_object()) { + throw LottieParsingException(); + } + return value->second.object_items(); +} + +std::optional getOptionalObject(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_object()) { + throw LottieParsingException(); + } + return value->second.object_items(); +} + +std::vector getObjectArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + std::vector result; + for (const auto &item : value->second.array_items()) { + if (!item.is_object()) { + throw LottieParsingException(); + } + result.push_back(item.object_items()); + } + + return result; +} + +std::optional> getOptionalObjectArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + std::vector result; + for (const auto &item : value->second.array_items()) { + if (!item.is_object()) { + throw LottieParsingException(); + } + result.push_back(item.object_items()); + } + + return result; +} + +std::vector getAnyArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + return value->second.array_items(); +} + +std::optional> getOptionalAnyArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw std::nullopt; + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + return value->second.array_items(); +} + +std::string getString(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_string()) { + throw LottieParsingException(); + } + return value->second.string_value(); +} + +std::optional getOptionalString(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_string()) { + throw LottieParsingException(); + } + return value->second.string_value(); +} + +int32_t getInt(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.int_value(); +} + +std::optional getOptionalInt(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.int_value(); +} + +double getDouble(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.number_value(); +} + +std::optional getOptionalDouble(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.number_value(); +} + +bool getBool(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_bool()) { + throw LottieParsingException(); + } + return value->second.bool_value(); +} + +std::optional getOptionalBool(lottiejson11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_bool()) { + throw LottieParsingException(); + } + return value->second.bool_value(); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.hpp new file mode 100644 index 00000000000..a0bd7951b3e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.hpp @@ -0,0 +1,52 @@ +#ifndef JsonParsing_hpp +#define JsonParsing_hpp + +#include + +#include +#include +#include + +namespace lottie { + +class LottieParsingException: public std::exception { +public: + class Guard { + public: + Guard(); + ~Guard(); + }; + +public: + LottieParsingException(); + + virtual const char* what() const throw(); +}; + +lottiejson11::Json getAny(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalAny(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); + +lottiejson11::Json::object getObject(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalObject(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); + +std::vector getObjectArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional> getOptionalObjectArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); + +std::vector getAnyArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional> getOptionalAnyArray(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); + +std::string getString(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalString(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); + +int32_t getInt(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalInt(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); + +double getDouble(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalDouble(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); + +bool getBool(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalBool(lottiejson11::Json::object const &object, std::string const &key) noexcept(false); + +} + +#endif /* JsonParsing_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp new file mode 100644 index 00000000000..85c50dba1e0 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp @@ -0,0 +1,690 @@ +#include + +#include +#include + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +BezierTrimPathPosition::BezierTrimPathPosition(float start_, float end_) : +start(start_), +end(end_) { +} + +BezierPathContents::BezierPathContents(CurveVertex const &startPoint) : +elements({ PathElement(startPoint) }) { +} + +BezierPathContents::BezierPathContents() : +elements({}), +closed(false) { +} + +BezierPathContents::BezierPathContents(lottiejson11::Json const &jsonAny) noexcept(false) : +elements({}) { + lottiejson11::Json::object const *json = nullptr; + if (jsonAny.is_object()) { + json = &jsonAny.object_items(); + } else if (jsonAny.is_array()) { + if (jsonAny.array_items().empty()) { + throw LottieParsingException(); + } + if (!jsonAny.array_items()[0].is_object()) { + throw LottieParsingException(); + } + json = &jsonAny.array_items()[0].object_items(); + } + + if (const auto closedData = getOptionalBool(*json, "c")) { + closed = closedData.value(); + } + + auto vertexContainer = getAnyArray(*json, "v"); + auto inPointsContainer = getAnyArray(*json, "i"); + auto outPointsContainer = getAnyArray(*json, "o"); + + if (vertexContainer.size() != inPointsContainer.size() || inPointsContainer.size() != outPointsContainer.size()) { + throw LottieParsingException(); + } + if (vertexContainer.empty()) { + return; + } + + /// Create first point + Vector2D firstPoint = Vector2D(vertexContainer[0]); + Vector2D firstInPoint = Vector2D(inPointsContainer[0]); + Vector2D firstOutPoint = Vector2D(outPointsContainer[0]); + CurveVertex firstVertex = CurveVertex::relative( + firstPoint, + firstInPoint, + firstOutPoint + ); + PathElement previousElement(firstVertex); + elements.push_back(previousElement); + + for (size_t i = 1; i < vertexContainer.size(); i++) { + Vector2D point = Vector2D(vertexContainer[i]); + Vector2D inPoint = Vector2D(inPointsContainer[i]); + Vector2D outPoint = Vector2D(outPointsContainer[i]); + CurveVertex vertex = CurveVertex::relative( + point, + inPoint, + outPoint + ); + auto pathElement = previousElement.pathElementTo(vertex); + elements.push_back(pathElement); + previousElement = pathElement; + } + + if (closed.value_or(false)) { + auto closeElement = previousElement.pathElementTo(firstVertex); + elements.push_back(closeElement); + } +} + +lottiejson11::Json BezierPathContents::toJson() const { + lottiejson11::Json::object result; + + lottiejson11::Json::array vertices; + lottiejson11::Json::array inPoints; + lottiejson11::Json::array outPoints; + + for (const auto &element : elements) { + vertices.push_back(element.vertex.point.toJson()); + inPoints.push_back(element.vertex.inTangentRelative().toJson()); + outPoints.push_back(element.vertex.outTangentRelative().toJson()); + } + + result.insert(std::make_pair("v", vertices)); + result.insert(std::make_pair("i", inPoints)); + result.insert(std::make_pair("o", outPoints)); + + if (closed.has_value()) { + result.insert(std::make_pair("c", closed.value())); + } + + return lottiejson11::Json(result); +} + +std::shared_ptr BezierPathContents::cgPath() const { + auto cgPath = CGPath::makePath(); + + std::optional previousElement; + for (const auto &element : elements) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + cgPath->addLineTo(element.vertex.point); + } else { + cgPath->addCurveTo(element.vertex.point, previousElement->vertex.outTangent, element.vertex.inTangent); + } + } else { + cgPath->moveTo(element.vertex.point); + } + previousElement = element; + } + if (closed.value_or(true)) { + cgPath->closeSubpath(); + } + return cgPath; +} + +float BezierPathContents::length() { + if (_length.has_value()) { + return _length.value(); + } else { + float result = 0.0; + for (size_t i = 1; i < elements.size(); i++) { + result += elements[i].length(elements[i - 1]); + } + _length = result; + return result; + } +} + +void BezierPathContents::moveToStartPoint(CurveVertex const &vertex) { + elements = { PathElement(vertex) }; + _length = std::nullopt; +} + +void BezierPathContents::addVertex(CurveVertex const &vertex) { + addElement(PathElement(vertex)); +} + +void BezierPathContents::reserveCapacity(size_t capacity) { + elements.reserve(capacity); +} + +void BezierPathContents::setElementCount(size_t count) { + elements.resize(count, PathElement(CurveVertex::absolute(Vector2D(0.0, 0.0), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0)))); +} + +void BezierPathContents::invalidateLength() { + _length.reset(); +} + +void BezierPathContents::addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) { + if (elements.empty()) { + return; + } + auto previous = elements[elements.size() - 1]; + auto newVertex = CurveVertex::absolute(toPoint, inTangent, toPoint); + updateVertex( + CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, outTangent), + (int)elements.size() - 1, + false + ); + addVertex(newVertex); +} + +void BezierPathContents::addLine(Vector2D const &toPoint) { + if (elements.empty()) { + return; + } + auto previous = elements[elements.size() - 1]; + auto newVertex = CurveVertex::relative(toPoint, Vector2D::Zero(), Vector2D::Zero()); + updateVertex( + CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, previous.vertex.point), + (int)elements.size() - 1, + false + ); + addVertex(newVertex); +} + +void BezierPathContents::close() { + closed = true; +} + +void BezierPathContents::addElement(PathElement const &pathElement) { + elements.push_back(pathElement); +} + +void BezierPathContents::updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) { + if (remeasure) { + PathElement newElement(CurveVertex::absolute(Vector2D::Zero(), Vector2D::Zero(), Vector2D::Zero())); + if (atIndex > 0) { + auto previousElement = elements[atIndex - 1]; + newElement = previousElement.pathElementTo(vertex); + } else { + newElement = PathElement(vertex); + } + elements[atIndex] = newElement; + + if (atIndex + 1 < elements.size()) { + auto nextElement = elements[atIndex + 1]; + elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex); + } + + } else { + auto oldElement = elements[atIndex]; + elements[atIndex] = oldElement.updateVertex(vertex); + } +} + +std::vector> BezierPathContents::trim(float fromLength, float toLength, float offsetLength) { + if (elements.size() <= 1) { + return {}; + } + + if (fromLength == toLength) { + return {}; + } + + float lengthValue = length(); + + /// Normalize lengths to the curve length. + auto start = fmod(fromLength + offsetLength, lengthValue); + auto end = fmod(toLength + offsetLength, lengthValue); + + if (start < 0.0) { + start = lengthValue + start; + } + + if (end < 0.0) { + end = lengthValue + end; + } + + if (start == lengthValue) { + start = 0.0; + } + if (end == 0.0) { + end = lengthValue; + } + + if ( + (start == 0.0 && end == lengthValue) || + start == end || + (start == lengthValue && end == 0.0) + ) { + /// The trim encompasses the entire path. Return. + return { shared_from_this() }; + } + + if (start > end) { + // Start is greater than end. Two paths are returned. + return trimPathAtLengths({ + BezierTrimPathPosition(0.0, end), + BezierTrimPathPosition(start, lengthValue) + }); + } + + return trimPathAtLengths({ BezierTrimPathPosition(start, end) }); +} + +// MARK: Private + +std::vector> BezierPathContents::trimPathAtLengths(std::vector const &positions) { + if (positions.empty()) { + return {}; + } + auto remainingPositions = positions; + + auto trim = remainingPositions[0]; + remainingPositions.erase(remainingPositions.begin()); + + std::vector> paths; + + float runningLength = 0.0; + bool finishedTrimming = false; + auto pathElements = elements; + + auto currentPath = std::make_shared(); + int i = 0; + + while (!finishedTrimming) { + if (pathElements.size() <= i) { + /// Do this for rounding errors + paths.push_back(currentPath); + finishedTrimming = true; + continue; + } + /// Loop through and add elements within start->end range. + /// Get current element + auto element = pathElements[i]; + float elementLength = 0.0; + if (i != 0) { + elementLength = element.length(pathElements[i - 1]); + } + + /// Calculate new running length. + auto newLength = runningLength + elementLength; + + if (newLength < trim.start) { + /// Element is not included in the trim, continue. + runningLength = newLength; + i = i + 1; + /// Increment index, we are done with this element. + continue; + } + + if (newLength == trim.start) { + /// Current element IS the start element. + /// For start we want to add a zero length element. + currentPath->moveToStartPoint(element.vertex); + runningLength = newLength; + i = i + 1; + /// Increment index, we are done with this element. + continue; + } + + if (runningLength < trim.start && trim.start < newLength && currentPath->elements.size() == 0) { + /// The start of the trim is between this element and the previous, trim. + /// Get previous element. + auto previousElement = pathElements[i - 1]; + /// Trim it + auto trimLength = trim.start - runningLength; + auto trimResults = element.splitElementAtPosition(previousElement, trimLength); + /// Add the right span start. + currentPath->moveToStartPoint(trimResults.rightSpan.start.vertex); + + pathElements[i] = trimResults.rightSpan.end; + pathElements[i - 1] = trimResults.rightSpan.start; + runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start); + /// Dont increment index or the current length, the end of this path can be within this span. + continue; + } + + if (trim.start < newLength && newLength < trim.end) { + /// Element lies within the trim span. + currentPath->addElement(element); + runningLength = newLength; + i = i + 1; + continue; + } + + if (newLength == trim.end) { + /// Element is the end element. + /// The element could have a new length if it's added right after the start node. + currentPath->addElement(element); + /// We are done with this span. + runningLength = newLength; + i = i + 1; + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + if (runningLength < trim.end && trim.end < newLength) { + /// New element must be cut for end. + /// Get previous element. + auto previousElement = pathElements[i - 1]; + /// Trim it + auto trimLength = trim.end - runningLength; + auto trimResults = element.splitElementAtPosition(previousElement, trimLength); + /// Add the left span end. + + currentPath->updateVertex(trimResults.leftSpan.start.vertex, (int)currentPath->elements.size() - 1, false); + currentPath->addElement(trimResults.leftSpan.end); + + pathElements[i] = trimResults.rightSpan.end; + pathElements[i - 1] = trimResults.rightSpan.start; + runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start); + /// Dont increment index or the current length, the start of the next path can be within this span. + /// We are done with this span. + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + paths.push_back(currentPath); + currentPath = std::make_shared(); + if (remainingPositions.size() > 0) { + trim = remainingPositions[0]; + remainingPositions.erase(remainingPositions.begin()); + } else { + finishedTrimming = true; + } + } + return paths; +} + +BezierPath::BezierPath(CurveVertex const &startPoint) : +_contents(std::make_shared(startPoint)) { +} + +BezierPath::BezierPath() : +_contents(std::make_shared()) { +} + +BezierPath::BezierPath(lottiejson11::Json const &jsonAny) noexcept(false) : +_contents(std::make_shared(jsonAny)) { +} + +lottiejson11::Json BezierPath::toJson() const { + return _contents->toJson(); +} + +float BezierPath::length() { + return _contents->length(); +} + +void BezierPath::moveToStartPoint(CurveVertex const &vertex) { + _contents->moveToStartPoint(vertex); +} + +void BezierPath::addVertex(CurveVertex const &vertex) { + _contents->addVertex(vertex); +} + +void BezierPath::reserveCapacity(size_t capacity) { + _contents->reserveCapacity(capacity); +} + +void BezierPath::setElementCount(size_t count) { + _contents->setElementCount(count); +} + +void BezierPath::invalidateLength() { + _contents->invalidateLength(); +} + +void BezierPath::addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) { + _contents->addCurve(toPoint, outTangent, inTangent); +} + +void BezierPath::addLine(Vector2D const &toPoint) { + _contents->addLine(toPoint); +} + +void BezierPath::close() { + _contents->close(); +} + +void BezierPath::addElement(PathElement const &pathElement) { + _contents->addElement(pathElement); +} + +void BezierPath::updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) { + _contents->updateVertex(vertex, atIndex, remeasure); +} + +std::vector BezierPath::trim(float fromLength, float toLength, float offsetLength) { + std::vector result; + + auto resultContents = _contents->trim(fromLength, toLength, offsetLength); + for (const auto &resultContent : resultContents) { + result.emplace_back(resultContent); + } + + return result; +} + +std::vector const &BezierPath::elements() const { + return _contents->elements; +} + +std::vector &BezierPath::mutableElements() { + return _contents->elements; +} + +std::optional const &BezierPath::closed() const { + return _contents->closed; +} +void BezierPath::setClosed(std::optional const &closed) { + _contents->closed = closed; +} + +std::shared_ptr BezierPath::cgPath() const { + return _contents->cgPath(); +} + +BezierPath BezierPath::copyUsingTransform(Transform2D const &transform) const { + if (transform == Transform2D::identity()) { + return (*this); + } + BezierPath result; + result._contents->closed = _contents->closed; + result.reserveCapacity(_contents->elements.size()); + for (const auto &element : _contents->elements) { + result._contents->elements.emplace_back(element.vertex.transformed(transform)); + } + return result; +} + +BezierPath::BezierPath(std::shared_ptr contents) : +_contents(contents) { +} + +BezierPathsBoundingBoxContext::BezierPathsBoundingBoxContext() : +pointsX((float *)malloc(1024 * 4)), +pointsY((float *)malloc(1024 * 4)), +pointsSize(1024) { +} + +BezierPathsBoundingBoxContext::~BezierPathsBoundingBoxContext() { + free(pointsX); + free(pointsY); +} + +static CGRect calculateBoundingRectOpt(float const *pointsX, float const *pointsY, int count) { + float minX = 0.0; + float maxX = 0.0; + vDSP_minv(pointsX, 1, &minX, count); + vDSP_maxv(pointsX, 1, &maxX, count); + + float minY = 0.0; + float maxY = 0.0; + vDSP_minv(pointsY, 1, &minY, count); + vDSP_maxv(pointsY, 1, &maxY, count); + + return CGRect(minX, minY, maxX - minX, maxY - minY); +} + +CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector const &paths) { + int pointCount = 0; + + float *pointsX = context.pointsX; + float *pointsY = context.pointsY; + int pointsSize = context.pointsSize; + + for (const auto &path : paths) { + PathElement const *pathElements = path.elements().data(); + int pathElementCount = (int)path.elements().size(); + + for (int i = 0; i < pathElementCount; i++) { + const auto &element = pathElements[i]; + + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 1) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)element.vertex.point.x; + pointsY[pointCount] = (float)element.vertex.point.y; + pointCount++; + + if (i != 0) { + const auto &previousElement = pathElements[i - 1]; + if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + } else { + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 2) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)previousElement.vertex.outTangent.x; + pointsY[pointCount] = (float)previousElement.vertex.outTangent.y; + pointCount++; + pointsX[pointCount] = (float)element.vertex.inTangent.x; + pointsY[pointCount] = (float)element.vertex.inTangent.y; + pointCount++; + } + } + } + } + + context.pointsX = pointsX; + context.pointsY = pointsY; + context.pointsSize = pointsSize; + + if (pointCount == 0) { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + + return calculateBoundingRectOpt(pointsX, pointsY, pointCount); +} + +CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, BezierPath const &path) { + int pointCount = 0; + + float *pointsX = context.pointsX; + float *pointsY = context.pointsY; + int pointsSize = context.pointsSize; + + PathElement const *pathElements = path.elements().data(); + int pathElementCount = (int)path.elements().size(); + + for (int i = 0; i < pathElementCount; i++) { + const auto &element = pathElements[i]; + + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 1) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)element.vertex.point.x; + pointsY[pointCount] = (float)element.vertex.point.y; + pointCount++; + + if (i != 0) { + const auto &previousElement = pathElements[i - 1]; + if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + } else { + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 2) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)previousElement.vertex.outTangent.x; + pointsY[pointCount] = (float)previousElement.vertex.outTangent.y; + pointCount++; + pointsX[pointCount] = (float)element.vertex.inTangent.x; + pointsY[pointCount] = (float)element.vertex.inTangent.y; + pointCount++; + } + } + } + + context.pointsX = pointsX; + context.pointsY = pointsY; + context.pointsSize = pointsSize; + + if (pointCount == 0) { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + + return calculateBoundingRectOpt(pointsX, pointsY, pointCount); +} + +CGRect bezierPathsBoundingBox(std::vector const &paths) { + int pointCount = 0; + + float *pointsX = (float *)malloc(128 * 4); + float *pointsY = (float *)malloc(128 * 4); + int pointsSize = 128; + + for (const auto &path : paths) { + PathElement const *pathElements = path.elements().data(); + int pathElementCount = (int)path.elements().size(); + + for (int i = 0; i < pathElementCount; i++) { + const auto &element = pathElements[i]; + + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 1) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)element.vertex.point.x; + pointsY[pointCount] = (float)element.vertex.point.y; + pointCount++; + + if (i != 0) { + const auto &previousElement = pathElements[i - 1]; + if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + } else { + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 2) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)previousElement.vertex.outTangent.x; + pointsY[pointCount] = (float)previousElement.vertex.outTangent.y; + pointCount++; + pointsX[pointCount] = (float)element.vertex.inTangent.x; + pointsY[pointCount] = (float)element.vertex.inTangent.y; + pointCount++; + } + } + } + } + + free(pointsX); + free(pointsY); + + if (pointCount == 0) { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + + return calculateBoundingRectOpt(pointsX, pointsY, pointCount); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.cpp new file mode 100644 index 00000000000..31df7039562 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.cpp @@ -0,0 +1,5 @@ +#include "CompoundBezierPath.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp new file mode 100644 index 00000000000..58d02eb283f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp @@ -0,0 +1,163 @@ +#ifndef CompoundBezierPath_hpp +#define CompoundBezierPath_hpp + +#include + +namespace lottie { + +/// A collection of BezierPath objects that can be trimmed and added. +/// +class CompoundBezierPath: public std::enable_shared_from_this { +public: + CompoundBezierPath() : + paths({}) { + } + + CompoundBezierPath(BezierPath const &path) : + paths({ path }) { + } + + CompoundBezierPath(std::vector paths_, std::optional length_) : + paths(paths_), _length(length_) { + } + + CompoundBezierPath(std::vector paths_) : + paths(paths_) { + } + +public: + std::vector paths; + + float length() { + if (_length.has_value()) { + return _length.value(); + } else { + float l = 0.0; + for (auto &path : paths) { + l += path.length(); + } + _length = l; + return l; + } + } + +private: + std::optional _length; + +public: + std::shared_ptr addingPath(BezierPath const &path) const { + auto newPaths = paths; + newPaths.push_back(path); + return std::make_shared(newPaths); + } + + void appendPath(BezierPath const &path) { + paths.push_back(path); + _length.reset(); + } + + std::shared_ptr combine(std::shared_ptr compoundBezier) { + auto newPaths = paths; + for (const auto &path : compoundBezier->paths) { + newPaths.push_back(path); + } + return std::make_shared(newPaths); + } + + std::shared_ptr trim(float fromPosition, float toPosition, float offset) { + if (fromPosition == toPosition) { + return std::make_shared(); + } + + float lengthValue = length(); + + /// Normalize lengths to the curve length. + float startPosition = fmod(fromPosition + offset, 1.0); + float endPosition = fmod(toPosition + offset, 1.0); + + if (startPosition < 0.0) { + startPosition = 1.0 + startPosition; + } + + if (endPosition < 0.0) { + endPosition = 1.0 + endPosition; + } + + if (startPosition == 1.0) { + startPosition = 0.0; + } + if (endPosition == 0.0) { + endPosition = 1.0; + } + + if ((startPosition == 0.0 && endPosition == 1.0) || + startPosition == endPosition || + (startPosition == 1.0 && endPosition == 0.0)) { + /// The trim encompasses the entire path. Return. + return shared_from_this(); + } + + std::vector positions; + if (endPosition < startPosition) { + positions = { + BezierTrimPathPosition(0.0, endPosition * lengthValue), + BezierTrimPathPosition(startPosition * lengthValue, lengthValue) + }; + } else { + positions = { BezierTrimPathPosition(startPosition * lengthValue, endPosition * lengthValue) }; + } + + auto compoundPath = std::make_shared(); + auto trim = positions[0]; + positions.erase(positions.begin()); + float pathStartPosition = 0.0; + + bool finishedTrimming = false; + int i = 0; + + while (!finishedTrimming) { + if (paths.size() <= i) { + /// Rounding errors + finishedTrimming = true; + continue; + } + auto path = paths[i]; + + auto pathEndPosition = pathStartPosition + path.length(); + + if (pathEndPosition < trim.start) { + /// Path is not included in the trim, continue. + pathStartPosition = pathEndPosition; + i = i + 1; + continue; + } else if (trim.start <= pathStartPosition && pathEndPosition <= trim.end) { + /// Full Path is inside of trim. Add full path. + compoundPath = compoundPath->addingPath(path); + } else { + auto trimPaths = path.trim(trim.start > pathStartPosition ? (trim.start - pathStartPosition) : 0, trim.end < pathEndPosition ? (trim.end - pathStartPosition) : path.length(), 0.0); + if (!trimPaths.empty()) { + compoundPath = compoundPath->addingPath(trimPaths[0]); + } + } + + if (trim.end <= pathEndPosition) { + /// We are done with the current trim. + /// Advance trim but remain on the same path in case the next trim overlaps it. + if (positions.size() > 0) { + trim = positions[0]; + positions.erase(positions.begin()); + } else { + finishedTrimming = true; + } + } else { + pathStartPosition = pathEndPosition; + i = i + 1; + } + } + return compoundPath; + } +}; + +} + +#endif /* CompoundBezierPath_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.cpp new file mode 100644 index 00000000000..695b8dc4e91 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.cpp @@ -0,0 +1,5 @@ +#include "CoordinateSpace.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.hpp new file mode 100644 index 00000000000..37a7b7054ca --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.hpp @@ -0,0 +1,13 @@ +#ifndef CoordinateSpace_hpp +#define CoordinateSpace_hpp + +namespace lottie { + +enum class CoordinateSpace { + Type2d, + Type3d +}; + +} + +#endif /* CoordinateSpace_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.cpp new file mode 100644 index 00000000000..cbdd11762a6 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.cpp @@ -0,0 +1,5 @@ +#include + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.cpp new file mode 100644 index 00000000000..af059629cd1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.cpp @@ -0,0 +1,5 @@ +#include + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.cpp new file mode 100644 index 00000000000..8f90bfc95e5 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.cpp @@ -0,0 +1,5 @@ +#include "AnimationKeypath.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.hpp new file mode 100644 index 00000000000..8679106d129 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.hpp @@ -0,0 +1,51 @@ +#ifndef AnimationKeypath_hpp +#define AnimationKeypath_hpp + +#include +#include + +namespace lottie { + +/// `AnimationKeypath` is an object that describes a keypath search for nodes in the +/// animation JSON. `AnimationKeypath` matches views and properties inside of `AnimationView` +/// to their backing `Animation` model by name. +/// +/// A keypath can be used to set properties on an existing animation, or can be validated +/// with an existing `Animation`. +/// +/// `AnimationKeypath` can describe a specific object, or can use wildcards for fuzzy matching +/// of objects. Acceptable wildcards are either "*" (star) or "**" (double star). +/// Single star will search a single depth for the next object. +/// Double star will search any depth. +/// +/// Read More at https://airbnb.io/lottie/#/ios?id=dynamic-animation-properties +/// +/// EG: +/// @"Layer.Shape Group.Stroke 1.Color" +/// Represents a specific color node on a specific stroke. +/// +/// @"**.Stroke 1.Color" +/// Represents the color node for every Stroke named "Stroke 1" in the animation. +class AnimationKeypath { +public: + /// Creates a keypath from a dot-separated string. The string is separated by "." + /*public init(keypath: String) { + keys = keypath.components(separatedBy: ".") + }*/ + + /// Creates a keypath from a list of strings. + AnimationKeypath(std::vector const &keys) : + _keys(keys) { + } + + std::vector const &keys() const { + return _keys; + } + +private: + std::vector _keys; +}; + +} + +#endif /* AnimationKeypath_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.cpp new file mode 100644 index 00000000000..f56124c957b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.cpp @@ -0,0 +1,5 @@ +#include "AnyValueProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.hpp new file mode 100644 index 00000000000..a9b708afbdc --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.hpp @@ -0,0 +1,38 @@ +#ifndef AnyValueProvider_hpp +#define AnyValueProvider_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Public/Primitives/AnyValue.hpp" + +#include +#include + +namespace lottie { + +/// `AnyValueProvider` is a protocol that return animation data for a property at a +/// given time. Every frame an `AnimationView` queries all of its properties and asks +/// if their ValueProvider has an update. If it does the AnimationView will read the +/// property and update that portion of the animation. +/// +/// Value Providers can be used to dynamically set animation properties at run time. +class AnyValueProvider { +public: + /// The Type of the value provider + virtual AnyValue::Type valueType() const = 0; + + /// Asks the provider if it has an update for the given frame. + virtual bool hasUpdate(AnimationFrameTime frame) const = 0; +}; + +/// A base protocol for strongly-typed Value Providers +template +class ValueProvider: public AnyValueProvider { +public: + /// Asks the provider to update the container with its value for the frame. + virtual T value(AnimationFrameTime frame) = 0; +}; + +} + +#endif /* AnyValueProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.cpp new file mode 100644 index 00000000000..a6a4774df78 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.cpp @@ -0,0 +1,5 @@ +#include "AnimationFontProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.hpp new file mode 100644 index 00000000000..c14d46f8d9b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.hpp @@ -0,0 +1,33 @@ +#ifndef AnimationFontProvider_hpp +#define AnimationFontProvider_hpp + +#include "Lottie/Public/Primitives/CTFont.hpp" + +#include + +namespace lottie { + +/// Font provider is a protocol that is used to supply fonts to `AnimationView`. +/// +class AnimationFontProvider { +public: + virtual std::shared_ptr fontFor(std::string const &family, float size) = 0; +}; + +/// Default Font provider. +class DefaultFontProvider: public AnimationFontProvider { +public: + DefaultFontProvider() { + } + + virtual ~DefaultFontProvider() = default; + + virtual std::shared_ptr fontFor(std::string const &family, float size) override { + //CTFontCreateWithName(family as CFString, size, nil) + return nullptr; + } +}; + +} + +#endif /* AnimationFontProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/ImageProvider/AnimationImageProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/ImageProvider/AnimationImageProvider.hpp new file mode 100644 index 00000000000..7efe74f8c8f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/ImageProvider/AnimationImageProvider.hpp @@ -0,0 +1,16 @@ +#ifndef AnimationImageProvider_hpp +#define AnimationImageProvider_hpp + +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Private/Model/Assets/ImageAsset.hpp" + +namespace lottie { + +class AnimationImageProvider { +public: + virtual std::shared_ptr imageForAsset(ImageAsset const &imageAsset) = 0; +}; + +} + +#endif /* AnimationImageProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.cpp new file mode 100644 index 00000000000..aa58f615c44 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.cpp @@ -0,0 +1,15 @@ +#include "Interpolatable.hpp" + +namespace lottie { + +float remapFloat(float value, float fromLow, float fromHigh, float toLow, float toHigh) { + return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow); +} + +float clampFloat(float value, float a, float b) { + float minValue = a <= b ? a : b; + float maxValue = a <= b ? b : a; + return std::max(std::min(value, maxValue), minValue); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.hpp new file mode 100644 index 00000000000..b57223466ab --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.hpp @@ -0,0 +1,14 @@ +#ifndef Interpolatable_hpp +#define Interpolatable_hpp + +#include + +namespace lottie { + +float remapFloat(float value, float fromLow, float fromHigh, float toLow, float toHigh); + +float clampFloat(float value, float a, float b); + +} + +#endif /* Interpolatable_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.cpp new file mode 100644 index 00000000000..e699e6daf81 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.cpp @@ -0,0 +1,5 @@ +#include "Keyframe.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp new file mode 100644 index 00000000000..a3d12b226f7 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp @@ -0,0 +1,259 @@ +#ifndef Keyframe_hpp +#define Keyframe_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include +#include "Lottie/Public/Keyframes/Interpolatable.hpp" +#include "Lottie/Public/Keyframes/ValueInterpolators.hpp" + +#include + +namespace lottie { + +/// A keyframe with a single value, and timing information +/// about when the value should be displayed and how it +/// should be interpolated. +template +class Keyframe { +public: + /// Initialize a value-only keyframe with no time data. + Keyframe( + T const &value_, + std::optional spatialInTangent_, + std::optional spatialOutTangent_ + ) : + value(value_), + time(0), + isHold(true), + inTangent(std::nullopt), + outTangent(std::nullopt), + spatialInTangent(spatialInTangent_), + spatialOutTangent(spatialOutTangent_) { + } + + /// Initialize a keyframe + Keyframe( + T value_, + AnimationFrameTime time_, + bool isHold_, + std::optional inTangent_, + std::optional outTangent_, + std::optional spatialInTangent_, + std::optional spatialOutTangent_ + ) : + value(value_), + time(time_), + isHold(isHold_), + inTangent(inTangent_), + outTangent(outTangent_), + spatialInTangent(spatialInTangent_), + spatialOutTangent(spatialOutTangent_) { + } + + bool operator==(Keyframe const &rhs) { + return value == rhs.value + && time == rhs.time + && isHold == rhs.isHold + && inTangent == rhs.inTangent + && outTangent == rhs.outTangent + && spatialInTangent == rhs.spatialInTangent + && spatialOutTangent == rhs.spatialOutTangent; + } + + bool operator!=(Keyframe const &rhs) { + return !(*this == rhs); + } + +public: + T interpolate(Keyframe const &to, float progress) { + std::optional spatialOutTangent2d; + if (spatialOutTangent) { + spatialOutTangent2d = Vector2D(spatialOutTangent->x, spatialOutTangent->y); + } + std::optional spatialInTangent2d; + if (to.spatialInTangent) { + spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y); + } + return ValueInterpolator::interpolate(value, to.value, progress, spatialOutTangent2d, spatialInTangent2d); + } + + /// Interpolates the keyTime into a value from 0-1 + float interpolatedProgress(Keyframe const &to, float keyTime) { + float startTime = time; + float endTime = to.time; + if (keyTime <= startTime) { + return 0.0; + } + if (endTime <= keyTime) { + return 1.0; + } + + if (isHold) { + return 0.0; + } + + Vector2D outTanPoint = Vector2D::Zero(); + if (outTangent.has_value()) { + outTanPoint = outTangent.value(); + } + Vector2D inTanPoint = Vector2D(1.0, 1.0); + if (to.inTangent.has_value()) { + inTanPoint = to.inTangent.value(); + } + float progress = remapFloat(keyTime, startTime, endTime, 0.0f, 1.0f); + if (!outTanPoint.isZero() || inTanPoint != Vector2D(1.0f, 1.0f)) { + /// Cubic interpolation + progress = cubicBezierInterpolate(progress, Vector2D::Zero(), outTanPoint, inTanPoint, Vector2D(1.0, 1.0)); + } + return progress; + } + +public: + /// The value of the keyframe + T value; + /// The time in frames of the keyframe. + AnimationFrameTime time; + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + bool isHold; + /// The in tangent for the time interpolation curve. + std::optional inTangent; + /// The out tangent for the time interpolation curve. + std::optional outTangent; + + /// The spatial in tangent of the vector. + std::optional spatialInTangent; + /// The spatial out tangent of the vector. + std::optional spatialOutTangent; +}; + +template +class KeyframeData { +public: + KeyframeData( + std::optional startValue_, + std::optional endValue_, + std::optional time_, + std::optional hold_, + std::optional inTangent_, + std::optional outTangent_, + std::optional spatialInTangent_, + std::optional spatialOutTangent_ + ) : + startValue(startValue_), + endValue(endValue_), + time(time_), + hold(hold_), + inTangent(inTangent_), + outTangent(outTangent_), + spatialInTangent(spatialInTangent_), + spatialOutTangent(spatialOutTangent_) { + } + + explicit KeyframeData(lottiejson11::Json const &json) noexcept(false) { + if (!json.is_object()) { + throw LottieParsingException(); + } + + if (const auto startValueData = getOptionalAny(json.object_items(), "s")) { + startValue = T(startValueData.value()); + } + + if (const auto endValueData = getOptionalAny(json.object_items(), "e")) { + endValue = T(endValueData.value()); + } + + if (const auto timeValue = getOptionalDouble(json.object_items(), "t")) { + time = (float)timeValue.value(); + } + + hold = getOptionalInt(json.object_items(), "h"); + + if (const auto inTangentData = getOptionalObject(json.object_items(), "i")) { + inTangent = Vector2D(inTangentData.value()); + } + + if (const auto outTangentData = getOptionalObject(json.object_items(), "o")) { + outTangent = Vector2D(outTangentData.value()); + } + + if (const auto spatialInTangentData = getOptionalAny(json.object_items(), "ti")) { + spatialInTangent = Vector3D(spatialInTangentData.value()); + } + + if (const auto spatialOutTangentData = getOptionalAny(json.object_items(), "to")) { + spatialOutTangent = Vector3D(spatialOutTangentData.value()); + } + + if (const auto nDataValue = getOptionalAny(json.object_items(), "n")) { + nData = nDataValue.value(); + } + } + + lottiejson11::Json::object toJson() const { + lottiejson11::Json::object result; + + if (startValue.has_value()) { + result.insert(std::make_pair("s", startValue->toJson())); + } + if (endValue.has_value()) { + result.insert(std::make_pair("e", endValue->toJson())); + } + if (time.has_value()) { + result.insert(std::make_pair("t", time.value())); + } + if (hold.has_value()) { + result.insert(std::make_pair("h", hold.value())); + } + if (inTangent.has_value()) { + result.insert(std::make_pair("i", inTangent->toJson())); + } + if (outTangent.has_value()) { + result.insert(std::make_pair("o", outTangent->toJson())); + } + if (spatialInTangent.has_value()) { + result.insert(std::make_pair("ti", spatialInTangent->toJson())); + } + if (spatialOutTangent.has_value()) { + result.insert(std::make_pair("to", spatialOutTangent->toJson())); + } + if (nData.has_value()) { + result.insert(std::make_pair("n", nData.value())); + } + + return result; + } + +public: + /// The start value of the keyframe + std::optional startValue; + /// The End value of the keyframe. Note: Newer versions animation json do not have this field. + std::optional endValue; + /// The time in frames of the keyframe. + std::optional time; + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + std::optional hold; + + /// The in tangent for the time interpolation curve. + std::optional inTangent; + /// The out tangent for the time interpolation curve. + std::optional outTangent; + + /// The spacial in tangent of the vector. + std::optional spatialInTangent; + /// The spacial out tangent of the vector. + std::optional spatialOutTangent; + + std::optional nData; + + bool isHold() const { + if (hold.has_value()) { + return hold.value() > 0; + } else { + return false; + } + } +}; + +} + +#endif /* Keyframe_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.cpp new file mode 100644 index 00000000000..650d25e0411 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.cpp @@ -0,0 +1,20 @@ +#include "ValueInterpolators.hpp" + +#include + +namespace lottie { + +void batchInterpolate(std::vector const &from, std::vector const &to, BezierPath &resultPath, float amount) { + int elementCount = (int)from.size(); + if (elementCount > (int)to.size()) { + elementCount = (int)to.size(); + } + + static_assert(sizeof(PathElement) == 4 * 2 * 3); + + resultPath.setElementCount(elementCount); + float floatAmount = (float)amount; + vDSP_vintb((float *)&from[0], 1, (float *)&to[0], 1, &floatAmount, (float *)&resultPath.elements()[0], 1, elementCount * 2 * 3); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp new file mode 100644 index 00000000000..4cc473bfc90 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp @@ -0,0 +1,227 @@ +#ifndef ValueInterpolators_hpp +#define ValueInterpolators_hpp + +#include +#import +#include +#include "Lottie/Private/Model/Text/TextDocument.hpp" +#include "Lottie/Public/Primitives/GradientColorSet.hpp" +#include "Lottie/Public/Primitives/DashPattern.hpp" + +#include +#include + +namespace lottie { + +template +struct ValueInterpolator { +}; + +template<> +struct ValueInterpolator { +public: + static float interpolate(float value, float to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + return value + ((to - value) * amount); + } +}; + +template<> +struct ValueInterpolator { +public: + static Vector1D interpolate(Vector1D const &value, Vector1D const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + return Vector1D(ValueInterpolator::interpolate(value.value, to.value, amount, spatialOutTangent, spatialInTangent)); + } +}; + +template<> +struct ValueInterpolator { +public: + static Vector2D interpolate(Vector2D const &value, Vector2D const &to, float amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) { + auto cp1 = value + spatialOutTangent; + auto cp2 = to + spatialInTangent; + + return value.interpolate(to, cp1, cp2, amount); + } + + static Vector2D interpolate(Vector2D const &value, Vector2D const &to, float amount) { + return value.interpolate(to, amount); + } +}; + +template<> +struct ValueInterpolator { +public: + static Vector3D interpolate(Vector3D const &value, Vector3D const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + if (spatialOutTangent && spatialInTangent) { + Vector2D from2d(value.x, value.y); + Vector2D to2d(to.x, to.y); + + auto cp1 = from2d + spatialOutTangent.value(); + auto cp2 = to2d + spatialInTangent.value(); + + Vector2D result2d = from2d.interpolate(to2d, cp1, cp2, amount); + + return Vector3D( + result2d.x, + result2d.y, + ValueInterpolator::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent) + ); + } + + return Vector3D( + ValueInterpolator::interpolate(value.x, to.x, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.y, to.y, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent) + ); + } +}; + +template<> +struct ValueInterpolator { +public: + static Color interpolate(Color const &value, Color const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + return Color( + ValueInterpolator::interpolate(value.r, to.r, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.g, to.g, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.b, to.b, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.a, to.a, amount, spatialOutTangent, spatialInTangent) + ); + } +}; + +void batchInterpolate(std::vector const &from, std::vector const &to, BezierPath &resultPath, float amount); + +template<> +struct ValueInterpolator { +public: + static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, float amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) { + return CurveVertex::absolute( + ValueInterpolator::interpolate(value.point, to.point, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.inTangent, to.inTangent, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.outTangent, to.outTangent, amount, spatialOutTangent, spatialInTangent) + ); + } + + static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, float amount) { + return CurveVertex::absolute( + ValueInterpolator::interpolate(value.point, to.point, amount), + ValueInterpolator::interpolate(value.inTangent, to.inTangent, amount), + ValueInterpolator::interpolate(value.outTangent, to.outTangent, amount) + ); + } +}; + +template<> +struct ValueInterpolator { +public: + static BezierPath interpolate(BezierPath const &value, BezierPath const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + BezierPath newPath; + newPath.reserveCapacity(std::max(value.elements().size(), to.elements().size())); + //TODO:probably a bug in the upstream code, uncomment + //newPath.setClosed(value.closed()); + size_t elementCount = std::min(value.elements().size(), to.elements().size()); + + if (spatialInTangent && spatialOutTangent) { + Vector2D spatialInTangentValue = spatialInTangent.value(); + Vector2D spatialOutTangentValue = spatialOutTangent.value(); + + for (size_t i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + newPath.addVertex(ValueInterpolator::interpolate(fromVertex, toVertex, amount, spatialOutTangentValue, spatialInTangentValue)); + } + } else { + for (size_t i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + newPath.addVertex(ValueInterpolator::interpolate(fromVertex, toVertex, amount)); + } + } + return newPath; + } + + static void setInplace(BezierPath const &value, BezierPath &resultPath) { + resultPath.reserveCapacity(value.elements().size()); + resultPath.setElementCount(value.elements().size()); + resultPath.invalidateLength(); + + memcpy(resultPath.mutableElements().data(), value.elements().data(), value.elements().size() * sizeof(PathElement)); + } + + static void interpolateInplace(BezierPath const &value, BezierPath const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent, BezierPath &resultPath) { + /*if (value.elements().size() != to.elements().size()) { + return to; + }*/ + + //TODO:probably a bug in the upstream code, uncomment + //newPath.setClosed(value.closed()); + + int elementCount = (int)std::min(value.elements().size(), to.elements().size()); + + resultPath.reserveCapacity(std::max(value.elements().size(), to.elements().size())); + resultPath.setElementCount(elementCount); + resultPath.invalidateLength(); + + if (spatialInTangent && spatialOutTangent) { + Vector2D spatialInTangentValue = spatialInTangent.value(); + Vector2D spatialOutTangentValue = spatialOutTangent.value(); + + for (int i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + auto vertex = ValueInterpolator::interpolate(fromVertex, toVertex, amount, spatialOutTangentValue, spatialInTangentValue); + + resultPath.updateVertex(vertex, i, false); + } + } else { + batchInterpolate(value.elements(), to.elements(), resultPath, amount); + } + } +}; + +template<> +struct ValueInterpolator { +public: + static TextDocument interpolate(TextDocument const &value, TextDocument const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + if (amount == 1.0) { + return to; + } else { + return value; + } + } +}; + +template<> +struct ValueInterpolator { +public: + static GradientColorSet interpolate(GradientColorSet const &value, GradientColorSet const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + assert(value.colors.size() == to.colors.size()); + std::vector colors; + size_t colorCount = std::min(value.colors.size(), to.colors.size()); + for (size_t i = 0; i < colorCount; i++) { + colors.push_back(ValueInterpolator::interpolate(value.colors[i], to.colors[i], amount, spatialOutTangent, spatialInTangent)); + } + return GradientColorSet(colors); + } +}; + +template<> +struct ValueInterpolator { +public: + static DashPattern interpolate(DashPattern const &value, DashPattern const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + assert(value.values.size() == to.values.size()); + std::vector values; + size_t colorCount = std::min(value.values.size(), to.values.size()); + for (size_t i = 0; i < colorCount; i++) { + values.push_back(ValueInterpolator::interpolate(value.values[i], to.values[i], amount, spatialOutTangent, spatialInTangent)); + } + return DashPattern(std::move(values)); + } +}; + +} + +#endif /* ValueInterpolators_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.cpp new file mode 100644 index 00000000000..c804b75b106 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.cpp @@ -0,0 +1,5 @@ +#include "AnimationTime.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.hpp new file mode 100644 index 00000000000..a2a7ec3fdd1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.hpp @@ -0,0 +1,14 @@ +#ifndef AnimationTime_hpp +#define AnimationTime_hpp + +namespace lottie { + +/// Defines animation time in Frames (Seconds * Framerate). +typedef float AnimationFrameTime; + +/// Defines animation time by a progress from 0 (beginning of the animation) to 1 (end of the animation) +typedef float AnimationProgressTime; + +} + +#endif /* AnimationTime_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.cpp new file mode 100644 index 00000000000..1af508b3de1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.cpp @@ -0,0 +1,5 @@ +#include "AnyValue.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp new file mode 100644 index 00000000000..f730fad6528 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp @@ -0,0 +1,245 @@ +#ifndef AnyValue_hpp +#define AnyValue_hpp + +#include +#import +#include +#include "Lottie/Private/Model/Text/TextDocument.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" +#include "Lottie/Private/Model/Objects/DashElement.hpp" + +#include +#include + +namespace lottie { + +class AnyValue { +public: + enum class Type { + Float, + Vector1D, + Vector2D, + Vector3D, + Color, + BezierPath, + TextDocument, + GradientColorSet, + DashPattern + }; + +public: + AnyValue(float value) : + _type(Type::Float), + _floatValue(value) { + } + + AnyValue(Vector1D const &value) : + _type(Type::Vector1D), + _vector1DValue(value) { + } + + AnyValue(Vector2D const &value) : + _type(Type::Vector2D), + _vector2DValue(value) { + } + + AnyValue(Vector3D const & value) : + _type(Type::Vector3D), + _vector3DValue(value) { + } + + AnyValue(Color const &value) : + _type(Type::Color), + _colorValue(value) { + } + + AnyValue(BezierPath const &value) : + _type(Type::BezierPath), + _bezierPathValue(value) { + } + + AnyValue(TextDocument const &value) : + _type(Type::TextDocument), + _textDocumentValue(value) { + } + + AnyValue(GradientColorSet const &value) : + _type(Type::GradientColorSet), + _gradientColorSetValue(value) { + } + + AnyValue(DashPattern const &value) : + _type(Type::DashPattern), + _dashPatternValue(value) { + } + + template::value>> + float get() { + return asFloat(); + } + + template::value>> + Vector1D get() { + return asVector1D(); + } + + template::value>> + Vector2D get() { + return asVector2D(); + } + + template::value>> + Vector3D get() { + return asVector3D(); + } + + template::value>> + Color get() { + return asColor(); + } + + template::value>> + BezierPath get() { + return asBezierPath(); + } + + template::value>> + TextDocument get() { + return asTextDocument(); + } + + template::value>> + GradientColorSet get() { + return asGradientColorSet(); + } + + template::value>> + DashPattern get() { + return asDashPattern(); + } + +public: + Type type() { + return _type; + } + + float asFloat() { + return _floatValue.value(); + } + + Vector1D asVector1D() { + return _vector1DValue.value(); + } + + Vector2D asVector2D() { + return _vector2DValue.value(); + } + + Vector3D asVector3D() { + return _vector3DValue.value(); + } + + Color asColor() { + return _colorValue.value(); + } + + BezierPath asBezierPath() { + return _bezierPathValue.value(); + } + + TextDocument asTextDocument() { + return _textDocumentValue.value(); + } + + GradientColorSet asGradientColorSet() { + return _gradientColorSetValue.value(); + } + + DashPattern asDashPattern() { + return _dashPatternValue.value(); + } + +private: + Type _type; + + std::optional _floatValue; + std::optional _vector1DValue; + std::optional _vector2DValue; + std::optional _vector3DValue; + std::optional _colorValue; + std::optional _bezierPathValue; + std::optional _textDocumentValue; + std::optional _gradientColorSetValue; + std::optional _dashPatternValue; +}; + +template +struct AnyValueType { +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Float; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Vector1D; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Vector2D; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Vector3D; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Color; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::BezierPath; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::TextDocument; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::GradientColorSet; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::DashPattern; + } +}; + +} + +#endif /* AnyValue_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.cpp new file mode 100644 index 00000000000..add81ca9734 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.cpp @@ -0,0 +1,37 @@ +#include "CALayer.hpp" + +namespace lottie { + +std::shared_ptr CAShapeLayer::renderableItem() { + if (!_path) { + return nullptr; + } + + std::optional fill; + if (_fillColor) { + fill = ShapeRenderableItem::Fill( + _fillColor.value(), + _fillRule + ); + } + + std::optional stroke; + if (_strokeColor) { + stroke = ShapeRenderableItem::Stroke( + _strokeColor.value(), + _lineWidth, + _lineJoin, + _lineCap, + _lineDashPhase, + _dashPattern + ); + } + + return std::make_shared( + _path, + fill, + stroke + ); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp new file mode 100644 index 00000000000..c0c0a610062 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp @@ -0,0 +1,212 @@ +#ifndef CALayer_hpp +#define CALayer_hpp + +#import +#include +#include +#include +#include "Lottie/Private/Model/ShapeItems/Fill.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include +#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" + +#include +#include +#include + +namespace lottie { + +class CALayer: public std::enable_shared_from_this { +public: + CALayer() { + } + + virtual ~CALayer() = default; + + void addSublayer(std::shared_ptr layer) { + _sublayers.push_back(layer); + } + + void insertSublayer(std::shared_ptr layer, int index) { + _sublayers.insert(_sublayers.begin() + index, layer); + } + + virtual bool implementsDraw() const { + return false; + } + + virtual bool isInvertedMatte() const { + return false; + } + + virtual std::shared_ptr renderableItem() { + return nullptr; + } + + bool isHidden() const { + return _isHidden; + } + void setIsHidden(bool isHidden) { + _isHidden = isHidden; + } + + float opacity() const { + return _opacity; + } + void setOpacity(float opacity) { + _opacity = opacity; + } + + Vector2D const &size() const { + return _size; + } + void setSize(Vector2D const &size) { + _size = size; + } + + Transform2D const &transform() const { + return _transform; + } + void setTransform(Transform2D const &transform) { + _transform = transform; + } + + std::shared_ptr const &mask() const { + return _mask; + } + void setMask(std::shared_ptr mask) { + _mask = mask; + } + + bool masksToBounds() const { + return _masksToBounds; + } + void setMasksToBounds(bool masksToBounds) { + _masksToBounds = masksToBounds; + } + + std::vector> const &sublayers() const { + return _sublayers; + } + + std::optional const &compositingFilter() const { + return _compositingFilter; + } + void setCompositingFilter(std::optional const &compositingFilter) { + _compositingFilter = compositingFilter; + } + +protected: + template + std::shared_ptr shared_from_base() { + return std::static_pointer_cast(shared_from_this()); + } + +private: + void removeSublayer(CALayer *layer) { + for (auto it = _sublayers.begin(); it != _sublayers.end(); it++) { + if (it->get() == layer) { + _sublayers.erase(it); + break; + } + } + } + +private: + std::vector> _sublayers; + bool _isHidden = false; + float _opacity = 1.0; + Vector2D _size = Vector2D(0.0, 0.0); + Transform2D _transform = Transform2D::identity(); + std::shared_ptr _mask; + bool _masksToBounds = false; + std::optional _compositingFilter; +}; + +class CAShapeLayer: public CALayer { +public: + CAShapeLayer() { + } + + virtual ~CAShapeLayer() = default; + + std::optional const &strokeColor() { + return _strokeColor; + } + void setStrokeColor(std::optional const &strokeColor) { + _strokeColor = strokeColor; + } + + std::optional const &fillColor() { + return _fillColor; + } + void setFillColor(std::optional const &fillColor) { + _fillColor = fillColor; + } + + FillRule fillRule() { + return _fillRule; + } + void setFillRule(FillRule fillRule) { + _fillRule = fillRule; + } + + std::shared_ptr const &path() const { + return _path; + } + void setPath(std::shared_ptr const &path) { + _path = path; + } + + float lineWidth() const { + return _lineWidth; + } + void setLineWidth(float lineWidth) { + _lineWidth = lineWidth; + } + + LineJoin lineJoin() const { + return _lineJoin; + } + void setLineJoin(LineJoin lineJoin) { + _lineJoin = lineJoin; + } + + LineCap lineCap() const { + return _lineCap; + } + void setLineCap(LineCap lineCap) { + _lineCap = lineCap; + } + + float lineDashPhase() const { + return _lineDashPhase; + } + void setLineDashPhase(float lineDashPhase) { + _lineDashPhase = lineDashPhase; + } + + std::vector const &dashPattern() const { + return _dashPattern; + } + void setDashPattern(std::vector const &dashPattern) { + _dashPattern = dashPattern; + } + + std::shared_ptr renderableItem() override; + +private: + std::optional _strokeColor; + std::optional _fillColor = Color(0.0, 0.0, 0.0, 1.0); + FillRule _fillRule = FillRule::NonZeroWinding; + std::shared_ptr _path; + float _lineWidth = 1.0; + LineJoin _lineJoin = LineJoin::Miter; + LineCap _lineCap = LineCap::Butt; + float _lineDashPhase = 0.0; + std::vector _dashPattern; +}; + +} + +#endif /* CALayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.cpp new file mode 100644 index 00000000000..e985e183926 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.cpp @@ -0,0 +1,193 @@ +#include + +#include + +namespace lottie { + +namespace { + +void addPointToBoundingRect(bool *isFirst, CGRect *rect, Vector2D const *point) { + if (*isFirst) { + *isFirst = false; + + rect->x = point->x; + rect->y = point->y; + rect->width = 0.0; + rect->height = 0.0; + + return; + } + if (point->x > rect->x + rect->width) { + rect->width = point->x - rect->x; + } + if (point->y > rect->y + rect->height) { + rect->height = point->y - rect->y; + } + if (point->x < rect->x) { + rect->width += rect->x - point->x; + rect->x = point->x; + } + if (point->y < rect->y) { + rect->height += rect->y - point->y; + rect->y = point->y; + } +} + +} + +Vector2D transformVector(Vector2D const &v, Transform2D const &m) { + float transformedX = m.rows().columns[0][0] * v.x + m.rows().columns[1][0] * v.y + m.rows().columns[2][0] * 1.0f; + float transformedY = m.rows().columns[0][1] * v.x + m.rows().columns[1][1] * v.y + m.rows().columns[2][1] * 1.0f; + return Vector2D(transformedX, transformedY); +} + +class CGPathImpl: public CGPath { +public: + CGPathImpl(); + virtual ~CGPathImpl(); + + virtual CGRect boundingBox() const override; + + virtual bool empty() const override; + + virtual std::shared_ptr copyUsingTransform(Transform2D const &transform) const override; + + virtual void addLineTo(Vector2D const &point) override; + virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) override; + virtual void moveTo(Vector2D const &point) override; + virtual void closeSubpath() override; + virtual void addRect(CGRect const &rect) override; + virtual void addPath(std::shared_ptr const &path) override; + virtual bool isEqual(CGPath *other) const override; + virtual void enumerate(std::function) override; + +private: + std::vector _items; +}; + +CGPathImpl::CGPathImpl() { +} + +CGPathImpl::~CGPathImpl() { +} + +CGRect CGPathImpl::boundingBox() const { + bool isFirst = true; + CGRect result(0.0, 0.0, 0.0, 0.0); + + for (const auto &item : _items) { + switch (item.type) { + case CGPathItem::Type::MoveTo: { + addPointToBoundingRect(&isFirst, &result, &item.points[0]); + break; + } + case CGPathItem::Type::LineTo: { + addPointToBoundingRect(&isFirst, &result, &item.points[0]); + break; + } + case CGPathItem::Type::CurveTo: { + addPointToBoundingRect(&isFirst, &result, &item.points[0]); + addPointToBoundingRect(&isFirst, &result, &item.points[1]); + addPointToBoundingRect(&isFirst, &result, &item.points[2]); + break; + } + case CGPathItem::Type::Close: { + break; + } + default: { + break; + } + } + } + + return result; +} + +bool CGPathImpl::empty() const { + return _items.empty(); +} + +std::shared_ptr CGPathImpl::copyUsingTransform(Transform2D const &transform) const { + auto result = std::make_shared(); + + if (transform == Transform2D::identity()) { + result->_items = _items; + return result; + } + + result->_items.reserve(_items.capacity()); + for (auto &sourceItem : _items) { + CGPathItem &item = result->_items.emplace_back(sourceItem.type); + item.points[0] = transformVector(sourceItem.points[0], transform); + item.points[1] = transformVector(sourceItem.points[1], transform); + item.points[2] = transformVector(sourceItem.points[2], transform); + } + + return result; +} + +void CGPathImpl::addLineTo(Vector2D const &point) { + CGPathItem &item = _items.emplace_back(CGPathItem::Type::LineTo); + item.points[0] = point; +} + +void CGPathImpl::addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) { + CGPathItem &item = _items.emplace_back(CGPathItem::Type::CurveTo); + item.points[0] = control1; + item.points[1] = control2; + item.points[2] = point; +} + +void CGPathImpl::moveTo(Vector2D const &point) { + CGPathItem &item = _items.emplace_back(CGPathItem::Type::MoveTo); + item.points[0] = point; +} + +void CGPathImpl::closeSubpath() { + _items.emplace_back(CGPathItem::Type::Close); +} + +void CGPathImpl::addRect(CGRect const &rect) { + assert(false); + //CGPathAddRect(_path, nil, ::CGRectMake(rect.x, rect.y, rect.width, rect.height)); +} + +void CGPathImpl::addPath(std::shared_ptr const &path) { + if (_items.size() == 0) { + _items = std::static_pointer_cast(path)->_items; + } else { + size_t totalItemCount = _items.size() + std::static_pointer_cast(path)->_items.size(); + if (_items.capacity() < totalItemCount) { + _items.reserve(totalItemCount); + } + for (const auto &item : std::static_pointer_cast(path)->_items) { + _items.push_back(item); + } + } +} + +bool CGPathImpl::isEqual(CGPath *other) const { + if (_items.size() != ((CGPathImpl *)other)->_items.size()) { + return false; + } + + for (size_t i = 0; i < _items.size(); i++) { + if (_items[i] != ((CGPathImpl *)other)->_items[i]) { + return false; + } + } + + return true; +} + +void CGPathImpl::enumerate(std::function f) { + for (const auto &item : _items) { + f(item); + } +} + +std::shared_ptr CGPath::makePath() { + return std::static_pointer_cast(std::make_shared()); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.mm new file mode 100644 index 00000000000..93a5e242a10 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.mm @@ -0,0 +1,223 @@ +#include + +#include +#import + +namespace { + +void addPointToBoundingRect(bool *isFirst, CGRect *rect, CGPoint *point) { + if (*isFirst) { + *isFirst = false; + + rect->origin.x = point->x; + rect->origin.y = point->y; + rect->size.width = 0.0; + rect->size.height = 0.0; + + return; + } + if (point->x > rect->origin.x + rect->size.width) { + rect->size.width = point->x - rect->origin.x; + } + if (point->y > rect->origin.y + rect->size.height) { + rect->size.height = point->y - rect->origin.y; + } + if (point->x < rect->origin.x) { + rect->size.width += rect->origin.x - point->x; + rect->origin.x = point->x; + } + if (point->y < rect->origin.y) { + rect->size.height += rect->origin.y - point->y; + rect->origin.y = point->y; + } +} + +} + +CGRect calculatePathBoundingBox(CGPathRef path) { + __block CGRect result = CGRectMake(0.0, 0.0, 0.0, 0.0); + __block bool isFirst = true; + + CGPathApplyWithBlock(path, ^(const CGPathElement * _Nonnull element) { + switch (element->type) { + case kCGPathElementMoveToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + break; + } + case kCGPathElementAddLineToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + break; + } + case kCGPathElementAddCurveToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + addPointToBoundingRect(&isFirst, &result, &element->points[1]); + addPointToBoundingRect(&isFirst, &result, &element->points[2]); + break; + } + case kCGPathElementAddQuadCurveToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + addPointToBoundingRect(&isFirst, &result, &element->points[1]); + break; + } + case kCGPathElementCloseSubpath: { + break; + } + } + }); + + return result; +} + +namespace lottie { + +CGPathCocoaImpl::CGPathCocoaImpl() { + _path = CGPathCreateMutable(); +} + +CGPathCocoaImpl::CGPathCocoaImpl(CGMutablePathRef path) { + CFRetain(path); + _path = path; +} + +CGPathCocoaImpl::~CGPathCocoaImpl() { + CGPathRelease(_path); +} + +CGRect CGPathCocoaImpl::boundingBox() const { + auto rect = calculatePathBoundingBox(_path); + return CGRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); +} + +bool CGPathCocoaImpl::empty() const { + return CGPathIsEmpty(_path); +} + +std::shared_ptr CGPathCocoaImpl::copyUsingTransform(Transform2D const &transform) const { + CGAffineTransform affineTransform = CGAffineTransformMake( + transform.rows().columns[0][0], transform.rows().columns[0][1], + transform.rows().columns[1][0], transform.rows().columns[1][1], + transform.rows().columns[2][0], transform.rows().columns[2][1] + ); + + CGPathRef resultPath = CGPathCreateCopyByTransformingPath(_path, &affineTransform); + if (resultPath == nil) { + return nullptr; + } + + CGMutablePathRef resultMutablePath = CGPathCreateMutableCopy(resultPath); + CGPathRelease(resultPath); + auto result = std::make_shared(resultMutablePath); + CGPathRelease(resultMutablePath); + + return result; +} + +void CGPathCocoaImpl::addLineTo(Vector2D const &point) { + CGPathAddLineToPoint(_path, nil, point.x, point.y); +} + +void CGPathCocoaImpl::addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) { + CGPathAddCurveToPoint(_path, nil, control1.x, control1.y, control2.x, control2.y, point.x, point.y); +} + +void CGPathCocoaImpl::moveTo(Vector2D const &point) { + CGPathMoveToPoint(_path, nil, point.x, point.y); +} + +void CGPathCocoaImpl::closeSubpath() { + CGPathCloseSubpath(_path); +} + +void CGPathCocoaImpl::addRect(CGRect const &rect) { + CGPathAddRect(_path, nil, ::CGRectMake(rect.x, rect.y, rect.width, rect.height)); +} + +void CGPathCocoaImpl::addPath(std::shared_ptr const &path) { + if (CGPathIsEmpty(_path)) { + _path = CGPathCreateMutableCopy(std::static_pointer_cast(path)->_path); + } else { + CGPathAddPath(_path, nil, std::static_pointer_cast(path)->_path); + } +} + +CGPathRef CGPathCocoaImpl::nativePath() const { + return _path; +} + +bool CGPathCocoaImpl::isEqual(CGPath *other) const { + CGPathCocoaImpl *otherImpl = (CGPathCocoaImpl *)other; + return CGPathEqualToPath(_path, otherImpl->_path); +} + +void CGPathCocoaImpl::enumerate(std::function f) { + CGPathApplyWithBlock(_path, ^(const CGPathElement * _Nonnull element) { + CGPathItem item(CGPathItem::Type::MoveTo); + + switch (element->type) { + case kCGPathElementMoveToPoint: { + item.type = CGPathItem::Type::MoveTo; + item.points[0] = Vector2D(element->points[0].x, element->points[0].y); + f(item); + break; + } + case kCGPathElementAddLineToPoint: { + item.type = CGPathItem::Type::LineTo; + item.points[0] = Vector2D(element->points[0].x, element->points[0].y); + f(item); + break; + } + case kCGPathElementAddCurveToPoint: { + item.type = CGPathItem::Type::CurveTo; + item.points[0] = Vector2D(element->points[0].x, element->points[0].y); + item.points[1] = Vector2D(element->points[1].x, element->points[1].y); + item.points[2] = Vector2D(element->points[2].x, element->points[2].y); + f(item); + break; + } + case kCGPathElementAddQuadCurveToPoint: { + break; + } + case kCGPathElementCloseSubpath: { + item.type = CGPathItem::Type::Close; + f(item); + break; + } + } + }); +} + +void CGPathCocoaImpl::withNativePath(std::shared_ptr const &path, std::function f) { + CGMutablePathRef result = CGPathCreateMutable(); + + path->enumerate([result](CGPathItem const &element) { + switch (element.type) { + case CGPathItem::Type::MoveTo: { + CGPathMoveToPoint(result, nullptr, element.points[0].x, element.points[0].y); + break; + } + case CGPathItem::Type::LineTo: { + CGPathAddLineToPoint(result, nullptr, element.points[0].x, element.points[0].y); + break; + } + case CGPathItem::Type::CurveTo: { + CGPathAddCurveToPoint(result, nullptr, element.points[0].x, element.points[0].y, element.points[1].x, element.points[1].y, element.points[2].x, element.points[2].y); + break; + } + case CGPathItem::Type::Close: { + CGPathCloseSubpath(result); + break; + } + default: + break; + } + }); + + f(result); + CFRelease(result); +} + +/*std::shared_ptr CGPath::makePath() { + return std::static_pointer_cast(std::make_shared()); +}*/ + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.cpp new file mode 100644 index 00000000000..24022559bfe --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.cpp @@ -0,0 +1,5 @@ +#include "CTFont.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.hpp new file mode 100644 index 00000000000..351d4bfc224 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.hpp @@ -0,0 +1,12 @@ +#ifndef CTFont_hpp +#define CTFont_hpp + +namespace lottie { + +class CTFont { + +}; + +} + +#endif /* CTFont_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp new file mode 100644 index 00000000000..d627e71da43 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp @@ -0,0 +1,128 @@ +#include + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +Color::Color(float r_, float g_, float b_, float a_, ColorFormatDenominator denominator) { + float denominatorValue = 1.0; + switch (denominator) { + case ColorFormatDenominator::One: { + denominatorValue = 1.0; + break; + } + case ColorFormatDenominator::OneHundred: { + denominatorValue = 100.0; + break; + } + case ColorFormatDenominator::TwoFiftyFive: { + denominatorValue = 255.0; + break; + } + } + + r = r_ / denominatorValue; + g = g_ / denominatorValue; + b = b_ / denominatorValue; + a = a_ / denominatorValue; +} + +Color::Color(lottiejson11::Json const &jsonAny) noexcept(false) : + r(0.0), g(0.0), b(0.0), a(0.0) { + if (!jsonAny.is_array()) { + throw LottieParsingException(); + } + + for (const auto &item : jsonAny.array_items()) { + if (!item.is_number()) { + throw LottieParsingException(); + } + } + + size_t index = 0; + + float r1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + r1 = jsonAny.array_items()[index].number_value(); + index++; + } + + float g1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + g1 = jsonAny.array_items()[index].number_value(); + index++; + } + + float b1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + b1 = jsonAny.array_items()[index].number_value(); + index++; + } + + float a1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + a1 = jsonAny.array_items()[index].number_value(); + index++; + } + + if (r1 > 1.0 && r1 > 1.0 && b1 > 1.0 && a1 > 1.0) { + r1 = r1 / 255.0; + g1 = g1 / 255.0; + b1 = b1 / 255.0; + a1 = a1 / 255.0; + } + + r = r1; + g = g1; + b = b1; + a = a1; +} + +lottiejson11::Json Color::toJson() const { + lottiejson11::Json::array result; + + result.push_back(lottiejson11::Json(r)); + result.push_back(lottiejson11::Json(g)); + result.push_back(lottiejson11::Json(b)); + result.push_back(lottiejson11::Json(a)); + + return result; +} + +Color Color::fromString(std::string const &string) { + if (string.empty()) { + return Color(0.0, 0.0, 0.0, 0.0); + } + + std::string workString = string; + if (workString[0] == '#') { + workString.erase(workString.begin()); + } + + std::istringstream converter(workString); + uint32_t rgbValue; + converter >> std::hex >> rgbValue; + + return Color( + ((float)((rgbValue & 0xFF0000) >> 16)) / 255.0, + ((float)((rgbValue & 0x00FF00) >> 8)) / 255.0, + ((float)(rgbValue & 0x0000FF)) / 255.0, + 1.0 + ); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.cpp new file mode 100644 index 00000000000..33426de3384 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.cpp @@ -0,0 +1,5 @@ +#include "DashPattern.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.hpp new file mode 100644 index 00000000000..e6255ec8212 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.hpp @@ -0,0 +1,18 @@ +#ifndef DashPattern_hpp +#define DashPattern_hpp + +#include + +namespace lottie { + +struct DashPattern { + DashPattern(std::vector &&values_) : + values(std::move(values_)) { + } + + std::vector values; +}; + +} + +#endif /* DashPattern_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.cpp new file mode 100644 index 00000000000..3a92ce0d41b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.cpp @@ -0,0 +1,5 @@ +#include "GradientColorSet.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp new file mode 100644 index 00000000000..abf576690f8 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp @@ -0,0 +1,42 @@ +#ifndef GradientColorSet_hpp +#define GradientColorSet_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +struct GradientColorSet { + GradientColorSet() { + } + + explicit GradientColorSet(lottiejson11::Json const &jsonAny) noexcept(false) { + if (!jsonAny.is_array()) { + throw LottieParsingException(); + } + + for (const auto &item : jsonAny.array_items()) { + if (!item.is_number()) { + throw LottieParsingException(); + } + colors.push_back(item.number_value()); + } + } + + lottiejson11::Json toJson() const { + lottiejson11::Json::array result; + + for (auto value : colors) { + result.push_back(value); + } + + return result; + } + + std::vector colors; +}; + +} + +#endif /* GradientColorSet_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm new file mode 100644 index 00000000000..3695d35aa0d --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm @@ -0,0 +1,912 @@ +#include +#include + +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Keyframes/Interpolatable.hpp" + +#include + +#import + +namespace lottie { + +/*explicit Transform2D(Transform3D const &t) { + CGAffineTransform at = CATransform3DGetAffineTransform(nativeTransform(t)); + _rows.columns[0] = simd_make_float3(at.a, at.b, 0.0); + _rows.columns[1] = simd_make_float3(at.c, at.d, 0.0); + _rows.columns[2] = simd_make_float3(at.tx, at.ty, 1.0); +} + + Transform3D transform3D() { + CGAffineTransform at = CGAffineTransformMake( + _rows.columns[0][0], _rows.columns[0][1], + _rows.columns[1][0], _rows.columns[1][1], + _rows.columns[2][0], _rows.columns[2][1] + ); + return fromNativeTransform(CATransform3DMakeAffineTransform(at)); + }*/ + +/*struct Transform3D { + float m11, m12, m13, m14; + float m21, m22, m23, m24; + float m31, m32, m33, m34; + float m41, m42, m43, m44; + + Transform3D( + float m11_, float m12_, float m13_, float m14_, + float m21_, float m22_, float m23_, float m24_, + float m31_, float m32_, float m33_, float m34_, + float m41_, float m42_, float m43_, float m44_ + ) : + m11(m11_), m12(m12_), m13(m13_), m14(m14_), + m21(m21_), m22(m22_), m23(m23_), m24(m24_), + m31(m31_), m32(m32_), m33(m33_), m34(m34_), + m41(m41_), m42(m42_), m43(m43_), m44(m44_) { + } + + bool operator==(Transform3D const &rhs) const { + return m11 == rhs.m11 && m12 == rhs.m12 && m13 == rhs.m13 && m14 == rhs.m14 && + m21 == rhs.m21 && m22 == rhs.m22 && m23 == rhs.m23 && m24 == rhs.m24 && + m31 == rhs.m31 && m32 == rhs.m32 && m33 == rhs.m33 && m34 == rhs.m34 && + m41 == rhs.m41 && m42 == rhs.m42 && m43 == rhs.m43 && m44 == rhs.m44; + } + + bool operator!=(Transform3D const &rhs) const { + return !(*this == rhs); + } + + inline bool isIdentity() const { + return m11 == 1.0 && m12 == 0.0 && m13 == 0.0 && m14 == 0.0 && + m21 == 0.0 && m22 == 1.0 && m23 == 0.0 && m24 == 0.0 && + m31 == 0.0 && m32 == 0.0 && m33 == 1.0 && m34 == 0.0 && + m41 == 0.0 && m42 == 0.0 && m43 == 0.0 && m44 == 1.0; + } + + static Transform3D makeTranslation(float tx, float ty, float tz) { + return Transform3D( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + tx, ty, tz, 1 + ); + } + + static Transform3D makeScale(float sx, float sy, float sz) { + return Transform3D( + sx, 0, 0, 0, + 0, sy, 0, 0, + 0, 0, sz, 0, + 0, 0, 0, 1 + ); + } + + static Transform3D makeRotation(float radians); + + static Transform3D makeSkew(float skew, float skewAxis) { + float mCos = cos(degreesToRadians(skewAxis)); + float mSin = sin(degreesToRadians(skewAxis)); + float aTan = tan(degreesToRadians(skew)); + + Transform3D transform1( + mCos, + mSin, + 0.0, + 0.0, + -mSin, + mCos, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + Transform3D transform2( + 1.0, + 0.0, + 0.0, + 0.0, + aTan, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + Transform3D transform3( + mCos, + -mSin, + 0.0, + 0.0, + mSin, + mCos, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + return transform3 * transform2 * transform1; + } + + static Transform3D makeTransform( + Vector2D const &anchor, + Vector2D const &position, + Vector2D const &scale, + float rotation, + std::optional skew, + std::optional skewAxis + ) { + Transform3D result = Transform3D::identity(); + if (skew.has_value() && skewAxis.has_value()) { + result = Transform3D::identity().translated(position).rotated(rotation).skewed(-skew.value(), skewAxis.value()).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } else { + result = Transform3D::identity().translated(position).rotated(rotation).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } + + return result; + } + + Transform3D rotated(float degrees) const; + + Transform3D translated(Vector2D const &translation) const; + + Transform3D scaled(Vector2D const &scale) const; + + Transform3D skewed(float skew, float skewAxis) const { + return Transform3D::makeSkew(skew, skewAxis) * (*this); + } + + static Transform3D identity() { + return Transform3D( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ); + } + + Transform3D operator*(Transform3D const &b) const; +};*/ + +/*Transform2D t2d(Transform3D const &testMatrix) { + ::CATransform3D nativeTest; + + nativeTest.m11 = testMatrix.m11; + nativeTest.m12 = testMatrix.m12; + nativeTest.m13 = testMatrix.m13; + nativeTest.m14 = testMatrix.m14; + + nativeTest.m21 = testMatrix.m21; + nativeTest.m22 = testMatrix.m22; + nativeTest.m23 = testMatrix.m23; + nativeTest.m24 = testMatrix.m24; + + nativeTest.m31 = testMatrix.m31; + nativeTest.m32 = testMatrix.m32; + nativeTest.m33 = testMatrix.m33; + nativeTest.m34 = testMatrix.m34; + + nativeTest.m41 = testMatrix.m41; + nativeTest.m42 = testMatrix.m42; + nativeTest.m43 = testMatrix.m43; + nativeTest.m44 = testMatrix.m44; + + CGAffineTransform at = CATransform3DGetAffineTransform(nativeTest); + Transform2D result = Transform2D::identity(); + simd_float3x3 *rows = (simd_float3x3 *)&result.rows(); + rows->columns[0] = simd_make_float3(at.a, at.b, 0.0); + rows->columns[1] = simd_make_float3(at.c, at.d, 0.0); + rows->columns[2] = simd_make_float3(at.tx, at.ty, 1.0); + + return result; +} + +Transform3D t3d(Transform2D const &t) { + CGAffineTransform at = CGAffineTransformMake( + t.rows().columns[0][0], t.rows().columns[0][1], + t.rows().columns[1][0], t.rows().columns[1][1], + t.rows().columns[2][0], t.rows().columns[2][1] + ); + ::CATransform3D value = CATransform3DMakeAffineTransform(at); + + Transform3D result = Transform3D::identity(); + result.m11 = value.m11; + result.m12 = value.m12; + result.m13 = value.m13; + result.m14 = value.m14; + + result.m21 = value.m21; + result.m22 = value.m22; + result.m23 = value.m23; + result.m24 = value.m24; + + result.m31 = value.m31; + result.m32 = value.m32; + result.m33 = value.m33; + result.m34 = value.m34; + + result.m41 = value.m41; + result.m42 = value.m42; + result.m43 = value.m43; + result.m44 = value.m44; + + return result; +} + +Transform3D Transform3D::operator*(Transform3D const &b) const { + if (isIdentity()) { + return b; + } + if (b.isIdentity()) { + return *this; + } + + return t3d((t2d(*this) * t2d(b))); +}*/ + +Vector1D::Vector1D(lottiejson11::Json const &json) noexcept(false) { + if (json.is_number()) { + value = json.number_value(); + } else if (json.is_array()) { + if (json.array_items().empty()) { + throw LottieParsingException(); + } + if (!json.array_items()[0].is_number()) { + throw LottieParsingException(); + } + value = json.array_items()[0].number_value(); + } else { + throw LottieParsingException(); + } +} + +lottiejson11::Json Vector1D::toJson() const { + return lottiejson11::Json(value); +} + +Vector2D::Vector2D(lottiejson11::Json const &json) noexcept(false) { + x = 0.0; + y = 0.0; + + if (json.is_array()) { + int index = 0; + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + x = json.array_items()[index].number_value(); + index++; + } + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + y = json.array_items()[index].number_value(); + index++; + } + } else if (json.is_object()) { + auto xAny = getAny(json.object_items(), "x"); + if (xAny.is_number()) { + x = xAny.number_value(); + } else if (xAny.is_array()) { + if (xAny.array_items().empty()) { + throw LottieParsingException(); + } + if (!xAny.array_items()[0].is_number()) { + throw LottieParsingException(); + } + x = xAny.array_items()[0].number_value(); + } + + auto yAny = getAny(json.object_items(), "y"); + if (yAny.is_number()) { + y = yAny.number_value(); + } else if (yAny.is_array()) { + if (yAny.array_items().empty()) { + throw LottieParsingException(); + } + if (!yAny.array_items()[0].is_number()) { + throw LottieParsingException(); + } + y = yAny.array_items()[0].number_value(); + } + } else { + throw LottieParsingException(); + } +} + +lottiejson11::Json Vector2D::toJson() const { + lottiejson11::Json::object result; + + result.insert(std::make_pair("x", x)); + result.insert(std::make_pair("y", y)); + + return lottiejson11::Json(result); +} + +Vector3D::Vector3D(lottiejson11::Json const &json) noexcept(false) { + if (!json.is_array()) { + throw LottieParsingException(); + } + + int index = 0; + + x = 0.0; + y = 0.0; + z = 0.0; + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + x = json.array_items()[index].number_value(); + index++; + } + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + y = json.array_items()[index].number_value(); + index++; + } + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + z = json.array_items()[index].number_value(); + index++; + } +} + +lottiejson11::Json Vector3D::toJson() const { + lottiejson11::Json::array result; + + result.push_back(lottiejson11::Json(x)); + result.push_back(lottiejson11::Json(y)); + result.push_back(lottiejson11::Json(z)); + + return lottiejson11::Json(result); +} + +Transform2D Transform2D::_identity = Transform2D( + simd_float3x3({ + simd_make_float3(1.0f, 0.0f, 0.0f), + simd_make_float3(0.0f, 1.0f, 0.0f), + simd_make_float3(0.0f, 0.0f, 1.0f) + }) +); + +Transform2D Transform2D::makeTranslation(float tx, float ty) { + return Transform2D(simd_float3x3({ + simd_make_float3(1.0f, 0.0f, 0.0f), + simd_make_float3(0.0f, 1.0f, 0.0f), + simd_make_float3(tx, ty, 1.0f) + })); +} + +Transform2D Transform2D::makeScale(float sx, float sy) { + return Transform2D(simd_float3x3({ + simd_make_float3(sx, 0.0f, 0.0f), + simd_make_float3(0.0f, sy, 0.0f), + simd_make_float3(0.0f, 0.0f, 1.0f) + })); +} + +Transform2D Transform2D::makeRotation(float radians) { + float c = cos(radians); + float s = sin(radians); + + return Transform2D(simd_float3x3({ + simd_make_float3(c, s, 0.0f), + simd_make_float3(-s, c, 0.0f), + simd_make_float3(0.0f, 0.0f, 1.0f) + })); +} + +Transform2D Transform2D::makeSkew(float skew, float skewAxis) { + if (std::abs(skew) <= FLT_EPSILON && std::abs(skewAxis) <= FLT_EPSILON) { + return Transform2D::identity(); + } + + float mCos = cos(degreesToRadians(skewAxis)); + float mSin = sin(degreesToRadians(skewAxis)); + float aTan = tan(degreesToRadians(skew)); + + simd_float3x3 simd1 = simd_float3x3({ + simd_make_float3(mCos, -mSin, 0.0), + simd_make_float3(mSin, mCos, 0.0), + simd_make_float3(0.0, 0.0, 1.0) + }); + + simd_float3x3 simd2 = simd_float3x3({ + simd_make_float3(1.0, 0.0, 0.0), + simd_make_float3(aTan, 1.0, 0.0), + simd_make_float3(0.0, 0.0, 1.0) + }); + + simd_float3x3 simd3 = simd_float3x3({ + simd_make_float3(mCos, mSin, 0.0), + simd_make_float3(-mSin, mCos, 0.0), + simd_make_float3(0.0, 0.0, 1.0) + }); + + simd_float3x3 result = simd_mul(simd_mul(simd3, simd2), simd1); + Transform2D resultTransform(result); + + return resultTransform; +} + +Transform2D Transform2D::makeTransform( + Vector2D const &anchor, + Vector2D const &position, + Vector2D const &scale, + float rotation, + std::optional skew, + std::optional skewAxis +) { + Transform2D result = Transform2D::identity(); + if (skew.has_value() && skewAxis.has_value()) { + result = Transform2D::identity().translated(position).rotated(rotation).skewed(-skew.value(), skewAxis.value()).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } else { + result = Transform2D::identity().translated(position).rotated(rotation).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } + + return result; +} + +Transform2D Transform2D::rotated(float degrees) const { + return Transform2D::makeRotation(degreesToRadians(degrees)) * (*this); +} + +Transform2D Transform2D::translated(Vector2D const &translation) const { + return Transform2D::makeTranslation(translation.x, translation.y) * (*this); +} + +Transform2D Transform2D::scaled(Vector2D const &scale) const { + return Transform2D::makeScale(scale.x, scale.y) * (*this); +} + +Transform2D Transform2D::skewed(float skew, float skewAxis) const { + return Transform2D::makeSkew(skew, skewAxis) * (*this); +} + +float interpolate(float value, float to, float amount) { + return value + ((to - value) * amount); +} + +Vector1D interpolate( + Vector1D const &from, + Vector1D const &to, + float amount +) { + return Vector1D(interpolate(from.value, to.value, amount)); +} + +Vector2D interpolate( + Vector2D const &from, + Vector2D const &to, + float amount +) { + return Vector2D(interpolate(from.x, to.x, amount), interpolate(from.y, to.y, amount)); +} + + +Vector3D interpolate( + Vector3D const &from, + Vector3D const &to, + float amount +) { + return Vector3D(interpolate(from.x, to.x, amount), interpolate(from.y, to.y, amount), interpolate(from.z, to.z, amount)); +} + +static float cubicRoot(float value) { + return pow(value, 1.0 / 3.0); +} + +static float SolveQuadratic(float a, float b, float c) { + float result = (-b + sqrt((b * b) - 4 * a * c)) / (2 * a); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + + result = (-b - sqrt((b * b) - 4 * a * c)) / (2 * a); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + + return -1.0; +} + +inline bool isApproximatelyEqual(float value, float other) { + return std::abs(value - other) <= FLT_EPSILON; +} + +static float SolveCubic(float a, float b, float c, float d) { + if (isApproximatelyEqual(a, 0.0f)) { + return SolveQuadratic(b, c, d); + } + if (isApproximatelyEqual(d, 0.0f)) { + return 0.0; + } + b /= a; + c /= a; + d /= a; + float q = (3.0 * c - (b * b)) / 9.0; + float r = (-27.0 * d + b * (9.0 * c - 2.0 * (b * b))) / 54.0; + float disc = (q * q * q) + (r * r); + float term1 = b / 3.0; + + if (disc > 0.0) { + float s = r + sqrt(disc); + s = (s < 0) ? -cubicRoot(-s) : cubicRoot(s); + float t = r - sqrt(disc); + t = (t < 0) ? -cubicRoot(-t) : cubicRoot(t); + + float result = -term1 + s + t; + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + } else if (isApproximatelyEqual(disc, 0.0f)) { + float r13 = (r < 0) ? -cubicRoot(-r) : cubicRoot(r); + + float result = -term1 + 2.0 * r13; + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + + result = -(r13 + term1); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + } else { + q = -q; + float dum1 = q * q * q; + dum1 = acos(r / sqrt(dum1)); + float r13 = 2.0 * sqrt(q); + + float result = -term1 + r13 * cos(dum1 / 3.0); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + result = -term1 + r13 * cos((dum1 + 2.0 * M_PI) / 3.0); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + result = -term1 + r13 * cos((dum1 + 4.0 * M_PI) / 3.0); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + } + + return -1.0; +} + +float cubicBezierInterpolate(float value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3) { + float t = 0.0; + if (isApproximatelyEqual(value, P0.x)) { + // Handle corner cases explicitly to prevent rounding errors + t = 0.0; + } else if (isApproximatelyEqual(value, P3.x)) { + t = 1.0; + } else { + // Calculate t + float a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x; + float b = 3 * P0.x - 6 * P1.x + 3 * P2.x; + float c = -3 * P0.x + 3 * P1.x; + float d = P0.x - value; + float tTemp = SolveCubic(a, b, c, d); + if (isApproximatelyEqual(tTemp, -1.0f)) { + return -1.0; + } + t = tTemp; + } + + // Calculate y from t + float oneMinusT = 1.0 - t; + return (oneMinusT * oneMinusT * oneMinusT) * P0.y + 3 * t * (oneMinusT * oneMinusT) * P1.y + 3 * (t * t) * (1 - t) * P2.y + (t * t * t) * P3.y; +} + +struct InterpolationPoint2D { + InterpolationPoint2D(Vector2D const point_, float distance_) : + point(point_), distance(distance_) { + } + + Vector2D point; + float distance; +}; + +namespace { + float interpolateFloat(float value, float to, float amount) { + return value + ((to - value) * amount); + } +} + +Vector2D Vector2D::pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, float amount) const { + auto a = interpolate(outTangent, amount); + auto b = outTangent.interpolate(inTangent, amount); + auto c = inTangent.interpolate(to, amount); + auto d = a.interpolate(b, amount); + auto e = b.interpolate(c, amount); + auto f = d.interpolate(e, amount); + return f; +} + +Vector2D Vector2D::interpolate(Vector2D const &to, float amount) const { + return Vector2D( + interpolateFloat(x, to.x, amount), + interpolateFloat(y, to.y, amount) + ); +} + +Vector2D Vector2D::interpolate( + Vector2D const &to, + Vector2D const &outTangent, + Vector2D const &inTangent, + float amount, + int maxIterations, + int samples, + float accuracy +) const { + if (amount == 0.0) { + return *this; + } + if (amount == 1.0) { + return to; + } + + if (colinear(outTangent, inTangent) && outTangent.colinear(inTangent, to)) { + return interpolate(to, amount); + } + + float step = 1.0 / (float)samples; + + std::vector points; + points.push_back(InterpolationPoint2D(*this, 0.0)); + float totalLength = 0.0; + + Vector2D previousPoint = *this; + float previousAmount = 0.0; + + int closestPoint = 0; + + while (previousAmount < 1.0) { + previousAmount = previousAmount + step; + + if (previousAmount < amount) { + closestPoint = closestPoint + 1; + } + + auto newPoint = pointOnPath(to, outTangent, inTangent, previousAmount); + auto distance = previousPoint.distanceTo(newPoint); + totalLength = totalLength + distance; + points.push_back(InterpolationPoint2D(newPoint, totalLength)); + previousPoint = newPoint; + } + + float accurateDistance = amount * totalLength; + auto point = points[closestPoint]; + + bool foundPoint = false; + + float pointAmount = ((float)closestPoint) * step; + float nextPointAmount = pointAmount + step; + + int refineIterations = 0; + while (!foundPoint) { + refineIterations = refineIterations + 1; + /// First see if the next point is still less than the projected length. + auto nextPoint = points[std::min(closestPoint + 1, (int)points.size() - 1)]; + if (nextPoint.distance < accurateDistance) { + point = nextPoint; + closestPoint = closestPoint + 1; + pointAmount = ((float)closestPoint) * step; + nextPointAmount = pointAmount + step; + if (closestPoint == (int)points.size()) { + foundPoint = true; + } + continue; + } + if (accurateDistance < point.distance) { + closestPoint = closestPoint - 1; + if (closestPoint < 0) { + foundPoint = true; + continue; + } + point = points[closestPoint]; + pointAmount = ((float)closestPoint) * step; + nextPointAmount = pointAmount + step; + continue; + } + + /// Now we are certain the point is the closest point under the distance + auto pointDiff = nextPoint.distance - point.distance; + auto proposedPointAmount = remapFloat((accurateDistance - point.distance) / pointDiff, 0.0, 1.0, pointAmount, nextPointAmount); + + auto newPoint = pointOnPath(to, outTangent, inTangent, proposedPointAmount); + auto newDistance = point.distance + point.point.distanceTo(newPoint); + pointAmount = proposedPointAmount; + point = InterpolationPoint2D(newPoint, newDistance); + if (accurateDistance - newDistance <= accuracy || + newDistance - accurateDistance <= accuracy) { + foundPoint = true; + } + + if (refineIterations == maxIterations) { + foundPoint = true; + } + } + return point.point; +} + +::CATransform3D nativeTransform(Transform2D const &value) { + CGAffineTransform at = CGAffineTransformMake( + value.rows().columns[0][0], value.rows().columns[0][1], + value.rows().columns[1][0], value.rows().columns[1][1], + value.rows().columns[2][0], value.rows().columns[2][1] + ); + return CATransform3DMakeAffineTransform(at); + + /*::CATransform3D result; + + result.m11 = value.m11; + result.m12 = value.m12; + result.m13 = value.m13; + result.m14 = value.m14; + + result.m21 = value.m21; + result.m22 = value.m22; + result.m23 = value.m23; + result.m24 = value.m24; + + result.m31 = value.m31; + result.m32 = value.m32; + result.m33 = value.m33; + result.m34 = value.m34; + + result.m41 = value.m41; + result.m42 = value.m42; + result.m43 = value.m43; + result.m44 = value.m44; + + return result;*/ +} + +Transform2D fromNativeTransform(::CATransform3D const &value) { + CGAffineTransform at = CATransform3DGetAffineTransform(value); + return Transform2D( + simd_float3x3({ + simd_make_float3(at.a, at.b, 0.0), + simd_make_float3(at.c, at.d, 0.0), + simd_make_float3(at.tx, at.ty, 1.0) + }) + ); + + /*Transform2D result = Transform2D::identity(); + + result.m11 = value.m11; + result.m12 = value.m12; + result.m13 = value.m13; + result.m14 = value.m14; + + result.m21 = value.m21; + result.m22 = value.m22; + result.m23 = value.m23; + result.m24 = value.m24; + + result.m31 = value.m31; + result.m32 = value.m32; + result.m33 = value.m33; + result.m34 = value.m34; + + result.m41 = value.m41; + result.m42 = value.m42; + result.m43 = value.m43; + result.m44 = value.m44; + + return result;*/ +} + +/*Transform3D Transform3D::makeRotation(float radians) { + if (std::abs(radians) <= FLT_EPSILON) { + return Transform3D::identity(); + } + + float s = sin(radians); + float c = cos(radians); + + ::CGAffineTransform t = CGAffineTransformMake(c, s, -s, c, 0.0f, 0.0f); + return fromNativeTransform(CATransform3DMakeAffineTransform(t)); +} + +Transform3D Transform3D::rotated(float degrees) const { + return Transform3D::makeRotation(degreesToRadians(degrees)) * (*this); +} + +Transform3D Transform3D::translated(Vector2D const &translation) const { + return Transform3D::makeTranslation(translation.x, translation.y, 0.0f) * (*this); +} + +Transform3D Transform3D::scaled(Vector2D const &scale) const { + return Transform3D::makeScale(scale.x, scale.y, 1.0) * (*this); +} + +bool Transform3D::isInvertible() const { + return Transform2D(*this).isInvertible(); + //return std::abs(m11 * m22 - m12 * m21) >= 0.00000001; +} + +Transform3D Transform3D::inverted() const { + return Transform2D(*this).inverted().transform3D(); +}*/ + +bool CGRect::intersects(CGRect const &other) const { + return CGRectIntersectsRect(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); +} + +bool CGRect::contains(CGRect const &other) const { + return CGRectContainsRect(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); +} + +CGRect CGRect::intersection(CGRect const &other) const { + auto result = CGRectIntersection(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); + return CGRect(result.origin.x, result.origin.y, result.size.width, result.size.height); +} + +CGRect CGRect::unionWith(CGRect const &other) const { + auto result = CGRectUnion(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); + return CGRect(result.origin.x, result.origin.y, result.size.width, result.size.height); +} + +CGRect CGRect::applyingTransform(Transform2D const &transform) const { + if (transform.isIdentity()) { + return *this; + } + + Vector2D sourceTopLeft = Vector2D(x, y); + Vector2D sourceTopRight = Vector2D(x + width, y); + Vector2D sourceBottomLeft = Vector2D(x, y + height); + Vector2D sourceBottomRight = Vector2D(x + width, y + height); + + simd_float4 xs = simd_make_float4(sourceTopLeft.x, sourceTopRight.x, sourceBottomLeft.x, sourceBottomRight.x); + simd_float4 ys = simd_make_float4(sourceTopLeft.y, sourceTopRight.y, sourceBottomLeft.y, sourceBottomRight.y); + + simd_float4 rx = xs * transform.rows().columns[0][0] + ys * transform.rows().columns[1][0] + transform.rows().columns[2][0]; + simd_float4 ry = xs * transform.rows().columns[0][1] + ys * transform.rows().columns[1][1] + transform.rows().columns[2][1]; + + Vector2D topLeft = Vector2D(rx[0], ry[0]); + Vector2D topRight = Vector2D(rx[1], ry[1]); + Vector2D bottomLeft = Vector2D(rx[2], ry[2]); + Vector2D bottomRight = Vector2D(rx[3], ry[3]); + + float minX = simd_reduce_min(simd_make_float4(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x)); + float minY = simd_reduce_min(simd_make_float4(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y)); + float maxX = simd_reduce_max(simd_make_float4(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x)); + float maxY = simd_reduce_max(simd_make_float4(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y)); + + return CGRect(minX, minY, maxX - minX, maxY - minY); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.cpp new file mode 100644 index 00000000000..6ba1e020c87 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.cpp @@ -0,0 +1,5 @@ +#include "AnimationTextProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.hpp new file mode 100644 index 00000000000..dc5d3246283 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.hpp @@ -0,0 +1,50 @@ +#ifndef AnimationTextProvider_hpp +#define AnimationTextProvider_hpp + +#include +#include + +namespace lottie { + +/// Text provider is a protocol that is used to supply text to `AnimationView`. +class AnimationTextProvider { +public: + virtual std::string textFor(std::string const &keypathName, std::string const &sourceText) = 0; +}; + +/// Text provider that simply map values from dictionary +class DictionaryTextProvider: public AnimationTextProvider { +public: + DictionaryTextProvider(std::map const &values) : + _values(values) { + } + + virtual std::string textFor(std::string const &keypathName, std::string const &sourceText) override { + const auto it = _values.find(keypathName); + if (it != _values.end()) { + return it->second; + } else { + return sourceText; + } + } + +private: + std::map _values; +}; + +/// Default text provider. Uses text in the animation file +class DefaultTextProvider: public AnimationTextProvider { +public: + DefaultTextProvider() { + } + + virtual ~DefaultTextProvider() = default; + + virtual std::string textFor(std::string const &keypathName, std::string const &sourceText) override { + return sourceText; + } +}; + +} + +#endif /* AnimationTextProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimation.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimation.mm new file mode 100644 index 00000000000..db68b683708 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimation.mm @@ -0,0 +1,60 @@ +#include + +#include "Lottie/Private/Model/Animation.hpp" + +#include + +@interface LottieAnimation () { +@public + std::shared_ptr _animation; +} + +@end + +@implementation LottieAnimation + +- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data { + self = [super init]; + if (self != nil) { + std::string errorText; + auto json = lottiejson11::Json::parse(std::string((uint8_t const *)data.bytes, ((uint8_t const *)data.bytes) + data.length), errorText); + if (!json.is_object()) { + return nil; + } + + try { + _animation = lottie::Animation::fromJson(json.object_items()); + } catch(...) { + return nil; + } + } + return self; +} + +- (NSInteger)frameCount { + return (NSInteger)(_animation->endFrame - _animation->startFrame); +} + +- (NSInteger)framesPerSecond { + return (NSInteger)(_animation->framerate); +} + +- (CGSize)size { + return CGSizeMake(_animation->width, _animation->height); +} + +- (NSData * _Nonnull)toJson { + lottiejson11::Json::object json = _animation->toJson(); + std::string jsonString = lottiejson11::Json(json).dump(); + return [[NSData alloc] initWithBytes:jsonString.data() length:jsonString.size()]; +} + +@end + +@implementation LottieAnimation (Internal) + +- (std::shared_ptr)animationImpl { + return _animation; +} + +@end diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm new file mode 100644 index 00000000000..5f06c012229 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm @@ -0,0 +1,85 @@ +#include + +#include "Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp" +#include "LottieAnimationInternal.h" +#include + +@interface LottieAnimationContainer () { +@public + std::shared_ptr _layer; + std::shared_ptr _bezierPathsBoundingBoxContext; +} + +@end + +@implementation LottieAnimationContainer + +- (instancetype _Nonnull)initWithAnimation:(LottieAnimation * _Nonnull)animation { + self = [super init]; + if (self != nil) { + _bezierPathsBoundingBoxContext = std::make_shared(); + + _animation = animation; + + _layer = std::make_shared( + *[animation animationImpl].get(), + std::make_shared(), + std::make_shared(), + std::make_shared() + ); + } + return self; +} + +- (void)update:(NSInteger)frame { + _layer->setCurrentFrame(frame); +} + +- (LottieRenderNode * _Nullable)getCurrentRenderTreeForSize:(CGSize)size { + return nil; +} + +- (std::shared_ptr)internalGetRootRenderTreeNode { + auto renderNode = _layer->renderTreeNode(); + return renderNode; +} + +- (int64_t)getRootRenderNodeProxy { + std::shared_ptr renderNode = [self internalGetRootRenderTreeNode]; + return (int64_t)renderNode.get(); +} + +- (LottieRenderNodeProxy)getRenderNodeProxyById:(int64_t)nodeId __attribute__((objc_direct)) { + lottie::RenderTreeNode *node = (lottie::RenderTreeNode *)nodeId; + + LottieRenderNodeProxy result; + + result.internalId = nodeId; + result.isValid = node->renderData.isValid; + + + result.isInvertedMatte = node->renderData.isInvertedMatte; + if (node->mask()) { + result.maskId = (int64_t)node->mask().get(); + } else { + result.maskId = 0; + } + result.subnodeCount = (int)node->subnodes().size(); + + return result; +} + +- (LottieRenderNodeProxy)getRenderNodeSubnodeProxyById:(int64_t)nodeId index:(int)index __attribute__((objc_direct)) { + lottie::RenderTreeNode *node = (lottie::RenderTreeNode *)nodeId; + return [self getRenderNodeProxyById:(int64_t)node->subnodes()[index].get()]; +} + +@end + +@implementation LottieAnimationContainer (Internal) + +- (std::shared_ptr)layer { + return _layer; +} + +@end diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainerInternal.h b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainerInternal.h new file mode 100644 index 00000000000..c5f3a105ccd --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainerInternal.h @@ -0,0 +1,13 @@ +#ifndef LottieAnimationContainerInternal_h +#define LottieAnimationContainerInternal_h + +#include "Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp" +#include + +@interface LottieAnimationContainer (Internal) + +@property (nonatomic, readonly) std::shared_ptr layer; + +@end + +#endif /* LottieAnimationContainerInternal_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationInternal.h b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationInternal.h new file mode 100644 index 00000000000..d314f023d59 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationInternal.h @@ -0,0 +1,15 @@ +#ifndef LottieAnimationInternal_h +#define LottieAnimationInternal_h + +#include +#include "Lottie/Private/Model/Animation.hpp" + +#include + +@interface LottieAnimation (Internal) + +- (std::shared_ptr)animationImpl; + +@end + +#endif /* LottieAnimationInternal_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.h b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.h new file mode 100644 index 00000000000..e4158c3ccbe --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.h @@ -0,0 +1,147 @@ +#ifndef LottieRenderTree_h +#define LottieRenderTree_h + +#import + +#ifdef __cplusplus +extern "C" { +#endif + +typedef NS_ENUM(NSUInteger, LottiePathItemType) { + LottiePathItemTypeMoveTo, + LottiePathItemTypeLineTo, + LottiePathItemTypeCurveTo, + LottiePathItemTypeClose +}; + +typedef struct { + LottiePathItemType type; + CGPoint points[4]; +} LottiePathItem; + +typedef struct { + CGFloat r; + CGFloat g; + CGFloat b; + CGFloat a; +} LottieColor; + +typedef NS_ENUM(NSUInteger, LottieFillRule) { + LottieFillRuleEvenOdd, + LottieFillRuleWinding +}; + +typedef NS_ENUM(NSUInteger, LottieGradientType) { + LottieGradientTypeLinear, + LottieGradientTypeRadial +}; + +@interface LottieColorStop : NSObject + +@property (nonatomic, readonly, direct) LottieColor color; +@property (nonatomic, readonly, direct) CGFloat location; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithColor:(LottieColor)color location:(CGFloat)location __attribute__((objc_direct)); + +@end + +@interface LottiePath : NSObject + +- (void)enumerateItems:(void (^ _Nonnull)(LottiePathItem * _Nonnull))iterate __attribute__((objc_direct)); + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithCustomData:(NSData * _Nonnull)customData __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentShading : NSObject + +@end + +@interface LottieRenderContentSolidShading : LottieRenderContentShading + +@property (nonatomic, readonly, direct) LottieColor color; +@property (nonatomic, readonly, direct) CGFloat opacity; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithColor:(LottieColor)color opacity:(CGFloat)opacity __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentGradientShading : LottieRenderContentShading + +@property (nonatomic, readonly, direct) CGFloat opacity; +@property (nonatomic, readonly, direct) LottieGradientType gradientType; +@property (nonatomic, strong, readonly, direct) NSArray * _Nonnull colorStops; +@property (nonatomic, readonly, direct) CGPoint start; +@property (nonatomic, readonly, direct) CGPoint end; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithOpacity:(CGFloat)opacity gradientType:(LottieGradientType)gradientType colorStops:(NSArray * _Nonnull)colorStops start:(CGPoint)start end:(CGPoint)end __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentFill : NSObject + +@property (nonatomic, strong, readonly, direct) LottieRenderContentShading * _Nonnull shading; +@property (nonatomic, readonly, direct) LottieFillRule fillRule; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading fillRule:(LottieFillRule)fillRule __attribute__((objc_direct)); + +@end + +@interface LottieRenderContentStroke : NSObject + +@property (nonatomic, strong, readonly, direct) LottieRenderContentShading * _Nonnull shading; +@property (nonatomic, readonly, direct) CGFloat lineWidth; +@property (nonatomic, readonly, direct) CGLineJoin lineJoin; +@property (nonatomic, readonly, direct) CGLineCap lineCap; +@property (nonatomic, readonly, direct) CGFloat miterLimit; +@property (nonatomic, readonly, direct) CGFloat dashPhase; +@property (nonatomic, strong, readonly, direct) NSArray * _Nullable dashPattern; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading lineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit dashPhase:(CGFloat)dashPhase dashPattern:(NSArray * _Nullable)dashPattern __attribute__((objc_direct)); + +@end + +@interface LottieRenderContent : NSObject + +@property (nonatomic, strong, readonly, direct) LottiePath * _Nonnull path; +@property (nonatomic, strong, readonly, direct) LottieRenderContentStroke * _Nullable stroke; +@property (nonatomic, strong, readonly, direct) LottieRenderContentFill * _Nullable fill; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithPath:(LottiePath * _Nonnull)path stroke:(LottieRenderContentStroke * _Nullable)stroke fill:(LottieRenderContentFill * _Nullable)fill __attribute__((objc_direct)); + +@end + +@interface LottieRenderNode : NSObject + +@property (nonatomic, readonly, direct) CGPoint position; +@property (nonatomic, readonly, direct) CGRect bounds; +@property (nonatomic, readonly, direct) CATransform3D transform; +@property (nonatomic, readonly, direct) CGFloat opacity; +@property (nonatomic, readonly, direct) bool masksToBounds; +@property (nonatomic, readonly, direct) bool isHidden; + +@property (nonatomic, readonly, direct) CGRect globalRect; +@property (nonatomic, readonly, direct) CATransform3D globalTransform; +@property (nonatomic, readonly, direct) LottieRenderContent * _Nullable renderContent; +@property (nonatomic, readonly, direct) bool hasSimpleContents; +@property (nonatomic, readonly, direct) bool isInvertedMatte; +@property (nonatomic, readonly, direct) NSArray * _Nonnull subnodes; +@property (nonatomic, readonly, direct) LottieRenderNode * _Nullable mask; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; +- (instancetype _Nonnull)initWithPosition:(CGPoint)position bounds:(CGRect)bounds transform:(CATransform3D)transform opacity:(CGFloat)opacity masksToBounds:(bool)masksToBounds isHidden:(bool)isHidden globalRect:(CGRect)globalRect globalTransform:(CATransform3D)globalTransform renderContent:(LottieRenderContent * _Nullable)renderContent hasSimpleContents:(bool)hasSimpleContents isInvertedMatte:(bool)isInvertedMatte subnodes:(NSArray * _Nonnull)subnodes mask:(LottieRenderNode * _Nullable)mask __attribute__((objc_direct)); + +@end + +#ifdef __cplusplus +} +#endif + +#endif /* LottieRenderTree_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm new file mode 100644 index 00000000000..9cdf5122133 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm @@ -0,0 +1,426 @@ +#include "LottieRenderTree.h" + +#include +#include +#import +#include "Lottie/Public/Primitives/CALayer.hpp" +#include + +namespace { + +} + +@interface LottiePath () { + std::vector _paths; + NSData *_customData; +} + +@end + +@implementation LottiePath + +- (instancetype)initWithPaths:(std::vector)paths __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _paths = paths; + } + return self; +} + +- (instancetype _Nonnull)initWithCustomData:(NSData * _Nonnull)customData __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _customData = customData; + } + return self; +} + +- (void)enumerateItems:(void (^ _Nonnull)(LottiePathItem * _Nonnull))iterate { + LottiePathItem item; + + if (_customData != nil) { + int dataOffset = 0; + int dataLength = (int)_customData.length; + uint8_t const *dataBytes = (uint8_t const *)_customData.bytes; + while (dataOffset < dataLength) { + uint8_t itemType = dataBytes[dataOffset]; + dataOffset += 1; + + switch (itemType) { + case 0: { + Float32 px; + memcpy(&px, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 py; + memcpy(&py, dataBytes + dataOffset, 4); + dataOffset += 4; + + item.type = LottiePathItemTypeMoveTo; + item.points[0] = CGPointMake(px, py); + iterate(&item); + + break; + } + case 1: { + Float32 px; + memcpy(&px, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 py; + memcpy(&py, dataBytes + dataOffset, 4); + dataOffset += 4; + + item.type = LottiePathItemTypeLineTo; + item.points[0] = CGPointMake(px, py); + iterate(&item); + + break; + } + case 2: { + Float32 p1x; + memcpy(&p1x, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 p1y; + memcpy(&p1y, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 p2x; + memcpy(&p2x, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 p2y; + memcpy(&p2y, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 px; + memcpy(&px, dataBytes + dataOffset, 4); + dataOffset += 4; + + Float32 py; + memcpy(&py, dataBytes + dataOffset, 4); + dataOffset += 4; + + item.type = LottiePathItemTypeCurveTo; + item.points[0] = CGPointMake(p1x, p1y); + item.points[1] = CGPointMake(p2x, p2y); + item.points[2] = CGPointMake(px, py); + iterate(&item); + + break; + } + case 3: { + item.type = LottiePathItemTypeClose; + iterate(&item); + break; + } + default: { + break; + } + } + } + } else { + for (const auto &path : _paths) { + std::optional previousElement; + for (const auto &element : path.elements()) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + item.type = LottiePathItemTypeLineTo; + item.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(&item); + } else { + item.type = LottiePathItemTypeCurveTo; + item.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + item.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); + item.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); + iterate(&item); + } + } else { + item.type = LottiePathItemTypeMoveTo; + item.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(&item); + } + previousElement = element; + } + if (path.closed().value_or(true)) { + item.type = LottiePathItemTypeClose; + iterate(&item); + } + } + } +} + +@end + +@implementation LottieColorStop : NSObject + +- (instancetype _Nonnull)initWithColor:(LottieColor)color location:(CGFloat)location __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _color = color; + _location = location; + } + return self; +} + +@end + +@implementation LottieRenderContentShading + +- (instancetype _Nonnull)init { + self = [super init]; + if (self != nil) { + } + return self; +} + +@end + +static LottieColor lottieColorFromColor(lottie::Color color) { + LottieColor result; + result.r = color.r; + result.g = color.g; + result.b = color.b; + result.a = color.a; + + return result; +} + +@implementation LottieRenderContentSolidShading + +- (instancetype _Nonnull)initWithSolidShading:(lottie::RenderTreeNodeContentItem::SolidShading *)solidShading __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _color = lottieColorFromColor(solidShading->color); + _opacity = solidShading->opacity; + } + return self; +} + +- (instancetype _Nonnull)initWithColor:(LottieColor)color opacity:(CGFloat)opacity { + self = [super init]; + if (self != nil) { + _color = color; + _opacity = opacity; + } + return self; +} + +@end + +@implementation LottieRenderContentGradientShading + +- (instancetype _Nonnull)initWithGradientShading:(lottie::RenderTreeNodeContentItem::GradientShading *)gradientShading __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _opacity = gradientShading->opacity; + + switch (gradientShading->gradientType) { + case lottie::GradientType::Radial: { + _gradientType = LottieGradientTypeRadial; + break; + } + default: { + _gradientType = LottieGradientTypeLinear; + break; + } + } + + NSMutableArray *colorStops = [[NSMutableArray alloc] initWithCapacity:gradientShading->colors.size()]; + for (size_t i = 0; i < gradientShading->colors.size(); i++) { + [colorStops addObject:[[LottieColorStop alloc] initWithColor:lottieColorFromColor(gradientShading->colors[i]) location:gradientShading->locations[i]]]; + } + _colorStops = colorStops; + + _start = CGPointMake(gradientShading->start.x, gradientShading->start.y); + _end = CGPointMake(gradientShading->end.x, gradientShading->end.y); + } + return self; +} + +- (instancetype _Nonnull)initWithOpacity:(CGFloat)opacity gradientType:(LottieGradientType)gradientType colorStops:(NSArray * _Nonnull)colorStops start:(CGPoint)start end:(CGPoint)end __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _opacity = opacity; + _gradientType = gradientType; + _colorStops = colorStops; + _start = start; + _end = end; + } + return self; +} + +@end + +@implementation LottieRenderContentFill + +- (instancetype _Nonnull)initWithFill:(std::shared_ptr const &)fill __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + switch (fill->shading->type()) { + case lottie::RenderTreeNodeContentItem::ShadingType::Solid: { + _shading = [[LottieRenderContentSolidShading alloc] initWithSolidShading:(lottie::RenderTreeNodeContentItem::SolidShading *)fill->shading.get()]; + break; + } + case lottie::RenderTreeNodeContentItem::ShadingType::Gradient: { + _shading = [[LottieRenderContentGradientShading alloc] initWithGradientShading:(lottie::RenderTreeNodeContentItem::GradientShading *)fill->shading.get()]; + break; + } + default: { + abort(); + } + } + + switch (fill->rule) { + case lottie::FillRule::EvenOdd: { + _fillRule = LottieFillRuleEvenOdd; + break; + } + default: { + _fillRule = LottieFillRuleWinding; + break; + } + } + } + return self; +} + +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading fillRule:(LottieFillRule)fillRule __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _shading = shading; + _fillRule = fillRule; + } + return self; +} + +@end + +@implementation LottieRenderContentStroke + +- (instancetype _Nonnull)initWithStroke:(std::shared_ptr const &)stroke __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + switch (stroke->shading->type()) { + case lottie::RenderTreeNodeContentItem::ShadingType::Solid: { + _shading = [[LottieRenderContentSolidShading alloc] initWithSolidShading:(lottie::RenderTreeNodeContentItem::SolidShading *)stroke->shading.get()]; + break; + } + case lottie::RenderTreeNodeContentItem::ShadingType::Gradient: { + _shading = [[LottieRenderContentGradientShading alloc] initWithGradientShading:(lottie::RenderTreeNodeContentItem::GradientShading *)stroke->shading.get()]; + break; + } + default: { + abort(); + } + } + + _lineWidth = stroke->lineWidth; + + switch (stroke->lineJoin) { + case lottie::LineJoin::Miter: { + _lineJoin = kCGLineJoinMiter; + break; + } + case lottie::LineJoin::Round: { + _lineJoin = kCGLineJoinRound; + break; + } + case lottie::LineJoin::Bevel: { + _lineJoin = kCGLineJoinBevel; + break; + } + default: { + _lineJoin = kCGLineJoinBevel; + break; + } + } + + switch (stroke->lineCap) { + case lottie::LineCap::Butt: { + _lineCap = kCGLineCapButt; + break; + } + case lottie::LineCap::Round: { + _lineCap = kCGLineCapRound; + break; + } + case lottie::LineCap::Square: { + _lineCap = kCGLineCapSquare; + break; + } + default: { + _lineCap = kCGLineCapSquare; + break; + } + } + + _miterLimit = stroke->miterLimit; + + _dashPhase = stroke->dashPhase; + + if (!stroke->dashPattern.empty()) { + NSMutableArray *dashPattern = [[NSMutableArray alloc] initWithCapacity:stroke->dashPattern.size()]; + for (auto value : stroke->dashPattern) { + [dashPattern addObject:@(value)]; + } + _dashPattern = dashPattern; + } + } + return self; +} + +- (instancetype _Nonnull)initWithShading:(LottieRenderContentShading * _Nonnull)shading lineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit dashPhase:(CGFloat)dashPhase dashPattern:(NSArray * _Nullable)dashPattern __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _shading = shading; + _lineWidth = lineWidth; + _lineJoin = lineJoin; + _lineCap = lineCap; + _miterLimit = miterLimit; + _dashPhase = dashPhase; + _dashPattern = dashPattern; + } + return self; +} + +@end + +@implementation LottieRenderContent + +- (instancetype _Nonnull)initWithPath:(LottiePath * _Nonnull)path stroke:(LottieRenderContentStroke * _Nullable)stroke fill:(LottieRenderContentFill * _Nullable)fill __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _path = path; + _stroke = stroke; + _fill = fill; + } + return self; +} + +@end + +@implementation LottieRenderNode + +- (instancetype _Nonnull)initWithPosition:(CGPoint)position bounds:(CGRect)bounds transform:(CATransform3D)transform opacity:(CGFloat)opacity masksToBounds:(bool)masksToBounds isHidden:(bool)isHidden globalRect:(CGRect)globalRect globalTransform:(CATransform3D)globalTransform renderContent:(LottieRenderContent * _Nullable)renderContent hasSimpleContents:(bool)hasSimpleContents isInvertedMatte:(bool)isInvertedMatte subnodes:(NSArray * _Nonnull)subnodes mask:(LottieRenderNode * _Nullable)mask __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _position = position; + _bounds = bounds; + _transform = transform; + _opacity = opacity; + _masksToBounds = masksToBounds; + _isHidden = isHidden; + _globalRect = globalRect; + _globalTransform= globalTransform; + _renderContent = renderContent; + _hasSimpleContents = hasSimpleContents; + _isInvertedMatte = isInvertedMatte; + _subnodes = subnodes; + _mask = mask; + } + return self; +} + +@end diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/lottiejson11/lottiejson11.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/lottiejson11/lottiejson11.cpp new file mode 100644 index 00000000000..f5a69b64e8a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/lottiejson11/lottiejson11.cpp @@ -0,0 +1,790 @@ +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +namespace lottiejson11 { + +static const int max_depth = 200; + +using std::string; +using std::vector; +using std::map; +using std::make_shared; +using std::initializer_list; +using std::move; + +/* Helper for representing null - just a do-nothing struct, plus comparison + * operators so the helpers in JsonValue work. We can't use nullptr_t because + * it may not be orderable. + */ +struct NullStruct { + bool operator==(NullStruct) const { return true; } + bool operator<(NullStruct) const { return false; } +}; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + +static void dump(NullStruct, string &out) { + out += "null"; +} + +static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } +} + +static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; +} + +static void dump(bool value, string &out) { + out += value ? "true" : "false"; +} + +static void dump(const string &value, string &out) { + out += '"'; + for (size_t i = 0; i < value.length(); i++) { + const char ch = value[i]; + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; +} + +static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; +} + +static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; +} + +void Json::dump(string &out) const { + m_ptr->dump(out); +} + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + +template +class Value : public JsonValue { +protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(std::move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { lottiejson11::dump(m_value, out); } +}; + +class JsonDouble final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonDouble(double value) : Value(value) {} +}; + +class JsonInt final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonInt(int value) : Value(value) {} +}; + +class JsonBoolean final : public Value { + bool bool_value() const override { return m_value; } +public: + explicit JsonBoolean(bool value) : Value(value) {} +}; + +class JsonString final : public Value { + const string &string_value() const override { return m_value; } +public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(std::move(value)) {} +}; + +class JsonArray final : public Value { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](size_t i) const override; +public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(std::move(value)) {} +}; + +class JsonObject final : public Value { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; +public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(std::move(value)) {} +}; + +class JsonNull final : public Value { +public: + JsonNull() : Value({}) {} +}; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ +struct Statics { + const std::shared_ptr null = make_shared(); + const std::shared_ptr t = make_shared(true); + const std::shared_ptr f = make_shared(false); + const string empty_string; + const vector empty_vector; + const map empty_map; + Statics() {} +}; + +static const Statics & statics() { + static const Statics s {}; + return s; +} + +static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; +} + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + +Json::Json() noexcept : m_ptr(statics().null) {} +Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} +Json::Json(double value) : m_ptr(make_shared(value)) {} +Json::Json(int value) : m_ptr(make_shared(value)) {} +Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} +Json::Json(const string &value) : m_ptr(make_shared(value)) {} +Json::Json(string &&value) : m_ptr(make_shared(std::move(value))) {} +Json::Json(const char * value) : m_ptr(make_shared(value)) {} +Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::array &&values) : m_ptr(make_shared(std::move(values))) {} +Json::Json(const Json::object &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::object &&values) : m_ptr(make_shared(std::move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + +Json::Type Json::type() const { return m_ptr->type(); } +double Json::number_value() const { return m_ptr->number_value(); } +int Json::int_value() const { return m_ptr->int_value(); } +bool Json::bool_value() const { return m_ptr->bool_value(); } +const string & Json::string_value() const { return m_ptr->string_value(); } +const vector & Json::array_items() const { return m_ptr->array_items(); } +const map & Json::object_items() const { return m_ptr->object_items(); } +const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + +double JsonValue::number_value() const { return 0; } +int JsonValue::int_value() const { return 0; } +bool JsonValue::bool_value() const { return false; } +const string & JsonValue::string_value() const { return statics().empty_string; } +const vector & JsonValue::array_items() const { return statics().empty_vector; } +const map & JsonValue::object_items() const { return statics().empty_map; } +const Json & JsonValue::operator[] (size_t) const { return static_null(); } +const Json & JsonValue::operator[] (const string &) const { return static_null(); } + +const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; +} +const Json & JsonArray::operator[] (size_t i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; +} + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + +bool Json::operator== (const Json &other) const { + if (m_ptr == other.m_ptr) + return true; + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); +} + +bool Json::operator< (const Json &other) const { + if (m_ptr == other.m_ptr) + return false; + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); +} + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ +static inline string esc(char c) { + char buf[12]; + if (static_cast(c) >= 0x20 && static_cast(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); +} + +static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); +} + +namespace { +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ +struct JsonParser final { + + /* State + */ + const string &str; + size_t i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(std::move(msg), Json()); + } + + template + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input after start of comment", false); + if (str[i] == '/') { // inline comment + i++; + // advance until next line, or end of input + while (i < str.size() && str[i] != '\n') { + i++; + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", false); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", false); + } + i += 2; + comment_found = true; + } + else + return fail("malformed comment", false); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + if (failed) return; + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (failed) return static_cast(0); + if (i == str.size()) + return fail("unexpected end of input", static_cast(0)); + + return str[i++]; + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast(pt); + } else if (pt < 0x800) { + out += static_cast((pt >> 6) | 0xC0); + out += static_cast((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast((pt >> 12) | 0xE0); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } else { + out += static_cast((pt >> 18) | 0xF0); + out += static_cast(((pt >> 12) & 0x3F) | 0x80); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++]; + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail("unescaped " + esc(ch) + " in string", ""); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++]; + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.substr(i, 4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail("bad \\u escape: " + esc, ""); + } + for (size_t j = 0; j < 4; j++) { + if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') + && !in_range(esc[j], '0', '9')) + return fail("bad \\u escape: " + esc, ""); + } + + long codepoint = strtol(esc.data(), nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail("invalid escape character " + esc(ch), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + size_t start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i], '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i], '1', '9')) { + i++; + while (in_range(str[i], '0', '9')) + i++; + } else { + return fail("invalid " + esc(str[i]) + " in number"); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast(std::numeric_limits::digits10)) { + return std::atoi(str.c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i], '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i], '0', '9')) + i++; + } + + return std::strtod(str.c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str.compare(i, expected.length(), expected) == 0) { + i += expected.length(); + return res; + } else { + return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail("expected '\"' in object, got " + esc(ch)); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail("expected ':' in object, got " + esc(ch)); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail("expected ',' in object, got " + esc(ch)); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail("expected ',' in list, got " + esc(ch)); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail("expected value, got " + esc(ch)); + } +}; +}//namespace { + +Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.failed) + return Json(); + if (parser.i != in.size()) + return parser.fail("unexpected trailing " + esc(in[parser.i])); + + return result; +} + +// Documented in lottiejson11.hpp +vector Json::parse_multi(const string &in, + std::string::size_type &parser_stop_pos, + string &err, + JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + parser_stop_pos = 0; + vector json_vec; + while (parser.i != in.size() && !parser.failed) { + json_vec.push_back(parser.parse_json(0)); + if (parser.failed) + break; + + // Check for another object + parser.consume_garbage(); + if (parser.failed) + break; + parser_stop_pos = parser.i; + } + return json_vec; +} + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + +bool Json::has_shape(const shape & types, string & err) const { + if (!is_object()) { + err = "expected JSON object, got " + dump(); + return false; + } + + const auto& obj_items = object_items(); + for (auto & item : types) { + const auto it = obj_items.find(item.first); + if (it == obj_items.cend() || it->second.type() != item.second) { + err = "bad type for " + item.first + " in " + dump(); + return false; + } + } + + return true; +} + +} // namespace lottiejson11 diff --git a/submodules/TelegramUI/Components/LottieMetal/BUILD b/submodules/TelegramUI/Components/LottieMetal/BUILD new file mode 100644 index 00000000000..dd63b1b0eb1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/BUILD @@ -0,0 +1,69 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +load( + "@build_bazel_rules_apple//apple:resources.bzl", + "apple_resource_bundle", + "apple_resource_group", +) +load("//build-system/bazel-utils:plist_fragment.bzl", + "plist_fragment", +) + +filegroup( + name = "LottieMetalSources", + srcs = glob([ + "Metal/**/*.metal", + ]), + visibility = ["//visibility:public"], +) + +plist_fragment( + name = "LottieMetalSourcesBundleInfoPlist", + extension = "plist", + template = + """ + CFBundleIdentifier + org.telegram.LottieMetalSources + CFBundleDevelopmentRegion + en + CFBundleName + LottieMetal + """ +) + +apple_resource_bundle( + name = "LottieMetalSourcesBundle", + infoplists = [ + ":LottieMetalSourcesBundleInfoPlist", + ], + resources = [ + ":LottieMetalSources", + ], +) + +swift_library( + name = "LottieMetal", + module_name = "LottieMetal", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + data = [ + ":LottieMetalSourcesBundle", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/AnimatedStickerNode", + "//submodules/GZip", + "//submodules/MetalEngine", + "//submodules/TelegramUI/Components/LottieCpp", + "//submodules/Components/HierarchyTrackingLayer", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/LottieMetal/Metal/LottieMetalShaders.metal b/submodules/TelegramUI/Components/LottieMetal/Metal/LottieMetalShaders.metal new file mode 100644 index 00000000000..41d3607b54b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Metal/LottieMetalShaders.metal @@ -0,0 +1,871 @@ +#include +using namespace metal; + +typedef struct +{ + packed_float2 position; + packed_float2 texCoord; +} QuadVertex; + +typedef struct +{ + packed_float2 position; +} Vertex; + +typedef struct +{ + float4 position [[position]]; + float2 texCoord; + float2 transformedPosition; +} QuadOut; + +typedef struct +{ + float4 position [[position]]; + float direction; +} FillVertexOut; + +float calculateNormalDirection(float2 a, float2 b, float2 c) { + float2 ab = b - a; + float2 ac = c - a; + + return ab.x * ac.y - ab.y * ac.x; +} + +vertex QuadOut quad_vertex_shader( + device QuadVertex const *vertices [[buffer(0)]], + uint vertexId [[vertex_id]], + device matrix const &transform [[buffer(1)]] +) { + QuadVertex in = vertices[vertexId]; + QuadOut out; + float4 position = transform * float4(float2(in.position), 0.0, 1.0); + out.position = position; + out.texCoord = in.texCoord; + out.transformedPosition = (transform * float4(float2(in.position), 0.0, 1.0)).xy; + + return out; +} + +vertex FillVertexOut fill_vertex_shader( + device Vertex const *vertices [[buffer(0)]], + uint vertexId [[vertex_id]], + device matrix const &transform [[buffer(1)]], + device packed_float2 const &baseVertex [[buffer(2)]] +) { + FillVertexOut out; + uint triangleIndex = vertexId / 3; + uint vertexInTriangleIndex = vertexId % 3; + + //[0, 1], [1, 2], [2, 3]... + //0, 1, 2 + + float2 sourcePosition; + float2 v1 = float2(vertices[triangleIndex].position); + float2 v2 = float2(vertices[triangleIndex + 1].position); + + sourcePosition = select( + select( + v2, + v1, + vertexInTriangleIndex == 1 + ), + baseVertex, + vertexInTriangleIndex == 0 + ); + + float normalDirection = calculateNormalDirection(baseVertex, v1, v2); + + float4 position = transform * float4(sourcePosition, 0.0, 1.0); + out.position = position; + + out.direction = sign(normalDirection); + + return out; +} + +struct ShapeOut { + half4 color [[color(1)]]; +}; + +fragment ShapeOut fragment_shader( + FillVertexOut in [[stage_in]], + ShapeOut current, + device const int32_t &mode [[buffer(1)]] +) { + ShapeOut out = current; + + if (mode == 0) { + half result = select(out.color.r, half(127.0 / 255.0), out.color.r == 0.0); + result += half(in.direction) * 3.0 / 255.0; + out.color.r = result; + } else { + out.color.r = out.color.r == 0.0 ? 1.0 : 0.0; + } + return out; +} + +fragment ShapeOut clear_mask_fragment( + QuadOut in [[stage_in]] +) { + ShapeOut out; + out.color = half4(0.0); + return out; +} + +struct ColorOut { + half4 color [[color(0)]]; +}; + +fragment ColorOut merge_color_fill_fragment_shader( + ShapeOut colorIn, + device const float4 &color [[buffer(0)]], + device const int32_t &mode [[buffer(1)]] +) { + ColorOut out; + + half4 sampledColor = half4(color); + sampledColor.r = sampledColor.r * sampledColor.a; + sampledColor.g = sampledColor.g * sampledColor.a; + sampledColor.b = sampledColor.b * sampledColor.a; + + if (mode == 0) { + half diff = abs(colorIn.color.r - 127.0 / 255.0); + float diffSelect = select(0.0, 1.0, diff > (2.0 / 255.0)); + float outColorFactor = select( + 0.0, + diffSelect, + colorIn.color.r > 1.0 / 255.0 + ); + out.color = sampledColor * outColorFactor; + } else { + float outColorFactor = select( + 0.0, + 1.0, + colorIn.color.r > 1.0 / 255.0 + ); + + out.color = sampledColor * outColorFactor; + } + + if (out.color.a == 0.0) { + //discard_fragment(); + } + + return out; +} + +typedef struct +{ + packed_float4 color; + float location; +} GradientColorStop; + +float linearGradientStep(float edge0, float edge1, float x) { + float t = clamp((x - edge0) / (edge1 - edge0), float(0), float(1)); + return t; +} + +fragment ColorOut merge_linear_gradient_fill_fragment_shader( + QuadOut quadIn [[stage_in]], + ShapeOut colorIn, + device const GradientColorStop *colorStops [[buffer(0)]], + device const int32_t &mode [[buffer(1)]], + device const uint &numColorStops [[buffer(2)]], + device const packed_float2 &localStartPosition [[buffer(3)]], + device const packed_float2 &localEndPosition [[buffer(4)]] +) { + ColorOut out; + + float4 sourceColor; + + if (numColorStops <= 1) { + sourceColor = colorStops[0].color; + } else { + float2 localPixelPosition = quadIn.transformedPosition.xy; + + float2 gradientVector = normalize(localEndPosition - localStartPosition); + float2 pointVector = localPixelPosition - localStartPosition; + float pixelDistance = dot(pointVector, gradientVector) / dot(gradientVector, gradientVector); + float gradientLength = length(localEndPosition - localStartPosition); + float pixelValue = clamp(pixelDistance / gradientLength, 0.0, 1.0); + + sourceColor = mix(colorStops[0].color, colorStops[1].color, linearGradientStep( + colorStops[0].location, + colorStops[1].location, + pixelValue + )); + for (int i = 1; i < (int)numColorStops - 1; i++) { + sourceColor = mix(sourceColor, colorStops[i + 1].color, linearGradientStep( + colorStops[i].location, + colorStops[i + 1].location, + pixelValue + )); + } + } + + half4 sampledColor = half4(sourceColor); + + sampledColor.r = sampledColor.r * sampledColor.a; + sampledColor.g = sampledColor.g * sampledColor.a; + sampledColor.b = sampledColor.b * sampledColor.a; + + if (mode == 0) { + half diff = abs(colorIn.color.r - 127.0 / 255.0); + float diffSelect = select(0.0, 1.0, diff > (2.0 / 255.0)); + float outColorFactor = select( + 0.0, + diffSelect, + colorIn.color.r > 1.0 / 255.0 + ); + out.color = sampledColor * outColorFactor; + } else { + float outColorFactor = select( + 0.0, + 1.0, + colorIn.color.r > 1.0 / 255.0 + ); + + out.color = sampledColor * outColorFactor; + } + + if (out.color.a == 0.0) { + //discard_fragment(); + } + + return out; +} + +fragment ColorOut merge_radial_gradient_fill_fragment_shader( + QuadOut quadIn [[stage_in]], + ShapeOut colorIn, + device const GradientColorStop *colorStops [[buffer(0)]], + device const int32_t &mode [[buffer(1)]], + device const uint &numColorStops [[buffer(2)]], + device const packed_float2 &localStartPosition [[buffer(3)]], + device const packed_float2 &localEndPosition [[buffer(4)]] +) { + ColorOut out; + + float4 sourceColor; + + if (numColorStops <= 1) { + sourceColor = colorStops[0].color; + } else { + float pixelDistance = distance(quadIn.transformedPosition.xy, localStartPosition); + float gradientLength = length(localEndPosition - localStartPosition); + float pixelValue = clamp(pixelDistance / gradientLength, 0.0, 1.0); + + sourceColor = colorStops[0].color; + for (int i = 0; i < (int)numColorStops - 1; i++) { + float currentStopLocation = colorStops[i].location; + float nextStopLocation = colorStops[i + 1].location; + float4 nextStopColor = colorStops[i + 1].color; + sourceColor = mix(sourceColor, nextStopColor, linearGradientStep( + currentStopLocation, + nextStopLocation, + pixelValue + )); + } + } + + half4 sampledColor = half4(sourceColor); + + sampledColor.r = sampledColor.r * sampledColor.a; + sampledColor.g = sampledColor.g * sampledColor.a; + sampledColor.b = sampledColor.b * sampledColor.a; + + if (mode == 0) { + half diff = abs(colorIn.color.r - 127.0 / 255.0); + float diffSelect = select(0.0, 1.0, diff > (2.0 / 255.0)); + float outColorFactor = select( + 0.0, + diffSelect, + colorIn.color.r > 1.0 / 255.0 + ); + out.color = sampledColor * outColorFactor; + } else { + float outColorFactor = select( + 0.0, + 1.0, + colorIn.color.r > 1.0 / 255.0 + ); + + out.color = sampledColor * outColorFactor; + } + + if (out.color.a == 0.0) { + //discard_fragment(); + } + + return out; +} + +typedef struct { + packed_float2 position; +} StrokePositionIn; + +typedef struct { + packed_float2 point; +} StrokePointIn; + +typedef struct { + float id; +} StrokeRoundJoinVertexIn; + +typedef struct { + packed_float4 position; +} StrokeMiterJoinVertexIn; + +typedef struct { + packed_float3 position; +} StrokeBevelJoinVertexIn; + +typedef struct { + packed_float2 position; +} StrokeCapVertexIn; + +typedef struct +{ + float4 position [[position]]; +} StrokeVertexOut; + +fragment ColorOut stroke_fragment_shader( + StrokeVertexOut in [[stage_in]], + device const float4 &color [[buffer(0)]] +) { + ColorOut out; + + half4 result = half4(color); + result.r *= result.a; + result.g *= result.a; + result.b *= result.a; + + out.color = result; + + return out; +} + +typedef struct { + int32_t bufferOffset; // 4 + packed_float2 start; // 4 * 2 + packed_float2 end; // 4 * 2 + packed_float2 cp1; // 4 * 2 + packed_float2 cp2; // 4 * 2 + float offset; // 4 +} BezierInputItem; + +kernel void evaluateBezier( + device BezierInputItem const *inputItems [[buffer(0)]], + device float *vertexData [[buffer(1)]], + device uint const &itemCount [[buffer(2)]], + uint2 index [[ thread_position_in_grid ]] +) { + if (index.x >= itemCount) { + return; + } + BezierInputItem item = inputItems[index.x]; + + float2 p0 = item.start; + float2 p1 = item.cp1; + float2 p2 = item.cp2; + float2 p3 = item.end; + + float t = (((float)index.y) + 1.0) / (8.0); + float oneMinusT = 1.0 - t; + + float2 value = oneMinusT * oneMinusT * oneMinusT * p0 + 3.0 * t * oneMinusT * oneMinusT * p1 + 3.0 * t * t * oneMinusT * p2 + t * t * t * p3; + + vertexData[item.bufferOffset + 2 * index.y] = value.x; + vertexData[item.bufferOffset + 2 * index.y + 1] = value.y; +} + +fragment half4 quad_offscreen_fragment( + QuadOut in [[stage_in]], + texture2d texture[[texture(0)]], + device float const &opacity [[buffer(1)]] +) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + half4 color = texture.sample(s, float2(in.texCoord.x, 1.0 - in.texCoord.y)); + + color *= half(opacity); + + return color; +} + +fragment half4 quad_offscreen_fragment_with_mask( + QuadOut in [[stage_in]], + texture2d texture[[texture(0)]], + texture2d maskTexture[[texture(1)]], + device float const &opacity [[buffer(1)]], + device uint const &maskMode [[buffer(2)]] +) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + half4 color = texture.sample(s, float2(in.texCoord.x, 1.0 - in.texCoord.y)); + half4 maskColor = maskTexture.sample(s, float2(in.texCoord.x, 1.0 - in.texCoord.y)); + + if (maskMode == 0) { + color *= maskColor.a; + } else { + color *= 1.0 - maskColor.a; + } + + color *= half(opacity); + + return color; +} + +bool myIsNan(float val) { + return (val < 0.0 || 0.0 < val || val == 0.0) ? false : true; +} + +bool isLinePointInvalid(float4 p) { + return p.w == 0.0 || myIsNan(p.x); +} + +// Adapted from https://github.com/rreusser/regl-gpu-lines + +vertex StrokeVertexOut strokeTerminalVertex( + uint instanceId [[instance_id]], + uint index [[vertex_id]], + device StrokePointIn const *points [[buffer(0)]], + device matrix const &transform [[buffer(1)]], + device packed_float2 const &_vertCnt2 [[buffer(2)]], + device packed_float2 const &_capJoinRes2 [[buffer(3)]], + device uint const &isJoinRound [[buffer(4)]], + device uint const &isCapRound [[buffer(5)]], + device float const &miterLimit [[buffer(6)]], + device float const &width [[buffer(7)]] +) { + const float2 ROUND_CAP_SCALE = float2(1.0, 1.0); + const float2 SQUARE_CAP_SCALE = float2(2.0, 2.0 / sqrt(3.0)); + + float2 _capScale = isCapRound ? ROUND_CAP_SCALE : SQUARE_CAP_SCALE; + + const float pi = 3.141592653589793; + + float2 xyB = points[instanceId * 3 + 0].point; + float2 xyC = points[instanceId * 3 + 1].point; + float2 xyD = points[instanceId * 3 + 2].point; + + StrokeVertexOut out; + + float4 pB = float4(xyB, 0.0, 1.0); + float4 pC = float4(xyC, 0.0, 1.0); + float4 pD = float4(xyD, 0.0, 1.0); + + // A sensible default for early returns + out.position = pB; + + bool aInvalid = false; + bool bInvalid = isLinePointInvalid(pB); + bool cInvalid = isLinePointInvalid(pC); + bool dInvalid = isLinePointInvalid(pD); + + // Vertex count for each part (first half of join, second (mirrored) half). Note that not all of + // these vertices may be used, for example if we have enough for a round cap but only draw a miter + // join. + float2 v = _vertCnt2 + 3.0; + + // Total vertex count + float N = dot(v, float2(1)); + + // If we're past the first half-join and half of the segment, then we swap all vertices and start + // over from the opposite end. + bool mirror = index >= v.x; + + // When rendering dedicated endpoints, this allows us to insert an end cap *alone* (without the attached + // segment and join) + if (dInvalid && mirror) { + return out; + } + + // Convert to screen-pixel coordinates + // Save w so we can perspective re-multiply at the end to get varyings depth-correct + float pw = mirror ? pC.w : pB.w; + pB = float4(float3(pB.xy, pB.z) / pB.w, 1); + pC = float4(float3(pC.xy, pC.z) / pC.w, 1); + pD = float4(float3(pD.xy, pD.z) / pD.w, 1); + + // If it's a cap, mirror A back onto C to accomplish a round + float4 pA = pC; + + // Reject if invalid or if outside viewing planes + if (bInvalid || cInvalid || max(abs(pB.z), abs(pC.z)) > 1.0) { + return out; + } + + // Swap everything computed so far if computing mirrored half + if (mirror) { + float4 vTmp = pC; pC = pB; pB = vTmp; + vTmp = pD; pD = pA; pA = vTmp; + bool bTmp = dInvalid; dInvalid = aInvalid; aInvalid = bTmp; + } + + bool isCap = !mirror; + + // Either flip A onto C (and D onto B) to produce a 180 degree-turn cap, or extrapolate to produce a + // degenerate (no turn) join, depending on whether we're inserting caps or just leaving ends hanging. + if (aInvalid) { pA = 2.0 * pB - pC; } + if (dInvalid) { pD = 2.0 * pC - pB; } + bool roundOrCap = isJoinRound || isCap; + + // Tangent and normal vectors + float2 tBC = pC.xy - pB.xy; + float lBC = length(tBC); + tBC /= lBC; + float2 nBC = float2(-tBC.y, tBC.x); + + float2 tAB = pB.xy - pA.xy; + float lAB = length(tAB); + if (lAB > 0.0) tAB /= lAB; + float2 nAB = float2(-tAB.y, tAB.x); + + float2 tCD = pD.xy - pC.xy; + float lCD = length(tCD); + if (lCD > 0.0) tCD /= lCD; + float2 nCD = float2(-tCD.y, tCD.x); + + // Clamp for safety, since we take the arccos + float cosB = clamp(dot(tAB, tBC), -1.0, 1.0); + + // This section is somewhat fragile. When lines are collinear, signs flip randomly and break orientation + // of the middle segment. The fix appears straightforward, but this took a few hours to get right. + const float tol = 1e-4; + float mirrorSign = mirror ? -1.0 : 1.0; + float dirB = -dot(tBC, nAB); + float dirC = dot(tBC, nCD); + bool bCollinear = abs(dirB) < tol; + bool cCollinear = abs(dirC) < tol; + bool bIsHairpin = bCollinear && cosB < 0.0; + // bool cIsHairpin = cCollinear && dot(tBC, tCD) < 0.0; + dirB = bCollinear ? -mirrorSign : sign(dirB); + dirC = cCollinear ? -mirrorSign : sign(dirC); + + float2 miter = bIsHairpin ? -tBC : 0.5 * (nAB + nBC) * dirB; + + // Compute our primary "join index", that is, the index starting at the very first point of the join. + // The second half of the triangle strip instance is just the first, reversed, and with vertices swapped! + float i = mirror ? N - index : index; + + // Decide the resolution of whichever feature we're drawing. n is twice the number of points used since + // that's the only form in which we use this number. + float res = (isCap ? _capJoinRes2.x : _capJoinRes2.y); + + // Shift the index to send unused vertices to an index below zero, which will then just get clamped to + // zero and result in repeated points, i.e. degenerate triangles. + i -= max(0.0, (mirror ? _vertCnt2.y : _vertCnt2.x) - res); + + // Use the direction to offset the index by one. This has the effect of flipping the winding number so + // that it's always consistent no matter which direction the join turns. + i += (dirB < 0.0 ? -1.0 : 0.0); + + // Vertices of the second (mirrored) half of the join are offset by one to get it to connect correctly + // in the middle, where the mirrored and unmirrored halves meet. + i -= mirror ? 1.0 : 0.0; + + // Clamp to zero and repeat unused excess vertices. + i = max(0.0, i); + + // Start with a default basis pointing along the segment with normal vector outward + float2 xBasis = tBC; + float2 yBasis = nBC * dirB; + + // Default point is 0 along the segment, 1 (width unit) normal to it + float2 xy = float2(0); + + if (i == res + 1.0) { + // pick off this one specific index to be the interior miter point + // If not div-by-zero, then sinB / (1 + cosB) + float m = cosB > -0.9999 ? (tAB.x * tBC.y - tAB.y * tBC.x) / (1.0 + cosB) : 0.0; + xy = float2(min(abs(m), min(lBC, lAB) / width), -1); + } else { + // Draw half of a join + float m2 = dot(miter, miter); + float lm = sqrt(m2); + yBasis = miter / lm; + xBasis = dirB * float2(yBasis.y, -yBasis.x); + bool isBevel = 1.0 > miterLimit * m2; + + if (((int)i) % 2 == 0) { + // Outer joint points + if (roundOrCap || i != 0.0) { + // Round joins + float theta = -0.5 * (acos(cosB) * (clamp(i, 0.0, res) / res) - pi) * (isCap ? 2.0 : 1.0); + xy = float2(cos(theta), sin(theta)); + + if (isCap) { + // A special multiplier factor for turning 3-point rounds into square caps (but leave the + // y == 0.0 point unaffected) + if (xy.y > 0.001) xy *= _capScale; + } + } else { + // Miter joins + yBasis = bIsHairpin ? float2(0) : miter; + xy.y = isBevel ? 1.0 : 1.0 / m2; + } + } else { + // Offset the center vertex position to get bevel SDF correct + if (isBevel && !roundOrCap) { + xy.y = -1.0 + sqrt((1.0 + cosB) * 0.5); + } + } + } + + // Point offset from main vertex position + float2 dP = float2x2(xBasis, yBasis) * xy; + + out.position = pB; + out.position.xy += width * dP; + out.position *= pw; + out.position = transform * out.position; + + return out; +} + +vertex StrokeVertexOut strokeInnerVertex( + uint instanceId [[instance_id]], + uint index [[vertex_id]], + device StrokePointIn const *points [[buffer(0)]], + device matrix const &transform [[buffer(1)]], + device packed_float2 const &_vertCnt2 [[buffer(2)]], + device packed_float2 const &_capJoinRes2 [[buffer(3)]], + device uint const &isJoinRound [[buffer(4)]], + device uint const &isCapRound [[buffer(5)]], + device float const &miterLimit [[buffer(6)]], + device float const &width [[buffer(7)]] +) { + const float2 ROUND_CAP_SCALE = float2(1.0, 1.0); + const float2 SQUARE_CAP_SCALE = float2(2.0, 2.0 / sqrt(3.0)); + + float2 _capScale = isCapRound ? ROUND_CAP_SCALE : SQUARE_CAP_SCALE; + + const float pi = 3.141592653589793; + + float2 xyA = points[instanceId + 0].point; + float2 xyB = points[instanceId + 1].point; + float2 xyC = points[instanceId + 2].point; + float2 xyD = points[instanceId + 3].point; + + StrokeVertexOut out; + + float4 pA = float4(xyA, 0.0, 1.0); + float4 pB = float4(xyB, 0.0, 1.0); + float4 pC = float4(xyC, 0.0, 1.0); + float4 pD = float4(xyD, 0.0, 1.0); + + // A sensible default for early returns + out.position = pB; + + bool aInvalid = isLinePointInvalid(pA); + bool bInvalid = isLinePointInvalid(pB); + bool cInvalid = isLinePointInvalid(pC); + bool dInvalid = isLinePointInvalid(pD); + + // Vertex count for each part (first half of join, second (mirrored) half). Note that not all of + // these vertices may be used, for example if we have enough for a round cap but only draw a miter + // join. + float2 v = _vertCnt2 + 3.0; + + // Total vertex count + float N = dot(v, float2(1)); + + // If we're past the first half-join and half of the segment, then we swap all vertices and start + // over from the opposite end. + bool mirror = index >= v.x; + + // When rendering dedicated endoints, this allows us to insert an end cap *alone* (without the attached + // segment and join) + + + // Convert to screen-pixel coordinates + // Save w so we can perspective re-multiply at the end to get varyings depth-correct + float pw = mirror ? pC.w : pB.w; + pA = float4(float3(pA.xy, pA.z) / pA.w, 1); + pB = float4(float3(pB.xy, pB.z) / pB.w, 1); + pC = float4(float3(pC.xy, pC.z) / pC.w, 1); + pD = float4(float3(pD.xy, pD.z) / pD.w, 1); + + // If it's a cap, mirror A back onto C to accomplish a round + + + // Reject if invalid or if outside viewing planes + if (bInvalid || cInvalid || max(abs(pB.z), abs(pC.z)) > 1.0) { + return out; + } + + // Swap everything computed so far if computing mirrored half + if (mirror) { + float4 vTmp = pC; pC = pB; pB = vTmp; + vTmp = pD; pD = pA; pA = vTmp; + bool bTmp = dInvalid; dInvalid = aInvalid; aInvalid = bTmp; + } + + const bool isCap = false; + + // Either flip A onto C (and D onto B) to produce a 180 degree-turn cap, or extrapolate to produce a + // degenerate (no turn) join, depending on whether we're inserting caps or just leaving ends hanging. + if (aInvalid) { pA = 2.0 * pB - pC; } + if (dInvalid) { pD = 2.0 * pC - pB; } + bool roundOrCap = isJoinRound || isCap; + + // Tangent and normal vectors + float2 tBC = pC.xy - pB.xy; + float lBC = length(tBC); + tBC /= lBC; + float2 nBC = float2(-tBC.y, tBC.x); + + float2 tAB = pB.xy - pA.xy; + float lAB = length(tAB); + if (lAB > 0.0) tAB /= lAB; + float2 nAB = float2(-tAB.y, tAB.x); + + float2 tCD = pD.xy - pC.xy; + float lCD = length(tCD); + if (lCD > 0.0) tCD /= lCD; + float2 nCD = float2(-tCD.y, tCD.x); + + // Clamp for safety, since we take the arccos + float cosB = clamp(dot(tAB, tBC), -1.0, 1.0); + + // This section is somewhat fragile. When lines are collinear, signs flip randomly and break orientation + // of the middle segment. The fix appears straightforward, but this took a few hours to get right. + const float tol = 1e-4; + float mirrorSign = mirror ? -1.0 : 1.0; + float dirB = -dot(tBC, nAB); + float dirC = dot(tBC, nCD); + bool bCollinear = abs(dirB) < tol; + bool cCollinear = abs(dirC) < tol; + bool bIsHairpin = bCollinear && cosB < 0.0; + // bool cIsHairpin = cCollinear && dot(tBC, tCD) < 0.0; + dirB = bCollinear ? -mirrorSign : sign(dirB); + dirC = cCollinear ? -mirrorSign : sign(dirC); + + float2 miter = bIsHairpin ? -tBC : 0.5 * (nAB + nBC) * dirB; + + // Compute our primary "join index", that is, the index starting at the very first point of the join. + // The second half of the triangle strip instance is just the first, reversed, and with vertices swapped! + float i = mirror ? N - index : index; + + // Decide the resolution of whichever feature we're drawing. n is twice the number of points used since + // that's the only form in which we use this number. + float res = (isCap ? _capJoinRes2.x : _capJoinRes2.y); + + // Shift the index to send unused vertices to an index below zero, which will then just get clamped to + // zero and result in repeated points, i.e. degenerate triangles. + i -= max(0.0, (mirror ? _vertCnt2.y : _vertCnt2.x) - res); + + // Use the direction to offset the index by one. This has the effect of flipping the winding number so + // that it's always consistent no matter which direction the join turns. + i += (dirB < 0.0 ? -1.0 : 0.0); + + // Vertices of the second (mirrored) half of the join are offset by one to get it to connect correctly + // in the middle, where the mirrored and unmirrored halves meet. + i -= mirror ? 1.0 : 0.0; + + // Clamp to zero and repeat unused excess vertices. + i = max(0.0, i); + + // Start with a default basis pointing along the segment with normal vector outward + float2 xBasis = tBC; + float2 yBasis = nBC * dirB; + + // Default point is 0 along the segment, 1 (width unit) normal to it + float2 xy = float2(0); + + if (i == res + 1.0) { + // pick off this one specific index to be the interior miter point + // If not div-by-zero, then sinB / (1 + cosB) + float m = cosB > -0.9999 ? (tAB.x * tBC.y - tAB.y * tBC.x) / (1.0 + cosB) : 0.0; + xy = float2(min(abs(m), min(lBC, lAB) / width), -1); + } else { + // Draw half of a join + float m2 = dot(miter, miter); + float lm = sqrt(m2); + yBasis = miter / lm; + xBasis = dirB * float2(yBasis.y, -yBasis.x); + bool isBevel = 1.0 > miterLimit * m2; + + if (((int)i) % 2 == 0) { + // Outer joint points + if (roundOrCap || i != 0.0) { + // Round joins + float theta = -0.5 * (acos(cosB) * (clamp(i, 0.0, res) / res) - pi) * (isCap ? 2.0 : 1.0); + xy = float2(cos(theta), sin(theta)); + + if (isCap) { + // A special multiplier factor for turning 3-point rounds into square caps (but leave the + // y == 0.0 point unaffected) + if (xy.y > 0.001) xy *= _capScale; + } + } else { + // Miter joins + yBasis = bIsHairpin ? float2(0) : miter; + xy.y = isBevel ? 1.0 : 1.0 / m2; + } + } else { + // Offset the center vertex position to get bevel SDF correct + if (isBevel && !roundOrCap) { + xy.y = -1.0 + sqrt((1.0 + cosB) * 0.5); + } + } + } + + // Point offset from main vertex position + float2 dP = float2x2(xBasis, yBasis) * xy; + + // The varying generation code handles clamping, if needed + + out.position = pB; + out.position.xy += width * dP; + out.position *= pw; + out.position = transform * out.position; + + return out; +} + +constant static float2 quadVertices[6] = { + float2(0.0, 0.0), + float2(1.0, 0.0), + float2(0.0, 1.0), + float2(1.0, 0.0), + float2(0.0, 1.0), + float2(1.0, 1.0) +}; + +struct MetalEngineRectangle { + float2 origin; + float2 size; +}; + +struct MetalEngineQuadVertexOut { + float4 position [[position]]; + float2 uv; +}; + +vertex MetalEngineQuadVertexOut blitVertex( + const device MetalEngineRectangle &rect [[ buffer(0) ]], + unsigned int vid [[ vertex_id ]] +) { + float2 quadVertex = quadVertices[vid]; + + MetalEngineQuadVertexOut out; + + out.position = float4(rect.origin.x + quadVertex.x * rect.size.x, rect.origin.y + quadVertex.y * rect.size.y, 0.0, 1.0); + out.position.x = -1.0 + out.position.x * 2.0; + out.position.y = -1.0 + out.position.y * 2.0; + + out.uv = float2(quadVertex.x, 1.0 - quadVertex.y); + + return out; +} + +fragment half4 blitFragment( + MetalEngineQuadVertexOut in [[stage_in]], + texture2d texture [[ texture(0) ]] +) { + constexpr sampler sampler(coord::normalized, address::repeat, filter::linear); + half4 color = texture.sample(sampler, in.uv); + + return half4(color.r, color.g, color.b, color.a); +} diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift new file mode 100644 index 00000000000..9e08a17d944 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift @@ -0,0 +1,1168 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import AnimatedStickerNode +import MetalEngine +import LottieCpp +import GZip +import MetalKit +import HierarchyTrackingLayer + +private final class BundleMarker: NSObject { +} + +private var metalLibraryValue: MTLLibrary? +func metalLibrary(device: MTLDevice) -> MTLLibrary? { + if let metalLibraryValue { + return metalLibraryValue + } + + let mainBundle = Bundle(for: BundleMarker.self) + guard let path = mainBundle.path(forResource: "LottieMetalSourcesBundle", ofType: "bundle") else { + return nil + } + guard let bundle = Bundle(path: path) else { + return nil + } + guard let library = try? device.makeDefaultLibrary(bundle: bundle) else { + return nil + } + + metalLibraryValue = library + return library +} + +private func generateTexture(device: MTLDevice, sideSize: Int, msaaSampleCount: Int) -> MTLTexture { + let textureDescriptor = MTLTextureDescriptor() + textureDescriptor.sampleCount = msaaSampleCount + if msaaSampleCount == 1 { + textureDescriptor.textureType = .type2D + } else { + textureDescriptor.textureType = .type2DMultisample + } + textureDescriptor.width = sideSize + textureDescriptor.height = sideSize + textureDescriptor.pixelFormat = .bgra8Unorm + //textureDescriptor.storageMode = .memoryless + textureDescriptor.storageMode = .private + textureDescriptor.usage = [.renderTarget, .shaderRead] + + return device.makeTexture(descriptor: textureDescriptor)! +} + +public func cacheLottieMetalAnimation(path: String) -> Data? { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { + let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data + if let lottieAnimation = LottieAnimation(data: decompressedData) { + let animationContainer = LottieAnimationContainer(animation: lottieAnimation) + + let startTime = CFAbsoluteTimeGetCurrent() + + let buffer = WriteBuffer() + var frameMapping = SerializedLottieMetalFrameMapping() + frameMapping.size = animationContainer.animation.size + frameMapping.frameCount = animationContainer.animation.frameCount + frameMapping.framesPerSecond = animationContainer.animation.framesPerSecond + for i in 0 ..< frameMapping.frameCount { + frameMapping.frameRanges[i] = 0 ..< 1 + } + serializeFrameMapping(buffer: buffer, frameMapping: frameMapping) + + for i in 0 ..< animationContainer.animation.frameCount { + animationContainer.update(i) + let frameRangeStart = buffer.length + if let node = animationContainer.getCurrentRenderTree(for: CGSize(width: 512.0, height: 512.0)) { + serializeNode(buffer: buffer, node: node) + let frameRangeEnd = buffer.length + frameMapping.frameRanges[i] = frameRangeStart ..< frameRangeEnd + } + } + + let previousLength = buffer.length + buffer.length = 0 + serializeFrameMapping(buffer: buffer, frameMapping: frameMapping) + buffer.length = previousLength + + buffer.trim() + let deltaTime = (CFAbsoluteTimeGetCurrent() - startTime) + let zippedData = TGGZipData(buffer.data, 1.0) + print("Serialized in \(deltaTime * 1000.0) size: \(zippedData.count / (1 * 1024 * 1024)) MB") + + return zippedData + } + } + return nil +} + +public func parseCachedLottieMetalAnimation(data: Data) -> LottieContentLayer.Content? { + if let unzippedData = TGGUnzipData(data, 32 * 1024 * 1024) { + let SerializedLottieMetalFrameMapping = deserializeFrameMapping(buffer: ReadBuffer(data: unzippedData)) + let serializedFrames = (SerializedLottieMetalFrameMapping, unzippedData) + return .serialized(frameMapping: serializedFrames.0, data: serializedFrames.1) + } + return nil +} + +private final class AnimationCacheState { + static let shared = AnimationCacheState() + + private final class QueuedTask { + let path: String + let cachePath: String + var isRunning: Bool + + init(path: String, cachePath: String) { + self.path = path + self.cachePath = cachePath + self.isRunning = false + } + } + + private final class Impl { + private let queue: Queue + private var queuedTasks: [QueuedTask] = [] + private var finishedTasks: [String] = [] + + init(queue: Queue) { + self.queue = queue + } + + func enqueue(path: String, cachePath: String) { + if self.finishedTasks.contains(path) { + return + } + if self.queuedTasks.contains(where: { $0.path == path }) { + return + } + self.queuedTasks.append(QueuedTask(path: path, cachePath: cachePath)) + while self.queuedTasks.count > 4 { + if let index = self.queuedTasks.firstIndex(where: { !$0.isRunning }) { + self.queuedTasks.remove(at: index) + } else { + break + } + } + self.update() + } + + private func update() { + while true { + var runningTaskCount = 0 + for task in self.queuedTasks { + if task.isRunning { + runningTaskCount += 1 + } + } + if runningTaskCount >= 2 { + break + } + guard let index = self.queuedTasks.firstIndex(where: { !$0.isRunning }) else { + break + } + self.run(task: self.queuedTasks[index]) + } + } + + private func run(task: QueuedTask) { + task.isRunning = true + let path = task.path + let cachePath = task.cachePath + let queue = self.queue + Queue.concurrentDefaultQueue().async { [weak self, weak task] in + if let zippedData = cacheLottieMetalAnimation(path: path) { + let _ = try? zippedData.write(to: URL(fileURLWithPath: cachePath), options: .atomic) + } + + queue.async { + guard let self, let task else { + return + } + self.finishedTasks.append(task.path) + guard let index = self.queuedTasks.firstIndex(where: { $0 === task }) else { + return + } + self.queuedTasks.remove(at: index) + self.update() + } + } + } + } + + private let queue = Queue(name: "AnimationCacheState", qos: .default) + private let impl: QueueLocalObject + + init() { + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue) + }) + } + + func enqueue(path: String, cachePath: String) { + self.impl.with { impl in + impl.enqueue(path: path, cachePath: cachePath) + } + } +} + +private func defaultTransformForSize(_ size: CGSize) -> CATransform3D { + var transform = CATransform3DIdentity + transform = CATransform3DScale(transform, 2.0 / size.width, 2.0 / size.height, 1.0) + transform = CATransform3DTranslate(transform, -size.width * 0.5, -size.height * 0.5, 0.0) + transform = CATransform3DTranslate(transform, 0.0, size.height, 0.0) + transform = CATransform3DScale(transform, 1.0, -1.0, 1.0) + + return transform +} + +private final class RenderFrameState { + let canvasSize: CGSize + let frameState: PathFrameState + let currentBezierIndicesBuffer: PathRenderBuffer + let currentBuffer: PathRenderBuffer + + var transform: CATransform3D + + init( + canvasSize: CGSize, + frameState: PathFrameState, + currentBezierIndicesBuffer: PathRenderBuffer, + currentBuffer: PathRenderBuffer + ) { + self.canvasSize = canvasSize + self.frameState = frameState + self.currentBezierIndicesBuffer = currentBezierIndicesBuffer + self.currentBuffer = currentBuffer + + self.transform = defaultTransformForSize(canvasSize) + } + + var transformStack: [CATransform3D] = [] + + func saveState() { + transformStack.append(transform) + } + + func restoreState() { + transform = transformStack.removeLast() + } + + func concat(_ other: CATransform3D) { + transform = CATransform3DConcat(other, transform) + } + + private func fillPath(path: LottiePath, shading: PathShading, rule: LottieFillRule, transform: CATransform3D) { + let fillState = PathRenderFillState(buffer: self.currentBuffer, bezierDataBuffer: self.currentBezierIndicesBuffer, fillRule: rule, shading: shading, transform: transform) + + path.enumerateItems { pathItem in + switch pathItem.pointee.type { + case .moveTo: + let point = pathItem.pointee.points.0 + fillState.begin(point: SIMD2(Float(point.x), Float(point.y))) + case .lineTo: + let point = pathItem.pointee.points.0 + fillState.addLine(to: SIMD2(Float(point.x), Float(point.y))) + case .curveTo: + let cp1 = pathItem.pointee.points.0 + let cp2 = pathItem.pointee.points.1 + let point = pathItem.pointee.points.2 + + fillState.addCurve( + to: SIMD2(Float(point.x), Float(point.y)), + cp1: SIMD2(Float(cp1.x), Float(cp1.y)), + cp2: SIMD2(Float(cp2.x), Float(cp2.y)) + ) + case .close: + fillState.close() + @unknown default: + break + } + } + + fillState.close() + + self.frameState.add(fill: fillState) + } + + private func strokePath(path: LottiePath, width: CGFloat, join: CGLineJoin, cap: CGLineCap, miterLimit: CGFloat, color: LottieColor, transform: CATransform3D) { + let strokeState = PathRenderStrokeState(buffer: self.currentBuffer, bezierDataBuffer: self.currentBezierIndicesBuffer, lineWidth: Float(width), lineJoin: join, lineCap: cap, miterLimit: Float(miterLimit), color: color, transform: transform) + + path.enumerateItems { pathItem in + switch pathItem.pointee.type { + case .moveTo: + let point = pathItem.pointee.points.0 + strokeState.begin(point: SIMD2(Float(point.x), Float(point.y))) + case .lineTo: + let point = pathItem.pointee.points.0 + strokeState.addLine(to: SIMD2(Float(point.x), Float(point.y))) + case .curveTo: + let cp1 = pathItem.pointee.points.0 + let cp2 = pathItem.pointee.points.1 + let point = pathItem.pointee.points.2 + + strokeState.addCurve( + to: SIMD2(Float(point.x), Float(point.y)), + cp1: SIMD2(Float(cp1.x), Float(cp1.y)), + cp2: SIMD2(Float(cp2.x), Float(cp2.y)) + ) + case .close: + strokeState.close() + @unknown default: + break + } + } + + strokeState.complete() + + self.frameState.add(stroke: strokeState) + } + + func renderNodeContent(item: LottieRenderContent, alpha: Double) { + if let fill = item.fill { + if let solidShading = fill.shading as? LottieRenderContentSolidShading { + self.fillPath( + path: item.path, + shading: .color(LottieColor(r: solidShading.color.r, g: solidShading.color.g, b: solidShading.color.b, a: solidShading.color.a * solidShading.opacity * alpha)), + rule: fill.fillRule, + transform: transform + ) + } else if let gradientShading = fill.shading as? LottieRenderContentGradientShading { + let gradientType: PathShading.Gradient.GradientType + switch gradientShading.gradientType { + case .linear: + gradientType = .linear + case .radial: + gradientType = .radial + @unknown default: + gradientType = .linear + } + var colorStops: [PathShading.Gradient.ColorStop] = [] + for colorStop in gradientShading.colorStops { + colorStops.append(PathShading.Gradient.ColorStop( + color: LottieColor(r: colorStop.color.r, g: colorStop.color.g, b: colorStop.color.b, a: colorStop.color.a * gradientShading.opacity * alpha), + location: Float(colorStop.location) + )) + } + let gradientShading = PathShading.Gradient( + gradientType: gradientType, + colorStops: colorStops, + start: SIMD2(Float(gradientShading.start.x), Float(gradientShading.start.y)), + end: SIMD2(Float(gradientShading.end.x), Float(gradientShading.end.y)) + ) + self.fillPath( + path: item.path, + shading: .gradient(gradientShading), + rule: fill.fillRule, + transform: transform + ) + } + } else if let stroke = item.stroke { + if let solidShading = stroke.shading as? LottieRenderContentSolidShading { + let color = solidShading.color + strokePath( + path: item.path, + width: stroke.lineWidth, + join: stroke.lineJoin, + cap: stroke.lineCap, + miterLimit: stroke.miterLimit, + color: LottieColor(r: color.r, g: color.g, b: color.b, a: color.a * solidShading.opacity * alpha), + transform: transform + ) + } + } + } + + func renderNode(node: LottieRenderNode, globalSize: CGSize, parentAlpha: CGFloat) { + let normalizedOpacity = node.opacity + let layerAlpha = normalizedOpacity * parentAlpha + + if node.isHidden || normalizedOpacity == 0.0 { + return + } + + saveState() + + var needsTempContext = false + if node.mask != nil { + needsTempContext = true + } else { + needsTempContext = (layerAlpha != 1.0 && !node.hasSimpleContents) || node.masksToBounds + } + + var maskSurface: PathFrameState.MaskSurface? + + if needsTempContext { + if node.mask != nil || node.masksToBounds { + var maskMode: PathFrameState.MaskSurface.Mode = .regular + + frameState.pushOffscreen(width: Int(node.globalRect.width), height: Int(node.globalRect.height)) + saveState() + + transform = defaultTransformForSize(node.globalRect.size) + concat(CATransform3DMakeTranslation(-node.globalRect.minX, -node.globalRect.minY, 0.0)) + concat(node.globalTransform) + + if node.masksToBounds { + let fillState = PathRenderFillState(buffer: self.currentBuffer, bezierDataBuffer: self.currentBezierIndicesBuffer, fillRule: .evenOdd, shading: .color(.init(r: 1.0, g: 1.0, b: 1.0, a: 1.0)), transform: transform) + + fillState.begin(point: SIMD2(Float(node.bounds.minX), Float(node.bounds.minY))) + fillState.addLine(to: SIMD2(Float(node.bounds.minX), Float(node.bounds.maxY))) + fillState.addLine(to: SIMD2(Float(node.bounds.maxX), Float(node.bounds.maxY))) + fillState.addLine(to: SIMD2(Float(node.bounds.maxX), Float(node.bounds.minY))) + fillState.close() + + frameState.add(fill: fillState) + } + if let maskNode = node.mask { + if maskNode.isInvertedMatte { + maskMode = .inverse + } + renderNode(node: maskNode, globalSize: globalSize, parentAlpha: 1.0) + } + + restoreState() + + maskSurface = frameState.popOffscreenMask(mode: maskMode) + } + + frameState.pushOffscreen(width: Int(node.globalRect.width), height: Int(node.globalRect.height)) + saveState() + + transform = defaultTransformForSize(node.globalRect.size) + concat(CATransform3DMakeTranslation(-node.globalRect.minX, -node.globalRect.minY, 0.0)) + concat(node.globalTransform) + } else { + concat(CATransform3DMakeTranslation(node.position.x, node.position.y, 0.0)) + concat(CATransform3DMakeTranslation(-node.bounds.origin.x, -node.bounds.origin.y, 0.0)) + concat(node.transform) + } + + var renderAlpha: CGFloat = 1.0 + if needsTempContext { + renderAlpha = 1.0 + } else { + renderAlpha = layerAlpha + } + + if let renderContent = node.renderContent { + renderNodeContent(item: renderContent, alpha: renderAlpha) + } + + for subnode in node.subnodes { + renderNode(node: subnode, globalSize: globalSize, parentAlpha: renderAlpha) + } + + if needsTempContext { + restoreState() + + concat(CATransform3DMakeTranslation(node.position.x, node.position.y, 0.0)) + concat(CATransform3DMakeTranslation(-node.bounds.origin.x, -node.bounds.origin.y, 0.0)) + concat(node.transform) + concat(CATransform3DInvert(node.globalTransform)) + + frameState.popOffscreen(rect: node.globalRect, transform: transform, opacity: Float(layerAlpha), mask: maskSurface) + } + + restoreState() + } + + func renderNode(animationContainer: LottieAnimationContainer, node: LottieRenderNodeProxy, globalSize: CGSize, parentAlpha: Float) { + let normalizedOpacity = node.layer.opacity + let layerAlpha = normalizedOpacity * parentAlpha + + if node.layer.isHidden || normalizedOpacity == 0.0 { + return + } + + saveState() + + var needsTempContext = false + if node.maskId != 0 { + needsTempContext = true + } else { + needsTempContext = (layerAlpha != 1.0 && !node.hasSimpleContents) || node.layer.masksToBounds + } + + var maskSurface: PathFrameState.MaskSurface? + + if needsTempContext { + if node.maskId != 0 || node.layer.masksToBounds { + var maskMode: PathFrameState.MaskSurface.Mode = .regular + + frameState.pushOffscreen(width: Int(node.globalRect.width), height: Int(node.globalRect.height)) + saveState() + + transform = defaultTransformForSize(node.globalRect.size) + concat(CATransform3DMakeTranslation(-node.globalRect.minX, -node.globalRect.minY, 0.0)) + concat(node.globalTransform) + + if node.layer.masksToBounds { + let fillState = PathRenderFillState(buffer: self.currentBuffer, bezierDataBuffer: self.currentBezierIndicesBuffer, fillRule: .evenOdd, shading: .color(.init(r: 1.0, g: 1.0, b: 1.0, a: 1.0)), transform: transform) + + fillState.begin(point: SIMD2(Float(node.layer.bounds.minX), Float(node.layer.bounds.minY))) + fillState.addLine(to: SIMD2(Float(node.layer.bounds.minX), Float(node.layer.bounds.maxY))) + fillState.addLine(to: SIMD2(Float(node.layer.bounds.maxX), Float(node.layer.bounds.maxY))) + fillState.addLine(to: SIMD2(Float(node.layer.bounds.maxX), Float(node.layer.bounds.minY))) + fillState.close() + + frameState.add(fill: fillState) + } + if node.maskId != 0 { + let maskNode = animationContainer.getRenderNodeProxy(byId: node.maskId) + if maskNode.isInvertedMatte { + maskMode = .inverse + } + renderNode(animationContainer: animationContainer, node: maskNode, globalSize: globalSize, parentAlpha: 1.0) + } + + restoreState() + + maskSurface = frameState.popOffscreenMask(mode: maskMode) + } + + frameState.pushOffscreen(width: Int(node.globalRect.width), height: Int(node.globalRect.height)) + saveState() + + transform = defaultTransformForSize(node.globalRect.size) + concat(CATransform3DMakeTranslation(-node.globalRect.minX, -node.globalRect.minY, 0.0)) + concat(node.globalTransform) + } else { + concat(CATransform3DMakeTranslation(node.layer.position.x, node.layer.position.y, 0.0)) + concat(CATransform3DMakeTranslation(-node.layer.bounds.origin.x, -node.layer.bounds.origin.y, 0.0)) + concat(node.layer.transform) + } + + var renderAlpha: Float = 1.0 + if needsTempContext { + renderAlpha = 1.0 + } else { + renderAlpha = layerAlpha + } + + /*if let renderContent = node.renderContent { + renderNodeContent(item: renderContent, alpha: renderAlpha) + }*/ + assert(false) + + for i in 0 ..< node.subnodeCount { + let subnode = animationContainer.getRenderNodeSubnodeProxy(byId: node.internalId, index: i) + renderNode(animationContainer: animationContainer, node: subnode, globalSize: globalSize, parentAlpha: renderAlpha) + } + + if needsTempContext { + restoreState() + + concat(CATransform3DMakeTranslation(node.layer.position.x, node.layer.position.y, 0.0)) + concat(CATransform3DMakeTranslation(-node.layer.bounds.origin.x, -node.layer.bounds.origin.y, 0.0)) + concat(node.layer.transform) + concat(CATransform3DInvert(node.globalTransform)) + + frameState.popOffscreen(rect: node.globalRect, transform: transform, opacity: Float(layerAlpha), mask: maskSurface) + } + + restoreState() + } +} + +public final class LottieContentLayer: MetalEngineSubjectLayer, MetalEngineSubject { + public enum Content { + case serialized(frameMapping: SerializedLottieMetalFrameMapping, data: Data) + case animation(LottieAnimationContainer) + + public var size: CGSize { + switch self { + case let .serialized(frameMapping, _): + return frameMapping.size + case let .animation(animation): + return animation.animation.size + } + } + + public var frameCount: Int { + switch self { + case let .serialized(frameMapping, _): + return frameMapping.frameCount + case let .animation(animation): + return animation.animation.frameCount + } + } + + public var framesPerSecond: Int { + switch self { + case let .serialized(frameMapping, _): + return frameMapping.framesPerSecond + case let .animation(animation): + return animation.animation.framesPerSecond + } + } + + func updateAndGetRenderNode(frameIndex: Int) -> LottieRenderNode? { + switch self { + case let .serialized(frameMapping, data): + guard let frameRange = frameMapping.frameRanges[frameIndex] else { + return nil + } + if frameRange.lowerBound < 0 || frameRange.upperBound > data.count { + return nil + } + return deserializeNode(buffer: ReadBuffer(data: data.subdata(in: frameRange))) + case let .animation(animation): + animation.update(frameIndex) + return animation.getCurrentRenderTree(for: CGSize(width: 512.0, height: 512.0)) + } + } + } + + private var content: Content? + public var frameIndex: Int = 0 + + public var internalData: MetalEngineSubjectInternalData? + + private let msaaSampleCount = 4 + private var renderBufferHeap: MTLHeap? + private var offscreenHeap: MTLHeap? + + private var multisampleTextureQueue: [MTLTexture] = [] + private var outTextureQueue: [MTLTexture] = [] + + private let currentBezierIndicesBuffer = PathRenderBuffer() + private let currentBuffer = PathRenderBuffer() + + final class PrepareState: ComputeState { + let pathRenderContext: PathRenderContext + + init?(device: MTLDevice) { + guard let pathRenderContext = PathRenderContext(device: device, msaaSampleCount: 4) else { + return nil + } + self.pathRenderContext = pathRenderContext + } + } + + final class RenderState: RenderToLayerState { + let pipelineState: MTLRenderPipelineState + + required init?(device: MTLDevice) { + guard let library = metalLibrary(device: device) else { + return nil + } + guard let vertexFunction = library.makeFunction(name: "blitVertex"), let fragmentFunction = library.makeFunction(name: "blitFragment") else { + return nil + } + + let pipelineDescriptor = MTLRenderPipelineDescriptor() + pipelineDescriptor.vertexFunction = vertexFunction + pipelineDescriptor.fragmentFunction = fragmentFunction + pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + guard let pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineDescriptor) else { + return nil + } + self.pipelineState = pipelineState + } + } + + public init(content: Content) { + self.content = content + + super.init() + + self.isOpaque = false + } + + public init(animation: LottieAnimationContainer) { + self.content = .animation(animation) + + super.init() + + self.isOpaque = false + } + + override public init(layer: Any) { + super.init(layer: layer) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var renderNodeCache: [Int: LottieRenderNode] = [:] + + public func update(context: MetalEngineSubjectContext) { + if self.bounds.isEmpty { + return + } + + let size = CGSize(width: 800.0, height: 800.0) + let msaaSampleCount = self.msaaSampleCount + + let renderSpec = RenderLayerSpec(size: RenderSize(width: Int(size.width), height: Int(size.height))) + + guard let content = self.content else { + return + } + + var maybeNode: LottieRenderNode? + if let current = self.renderNodeCache[self.frameIndex] { + maybeNode = current + } else { + if let value = content.updateAndGetRenderNode(frameIndex: self.frameIndex) { + maybeNode = value + //self.renderNodeCache[self.frameIndex] = value + } + } + guard let node = maybeNode else { + return + } + + self.currentBuffer.reset() + self.currentBezierIndicesBuffer.reset() + let frameState = PathFrameState(width: Int(size.width), height: Int(size.height), msaaSampleCount: self.msaaSampleCount, buffer: self.currentBuffer, bezierDataBuffer: self.currentBezierIndicesBuffer) + + let frameContext = RenderFrameState( + canvasSize: size, + frameState: frameState, + currentBezierIndicesBuffer: self.currentBezierIndicesBuffer, + currentBuffer: self.currentBuffer + ) + frameContext.concat(CATransform3DMakeScale(frameContext.canvasSize.width / content.size.width, frameContext.canvasSize.height / content.size.height, 1.0)) + + frameContext.renderNode(node: node, globalSize: frameContext.canvasSize, parentAlpha: 1.0) + + final class ComputeOutput { + let pathRenderContext: PathRenderContext + let renderBufferHeap: MTLHeap + let outTexture: MTLTexture + let takenMultisampleTextures: [MTLTexture] + + init(pathRenderContext: PathRenderContext, renderBufferHeap: MTLHeap, outTexture: MTLTexture, takenMultisampleTextures: [MTLTexture]) { + self.pathRenderContext = pathRenderContext + self.renderBufferHeap = renderBufferHeap + self.outTexture = outTexture + self.takenMultisampleTextures = takenMultisampleTextures + } + } + + var customCompletion: (() -> Void)? + + let computeOutput = context.compute(state: PrepareState.self, commands: { commandBuffer, state -> ComputeOutput? in + let renderBufferHeap: MTLHeap + if let current = self.renderBufferHeap { + renderBufferHeap = current + } else { + let heapDescriptor = MTLHeapDescriptor() + heapDescriptor.size = 32 * 1024 * 1024 + heapDescriptor.storageMode = .shared + heapDescriptor.cpuCacheMode = .writeCombined + if #available(iOS 13.0, *) { + heapDescriptor.hazardTrackingMode = .tracked + } + guard let value = MetalEngine.shared.device.makeHeap(descriptor: heapDescriptor) else { + print() + return nil + } + self.renderBufferHeap = value + renderBufferHeap = value + } + + guard let computeEncoder = commandBuffer.makeComputeCommandEncoder() else { + return nil + } + + frameState.prepare(heap: renderBufferHeap) + frameState.encodeCompute(context: state.pathRenderContext, computeEncoder: computeEncoder) + + computeEncoder.endEncoding() + + let multisampleTexture: MTLTexture + if !self.multisampleTextureQueue.isEmpty { + multisampleTexture = self.multisampleTextureQueue.removeFirst() + } else { + multisampleTexture = generateTexture(device: MetalEngine.shared.device, sideSize: Int(size.width), msaaSampleCount: msaaSampleCount) + } + + let tempTexture: MTLTexture + if !self.multisampleTextureQueue.isEmpty { + tempTexture = self.multisampleTextureQueue.removeFirst() + } else { + tempTexture = generateTexture(device: MetalEngine.shared.device, sideSize: Int(size.width), msaaSampleCount: msaaSampleCount) + } + + let outTexture: MTLTexture + if !self.outTextureQueue.isEmpty { + outTexture = self.outTextureQueue.removeFirst() + } else { + outTexture = generateTexture(device: MetalEngine.shared.device, sideSize: Int(size.width), msaaSampleCount: 1) + } + + let renderPassDescriptor = MTLRenderPassDescriptor() + renderPassDescriptor.colorAttachments[0].texture = multisampleTexture + if msaaSampleCount == 1 { + renderPassDescriptor.colorAttachments[0].storeAction = .store + } else { + renderPassDescriptor.colorAttachments[0].resolveTexture = outTexture + renderPassDescriptor.colorAttachments[0].storeAction = .multisampleResolve + } + renderPassDescriptor.colorAttachments[0].loadAction = .clear + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) + + renderPassDescriptor.colorAttachments[1].texture = tempTexture + renderPassDescriptor.colorAttachments[1].loadAction = .clear + renderPassDescriptor.colorAttachments[1].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) + renderPassDescriptor.colorAttachments[1].storeAction = .dontCare + + if msaaSampleCount == 4 { + renderPassDescriptor.setSamplePositions([ + MTLSamplePosition(x: 0.25, y: 0.25), + MTLSamplePosition(x: 0.75, y: 0.25), + MTLSamplePosition(x: 0.75, y: 0.75), + MTLSamplePosition(x: 0.25, y: 0.75) + ]) + } + + var offscreenHeapMemorySize = frameState.calculateOffscreenHeapMemorySize(device: MetalEngine.shared.device) + offscreenHeapMemorySize = max(offscreenHeapMemorySize, 1 * 1024 * 1024) + + let offscreenHeap: MTLHeap + if let current = self.offscreenHeap, current.size >= offscreenHeapMemorySize * 3 { + offscreenHeap = current + } else { + print("Creating offscreen heap \(offscreenHeapMemorySize * 3 / (1024 * 1024)) MB (3 * \(offscreenHeapMemorySize / (1024 * 1024)) MB)") + let heapDescriptor = MTLHeapDescriptor() + heapDescriptor.size = offscreenHeapMemorySize * 3 + heapDescriptor.storageMode = .private + heapDescriptor.cpuCacheMode = .defaultCache + if #available(iOS 13.0, *) { + heapDescriptor.hazardTrackingMode = .tracked + } + offscreenHeap = MetalEngine.shared.device.makeHeap(descriptor: heapDescriptor)! + self.offscreenHeap = offscreenHeap + } + + frameState.encodeOffscreen(context: state.pathRenderContext, heap: offscreenHeap, commandBuffer: commandBuffer, canvasSize: frameContext.canvasSize) + + guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { + self.multisampleTextureQueue.append(multisampleTexture) + self.multisampleTextureQueue.append(tempTexture) + return nil + } + + frameState.encodeRender(context: state.pathRenderContext, encoder: renderEncoder, canvasSize: frameContext.canvasSize) + + renderEncoder.endEncoding() + + let takenMultisampleTextures: [MTLTexture] = [multisampleTexture, tempTexture] + + return ComputeOutput( + pathRenderContext: state.pathRenderContext, + renderBufferHeap: renderBufferHeap, + outTexture: outTexture, + takenMultisampleTextures: takenMultisampleTextures + ) + }) + + context.renderToLayer(spec: renderSpec, state: RenderState.self, layer: self, inputs: computeOutput, commands: { [weak self] encoder, placement, computeOutput in + guard let computeOutput else { + return + } + + let effectiveRect = placement.effectiveRect + + var rect = SIMD4(Float(effectiveRect.minX), Float(effectiveRect.minY), Float(effectiveRect.width), Float(effectiveRect.height)) + encoder.setVertexBytes(&rect, length: 4 * 4, index: 0) + + encoder.setFragmentTexture(computeOutput.outTexture, index: 0) + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6) + + let takenMultisampleTextures = computeOutput.takenMultisampleTextures + let outTexture = computeOutput.outTexture + customCompletion = { + guard let self else { + return + } + for texture in takenMultisampleTextures { + self.multisampleTextureQueue.append(texture) + } + self.outTextureQueue.append(outTexture) + } + }) + + context.addCustomCompletion({ + customCompletion?() + }) + } +} + +public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode { + private final class LoadFrameTask { + var isCancelled: Bool = false + } + + private let hierarchyTrackingLayer: HierarchyTrackingLayer + + public var automaticallyLoadFirstFrame: Bool = false + public var automaticallyLoadLastFrame: Bool = false + public var playToCompletionOnStop: Bool = false + + private var layoutSize: CGSize? + private var lottieContent: LottieContentLayer.Content? + private var renderLayer: LottieContentLayer? + + private var displayLinkSubscription: SharedDisplayLinkDriver.Link? + + private var didStart: Bool = false + public var started: () -> Void = {} + + public var completed: (Bool) -> Void = { _ in } + private var didComplete: Bool = false + + public var frameUpdated: (Int, Int) -> Void = { _, _ in } + public var currentFrameIndex: Int { + get { + return self.frameIndex + } set(value) { + } + } + public var currentFrameCount: Int { + get { + if let lottieContent = self.lottieContent { + return Int(lottieContent.frameCount) + } else { + return 0 + } + } set(value) { + } + } + public var currentFrameImage: UIImage? { + return nil + } + + public private(set) var isPlaying: Bool = false + public var stopAtNearestLoop: Bool = false + + private let statusPromise = Promise() + public var status: Signal { + return self.statusPromise.get() + } + + public var autoplay: Bool = true + + public var visibility: Bool = false { + didSet { + self.updatePlayback() + } + } + + public var overrideVisibility: Bool = false + + public var isPlayingChanged: (Bool) -> Void = { _ in } + + private var sourceDisposable: Disposable? + private var playbackSize: CGSize? + + private var frameIndex: Int = 0 + private var playbackMode: AnimatedStickerPlaybackMode = .loop + + override public init() { + self.hierarchyTrackingLayer = HierarchyTrackingLayer() + + super.init() + + self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in + guard let self else { + return + } + self.updatePlayback() + } + self.hierarchyTrackingLayer.didExitHierarchy = { [weak self] in + guard let self else { + return + } + self.updatePlayback() + } + } + + deinit { + self.sourceDisposable?.dispose() + } + + public func cloneCurrentFrame(from otherNode: AnimatedStickerNode?) { + } + + public func setup(source: AnimatedStickerNodeSource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode, mode: AnimatedStickerMode) { + self.didStart = false + self.didComplete = false + + self.sourceDisposable?.dispose() + + self.playbackSize = CGSize(width: CGFloat(width), height: CGFloat(height)) + self.playbackMode = playbackMode + + var cachePathPrefix: String? + if case let .direct(cachePathPrefixValue) = mode { + cachePathPrefix = cachePathPrefixValue + } + + self.sourceDisposable = (source.directDataPath(attemptSynchronously: false) + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] path in + Queue.concurrentDefaultQueue().async { + guard let path else { + return + } + + var serializedFrames: (SerializedLottieMetalFrameMapping, Data)? + var cachePathValue: String? + if let cachePathPrefix { + let cachePath = cachePathPrefix + "-metal1" + cachePathValue = cachePath + if let data = try? Data(contentsOf: URL(fileURLWithPath: cachePath), options: .mappedIfSafe) { + if let unzippedData = TGGUnzipData(data, 32 * 1024 * 1024) { + let SerializedLottieMetalFrameMapping = deserializeFrameMapping(buffer: ReadBuffer(data: unzippedData)) + serializedFrames = (SerializedLottieMetalFrameMapping, unzippedData) + } + } + } + + let content: LottieContentLayer.Content + if let serializedFrames { + content = .serialized(frameMapping: serializedFrames.0, data: serializedFrames.1) + } else { + guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { + return + } + let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data + guard let lottieAnimation = LottieAnimation(data: decompressedData) else { + print("Could not load sticker data") + return + } + + let lottieInstance = LottieAnimationContainer(animation: lottieAnimation) + + if let cachePathValue { + AnimationCacheState.shared.enqueue(path: path, cachePath: cachePathValue) + } + + content = .animation(lottieInstance) + } + + Queue.mainQueue().async { + guard let self else { + return + } + self.setupPlayback(lottieContent: content, cachePathPrefix: cachePathPrefix) + } + } + }).strict() + } + + private func updatePlayback() { + let isPlaying = self.visibility && self.lottieContent != nil + if self.isPlaying != isPlaying { + self.isPlaying = isPlaying + self.isPlayingChanged(self.isPlaying) + } + + if isPlaying, let lottieContent = self.lottieContent { + if self.displayLinkSubscription == nil { + let fps: Int + if lottieContent.framesPerSecond == 30 { + fps = 30 + } else { + fps = 60 + } + self.displayLinkSubscription = SharedDisplayLinkDriver.shared.add(framesPerSecond: .fps(fps), { [weak self] deltaTime in + guard let self, let lottieContent = self.lottieContent, let renderLayer = self.renderLayer else { + return + } + if renderLayer.frameIndex == lottieContent.frameCount - 1 { + switch self.playbackMode { + case .loop: + self.completed(false) + case let .count(count): + if count <= 1 { + if !self.didComplete { + self.didComplete = true + self.completed(true) + } + return + } else { + self.playbackMode = .count(count - 1) + self.completed(false) + } + case .once: + if !self.didComplete { + self.didComplete = true + self.completed(true) + } + return + case .still: + break + } + } + + self.frameIndex = (self.frameIndex + 1) % lottieContent.frameCount + renderLayer.frameIndex = self.frameIndex + renderLayer.setNeedsUpdate() + }) + + self.renderLayer?.setNeedsUpdate() + } + } else { + self.displayLinkSubscription = nil + } + } + + private func setupPlayback(lottieContent: LottieContentLayer.Content, cachePathPrefix: String?) { + self.lottieContent = lottieContent + + let renderLayer = LottieContentLayer(content: lottieContent) + self.renderLayer = renderLayer + if let layoutSize = self.layoutSize { + renderLayer.frame = CGRect(origin: CGPoint(), size: layoutSize) + } + self.layer.addSublayer(renderLayer) + + self.updatePlayback() + } + + public func reset() { + } + + public func playOnce() { + } + + public func playLoop() { + } + + public func play(firstFrame: Bool, fromIndex: Int?) { + if let fromIndex = fromIndex { + self.frameIndex = fromIndex + } + } + + public func pause() { + } + + public func stop() { + } + + public func seekTo(_ position: AnimatedStickerPlaybackPosition) { + } + + public func playIfNeeded() -> Bool { + return false + } + + public func updateLayout(size: CGSize) { + self.layoutSize = size + if let renderLayer = self.renderLayer { + renderLayer.frame = CGRect(origin: CGPoint(), size: size) + } + } + + public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) { + } +} diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift new file mode 100644 index 00000000000..9013fff1daa --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift @@ -0,0 +1,374 @@ +import Foundation +import MetalKit +import LottieCpp + +private func alignUp(size: Int, align: Int) -> Int { + precondition(((align - 1) & align) == 0, "Align must be a power of two") + + let alignmentMask = align - 1 + return (size + alignmentMask) & ~alignmentMask +} + +final class PathFrameState { + struct RenderItem { + enum Content { + case fill(PathRenderFillState) + case stroke(PathRenderStrokeState) + case offscreen(surface: Surface, rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface?) + + func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, buffer: MTLBuffer, canvasSize: CGSize) { + switch self { + case let .fill(fill): + fill.encode(context: context, encoder: encoder, buffer: buffer) + case let .stroke(stroke): + stroke.encode(context: context, encoder: encoder, buffer: buffer) + case let .offscreen(surface, rect, transform, opacity, mask): + surface.encode(context: context, encoder: encoder, canvasSize: canvasSize, rect: rect, transform: transform, opacity: opacity, mask: mask) + } + } + } + + let content: Content + + init(content: Content) { + self.content = content + } + } + + final class MaskSurface { + enum Mode { + case regular + case inverse + } + + let surface: Surface + let mode: Mode + + init(surface: Surface, mode: Mode) { + self.surface = surface + self.mode = mode + } + } + + final class Surface { + let width: Int + let height: Int + private let msaaSampleCount: Int + + private var texture: MTLTexture? + + private(set) var items: [RenderItem] = [] + + init(width: Int, height: Int, msaaSampleCount: Int) { + self.width = width + self.height = height + self.msaaSampleCount = msaaSampleCount + } + + func add(fill: PathRenderFillState) { + self.items.append(RenderItem(content: .fill(fill))) + } + + func add(stroke: PathRenderStrokeState) { + self.items.append(RenderItem(content: .stroke(stroke))) + } + + func add(surface: Surface, rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface?) { + self.items.append(RenderItem(content: .offscreen(surface: surface, rect: rect, transform: transform, opacity: opacity, mask: mask))) + } + + func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, canvasSize: CGSize, rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface?) { + guard let texture = self.texture else { + print("Trying to encode offscreen blit pass, but no texture is present") + return + } + if mask != nil { + encoder.setRenderPipelineState(context.drawOffscreenWithMaskPipelineState) + } else { + encoder.setRenderPipelineState(context.drawOffscreenPipelineState) + } + + let identityTransform = CATransform3DIdentity + var identityTransformMatrix = SIMD16( + Float(identityTransform.m11), Float(identityTransform.m12), Float(identityTransform.m13), Float(identityTransform.m14), + Float(identityTransform.m21), Float(identityTransform.m22), Float(identityTransform.m23), Float(identityTransform.m24), + Float(identityTransform.m31), Float(identityTransform.m32), Float(identityTransform.m33), Float(identityTransform.m34), + Float(identityTransform.m41), Float(identityTransform.m42), Float(identityTransform.m43), Float(identityTransform.m44) + ) + + let boundingBox = rect.applying(CATransform3DGetAffineTransform(transform)) + + var quadVertices: [SIMD4] = [ + SIMD4(Float(boundingBox.minX), Float(boundingBox.minY), 0.0, 0.0), + SIMD4(Float(boundingBox.maxX), Float(boundingBox.minY), 1.0, 0.0), + SIMD4(Float(boundingBox.minX), Float(boundingBox.maxY), 0.0, 1.0), + + SIMD4(Float(boundingBox.maxX), Float(boundingBox.minY), 1.0, 0.0), + SIMD4(Float(boundingBox.minX), Float(boundingBox.maxY), 0.0, 1.0), + SIMD4(Float(boundingBox.maxX), Float(boundingBox.maxY), 1.0, 1.0) + ] + + encoder.setVertexBytes(&quadVertices, length: MemoryLayout>.size * quadVertices.count, index: 0) + encoder.setVertexBytes(&identityTransformMatrix, length: 4 * 4 * 4, index: 1) + encoder.setFragmentTexture(texture, index: 0) + if let mask { + guard let maskTexture = mask.surface.texture else { + print("Trying to encode offscreen blit pass, but no mask texture is present") + return + } + encoder.setFragmentTexture(maskTexture, index: 1) + } + var opacity = opacity + encoder.setFragmentBytes(&opacity, length: 4, index: 1) + + if let mask { + var maskMode: UInt32 = mask.mode == .regular ? 0 : 1; + encoder.setFragmentBytes(&maskMode, length: 4, index: 2) + } + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quadVertices.count) + } + + func offscreenTextureDescriptor() -> MTLTextureDescriptor { + let textureDescriptor = MTLTextureDescriptor() + textureDescriptor.textureType = .type2D + textureDescriptor.width = self.width + textureDescriptor.height = self.height + textureDescriptor.pixelFormat = .bgra8Unorm + textureDescriptor.storageMode = .private + textureDescriptor.usage = [.renderTarget, .shaderRead] + return textureDescriptor + } + + func offscreenTempTextureDescriptor() -> MTLTextureDescriptor { + let tempTextureDescriptor = MTLTextureDescriptor() + tempTextureDescriptor.sampleCount = self.msaaSampleCount + if self.msaaSampleCount == 1 { + tempTextureDescriptor.textureType = .type2D + } else { + tempTextureDescriptor.textureType = .type2DMultisample + } + tempTextureDescriptor.width = self.width + tempTextureDescriptor.height = self.height + tempTextureDescriptor.pixelFormat = .bgra8Unorm + tempTextureDescriptor.storageMode = .private + tempTextureDescriptor.usage = [.renderTarget, .shaderRead] + return tempTextureDescriptor + } + + func calculateOffscreenHeapMemorySize(device: MTLDevice) -> Int { + var result = 0 + + var sizeAndAlign = device.heapTextureSizeAndAlign(descriptor: self.offscreenTextureDescriptor()) + result += sizeAndAlign.size + + sizeAndAlign = device.heapTextureSizeAndAlign(descriptor: self.offscreenTempTextureDescriptor()) + result += sizeAndAlign.size * 2 + + for item in self.items { + if case let .offscreen(surface, _, _, _, mask) = item.content { + result += surface.calculateOffscreenHeapMemorySize(device: device) + if let mask { + result += mask.surface.calculateOffscreenHeapMemorySize(device: device) + } + } + } + return result + } + + func encodeOffscreen(context: PathRenderContext, heap: MTLHeap, commandBuffer: MTLCommandBuffer, materializedBuffer: MTLBuffer, canvasSize: CGSize) { + guard let resultTexture = heap.makeTexture(descriptor: self.offscreenTextureDescriptor()) else { + return + } + + for item in self.items { + if case let .offscreen(surface, _, _, _, mask) = item.content { + if let mask { + mask.surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize) + } + surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize) + } + } + + self.texture = resultTexture + + guard let offscreenTexture = heap.makeTexture(descriptor: self.offscreenTempTextureDescriptor()) else { + return + } + guard let tempTexture = heap.makeTexture(descriptor: self.offscreenTempTextureDescriptor()) else { + return + } + + let offscreenRenderPassDescriptor = MTLRenderPassDescriptor() + if msaaSampleCount == 1 { + offscreenRenderPassDescriptor.colorAttachments[0].texture = resultTexture + offscreenRenderPassDescriptor.colorAttachments[0].storeAction = .store + } else { + offscreenRenderPassDescriptor.colorAttachments[0].texture = offscreenTexture + offscreenRenderPassDescriptor.colorAttachments[0].storeAction = .multisampleResolve + offscreenRenderPassDescriptor.colorAttachments[0].resolveTexture = resultTexture + } + offscreenRenderPassDescriptor.colorAttachments[0].loadAction = .clear + offscreenRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) + + offscreenRenderPassDescriptor.colorAttachments[1].texture = tempTexture + offscreenRenderPassDescriptor.colorAttachments[1].loadAction = .clear + offscreenRenderPassDescriptor.colorAttachments[1].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) + offscreenRenderPassDescriptor.colorAttachments[1].storeAction = .dontCare + + if self.msaaSampleCount == 4 { + offscreenRenderPassDescriptor.setSamplePositions([ + MTLSamplePosition(x: 0.25, y: 0.25), + MTLSamplePosition(x: 0.75, y: 0.25), + MTLSamplePosition(x: 0.75, y: 0.75), + MTLSamplePosition(x: 0.25, y: 0.75) + ]) + } + + guard let offscreenRenderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: offscreenRenderPassDescriptor) else { + return + } + + for item in self.items { + item.content.encode(context: context, encoder: offscreenRenderEncoder, buffer: materializedBuffer, canvasSize: canvasSize) + } + + offscreenRenderEncoder.endEncoding() + } + } + + let msaaSampleCount: Int + let buffer: PathRenderBuffer + let bezierDataBuffer: PathRenderBuffer + + private var surfaceStack: [Surface] = [] + + private var materializedBuffer: MTLBuffer? + private var materializedBezierIndexBuffer: MTLBuffer? + + init(width: Int, height: Int, msaaSampleCount: Int, buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer) { + self.msaaSampleCount = msaaSampleCount + self.buffer = buffer + self.bezierDataBuffer = bezierDataBuffer + self.surfaceStack.append(Surface(width: width, height: height, msaaSampleCount: msaaSampleCount)) + } + + func pushOffscreen(width: Int, height: Int) { + self.surfaceStack.append(Surface(width: width, height: height, msaaSampleCount: self.msaaSampleCount)) + } + + func popOffscreen(rect: CGRect, transform: CATransform3D, opacity: Float, mask: MaskSurface? = nil) { + self.surfaceStack[self.surfaceStack.count - 2].add(surface: self.surfaceStack[self.surfaceStack.count - 1], rect: rect, transform: transform, opacity: opacity, mask: mask) + self.surfaceStack.removeLast() + } + + func popOffscreenMask(mode: MaskSurface.Mode) -> MaskSurface { + return MaskSurface( + surface: self.surfaceStack.removeLast(), + mode: mode + ) + } + + func add(fill: PathRenderFillState) { + self.surfaceStack.last!.add(fill: fill) + } + + func add(stroke: PathRenderStrokeState) { + self.surfaceStack.last!.add(stroke: stroke) + } + + func prepare(heap: MTLHeap) { + if self.buffer.length == 0 { + return + } + + var bufferOptions: MTLResourceOptions = [.storageModeShared, .cpuCacheModeWriteCombined] + if #available(iOS 13.0, *) { + bufferOptions.insert(.hazardTrackingModeTracked) + } + + guard let materializedBuffer = heap.makeBuffer(length: self.buffer.length, options: bufferOptions) else { + print("Could not create materialized buffer") + return + } + materializedBuffer.label = "materializedBuffer" + self.materializedBuffer = materializedBuffer + + memcpy(materializedBuffer.contents(), self.buffer.memory, self.buffer.length) + + if self.bezierDataBuffer.length != 0 { + guard let materializedBezierIndexBuffer = heap.makeBuffer(length: self.bezierDataBuffer.length, options: bufferOptions) else { + print("Could not create materialized bezier index buffer") + return + } + self.materializedBezierIndexBuffer = materializedBezierIndexBuffer + materializedBezierIndexBuffer.label = "materializedBezierIndexBuffer" + + memcpy(materializedBezierIndexBuffer.contents(), self.bezierDataBuffer.memory, self.bezierDataBuffer.length) + } + } + + func calculateOffscreenHeapMemorySize(device: MTLDevice) -> Int { + var result = 0 + for item in self.surfaceStack[0].items { + if case let .offscreen(surface, _, _, _, mask) = item.content { + result += surface.calculateOffscreenHeapMemorySize(device: device) + if let mask { + result += mask.surface.calculateOffscreenHeapMemorySize(device: device) + } + } + } + return result + } + + func encodeOffscreen(context: PathRenderContext, heap: MTLHeap, commandBuffer: MTLCommandBuffer, canvasSize: CGSize) { + guard let materializedBuffer = self.materializedBuffer else { + return + } + + assert(self.surfaceStack.count == 1) + + for item in self.surfaceStack[0].items { + if case let .offscreen(surface, _, _, _, mask) = item.content { + if let mask { + mask.surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize) + } + surface.encodeOffscreen(context: context, heap: heap, commandBuffer: commandBuffer, materializedBuffer: materializedBuffer, canvasSize: canvasSize) + } + } + } + + func encodeRender(context: PathRenderContext, encoder: MTLRenderCommandEncoder, canvasSize: CGSize) { + guard let materializedBuffer = self.materializedBuffer else { + return + } + + assert(self.surfaceStack.count == 1) + + for item in self.surfaceStack[0].items { + item.content.encode(context: context, encoder: encoder, buffer: materializedBuffer, canvasSize: canvasSize) + } + } + + func encodeCompute(context: PathRenderContext, computeEncoder: MTLComputeCommandEncoder) { + guard let materializedBuffer = self.materializedBuffer, let materializedBezierIndexBuffer = self.materializedBezierIndexBuffer else { + return + } + + let itemSize = 4 + 4 * 4 * 2 + 4 + let itemCount = self.bezierDataBuffer.length / itemSize + + computeEncoder.setComputePipelineState(context.prepareBezierPipelineState) + + let threadGroupWidth = 16 + let threadGroupHeight = 8 + + computeEncoder.useResource(materializedBuffer, usage: .write) + + computeEncoder.setBuffer(materializedBezierIndexBuffer, offset: 0, index: 0) + computeEncoder.setBuffer(materializedBuffer, offset: 0, index: 1) + var itemCountSize: UInt32 = UInt32(itemCount) + computeEncoder.setBytes(&itemCountSize, length: 4, index: 2) + let dispatchSize = alignUp(size: itemCount, align: threadGroupWidth) + computeEncoder.dispatchThreadgroups(MTLSize(width: dispatchSize, height: 1, depth: 1), threadsPerThreadgroup: MTLSize(width: 1, height: threadGroupHeight, depth: 1)) + } +} diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderBuffer.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderBuffer.swift new file mode 100644 index 00000000000..3190d0aa629 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderBuffer.swift @@ -0,0 +1,77 @@ +import Foundation +import MetalKit +import LottieCpp + +final class PathRenderBuffer { + private(set) var memory: UnsafeMutableRawPointer + private(set) var capacity: Int = 8 * 1024 * 1024 + private(set) var length: Int = 0 + + init() { + self.memory = malloc(self.capacity)! + } + + func reset() { + self.length = 0 + } + + func append(bytes: UnsafeRawPointer, length: Int) { + assert(length % 4 == 0) + + if self.length + length > self.capacity { + self.capacity = self.capacity * 2 + preconditionFailure() + } + memcpy(self.memory.advanced(by: self.length), bytes, length) + self.length += length + } + + func appendZero(count: Int) { + if self.length + length > self.capacity { + self.capacity = self.capacity * 2 + preconditionFailure() + } + self.length += count + } + + func append(float: Float) { + var value: Float = float + self.append(bytes: &value, length: 4) + } + + func append(float2: SIMD2) { + var value: SIMD2 = float2 + self.append(bytes: &value, length: 4 * 2) + } + + func append(float3: SIMD3) { + var value = float3.x + self.append(bytes: &value, length: 4) + value = float3.y + self.append(bytes: &value, length: 4) + value = float3.z + self.append(bytes: &value, length: 4) + } + + func append(int: Int32) { + var value = int + self.append(bytes: &value, length: 4) + } + + func appendBezierData( + bufferOffset: Int, + start: SIMD2, + end: SIMD2, + cp1: SIMD2, + cp2: SIMD2, + offset: Float + ) { + self.append(int: Int32(bufferOffset)) + self.append(float2: start) + self.append(float2: end) + self.append(float2: cp1) + self.append(float2: cp2) + self.append(float: offset) + } +} + diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderContext.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderContext.swift new file mode 100644 index 00000000000..524dda3006a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderContext.swift @@ -0,0 +1,208 @@ +import Foundation +import MetalKit +import LottieCpp + +final class PathRenderContext { + let device: MTLDevice + let msaaSampleCount: Int + + let prepareBezierPipelineState: MTLComputePipelineState + let shapePipelineState: MTLRenderPipelineState + let clearPipelineState: MTLRenderPipelineState + let mergeColorFillPipelineState: MTLRenderPipelineState + let mergeLinearGradientFillPipelineState: MTLRenderPipelineState + let mergeRadialGradientFillPipelineState: MTLRenderPipelineState + let strokeTerminalPipelineState: MTLRenderPipelineState + let strokeInnerPipelineState: MTLRenderPipelineState + let drawOffscreenPipelineState: MTLRenderPipelineState + let drawOffscreenWithMaskPipelineState: MTLRenderPipelineState + + let maximumThreadGroupWidth: Int + + init?(device: MTLDevice, msaaSampleCount: Int) { + self.device = device + self.msaaSampleCount = msaaSampleCount + + self.maximumThreadGroupWidth = device.maxThreadsPerThreadgroup.width + + guard let library = metalLibrary(device: device) else { + return nil + } + + guard let quadVertexFunction = library.makeFunction(name: "quad_vertex_shader") else { + print("Unable to find vertex function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let shapeVertexFunction = library.makeFunction(name: "fill_vertex_shader") else { + print("Unable to find vertex function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let shapeFragmentFunction = library.makeFunction(name: "fragment_shader") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let clearFragmentFunction = library.makeFunction(name: "clear_mask_fragment") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let mergeColorFillFragmentFunction = library.makeFunction(name: "merge_color_fill_fragment_shader") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let mergeLinearGradientFillFragmentFunction = library.makeFunction(name: "merge_linear_gradient_fill_fragment_shader") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let mergeRadialGradientFillFragmentFunction = library.makeFunction(name: "merge_radial_gradient_fill_fragment_shader") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let strokeFragmentFunction = library.makeFunction(name: "stroke_fragment_shader") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let strokeTerminalVertexFunction = library.makeFunction(name: "strokeTerminalVertex") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let strokeInnerVertexFunction = library.makeFunction(name: "strokeInnerVertex") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let prepareBezierPipelineFunction = library.makeFunction(name: "evaluateBezier") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let quadOffscreenFragmentFunction = library.makeFunction(name: "quad_offscreen_fragment") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + guard let quadOffscreenWithMaskFragmentFunction = library.makeFunction(name: "quad_offscreen_fragment_with_mask") else { + print("Unable to find fragment function. Are you sure you defined it and spelled the name right?") + return nil + } + + self.prepareBezierPipelineState = try! device.makeComputePipelineState(function: prepareBezierPipelineFunction) + + let shapePipelineDescriptor = MTLRenderPipelineDescriptor() + shapePipelineDescriptor.vertexFunction = shapeVertexFunction + shapePipelineDescriptor.fragmentFunction = shapeFragmentFunction + + shapePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + shapePipelineDescriptor.colorAttachments[0].writeMask = [] + shapePipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm + shapePipelineDescriptor.colorAttachments[1].writeMask = [.all] + shapePipelineDescriptor.rasterSampleCount = msaaSampleCount + + guard let shapePipelineState = try? device.makeRenderPipelineState(descriptor: shapePipelineDescriptor) else { + preconditionFailure() + } + self.shapePipelineState = shapePipelineState + + let clearPipelineDescriptor = MTLRenderPipelineDescriptor() + clearPipelineDescriptor.vertexFunction = quadVertexFunction + clearPipelineDescriptor.fragmentFunction = clearFragmentFunction + + clearPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + clearPipelineDescriptor.colorAttachments[0].writeMask = [] + clearPipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm + clearPipelineDescriptor.colorAttachments[1].writeMask = .all + clearPipelineDescriptor.rasterSampleCount = msaaSampleCount + + guard let clearPipelineState = try? device.makeRenderPipelineState(descriptor: clearPipelineDescriptor) else { + preconditionFailure() + } + self.clearPipelineState = clearPipelineState + + let mergePipelineDescriptor = MTLRenderPipelineDescriptor() + mergePipelineDescriptor.vertexFunction = quadVertexFunction + mergePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + mergePipelineDescriptor.colorAttachments[0].writeMask = [.all] + mergePipelineDescriptor.colorAttachments[0].isBlendingEnabled = true + mergePipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add + mergePipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add + mergePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .one + mergePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .one + mergePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha + mergePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .one + mergePipelineDescriptor.rasterSampleCount = msaaSampleCount + + mergePipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm + mergePipelineDescriptor.colorAttachments[1].writeMask = [] + + mergePipelineDescriptor.fragmentFunction = mergeColorFillFragmentFunction + guard let mergeColorFillPipelineState = try? device.makeRenderPipelineState(descriptor: mergePipelineDescriptor) else { + preconditionFailure() + } + self.mergeColorFillPipelineState = mergeColorFillPipelineState + + mergePipelineDescriptor.fragmentFunction = mergeLinearGradientFillFragmentFunction + guard let mergeLinearGradientFillPipelineState = try? device.makeRenderPipelineState(descriptor: mergePipelineDescriptor) else { + preconditionFailure() + } + self.mergeLinearGradientFillPipelineState = mergeLinearGradientFillPipelineState + + mergePipelineDescriptor.fragmentFunction = mergeRadialGradientFillFragmentFunction + guard let mergeRadialGradientFillPipelineState = try? device.makeRenderPipelineState(descriptor: mergePipelineDescriptor) else { + preconditionFailure() + } + self.mergeRadialGradientFillPipelineState = mergeRadialGradientFillPipelineState + + let strokePipelineDescriptor = MTLRenderPipelineDescriptor() + strokePipelineDescriptor.fragmentFunction = strokeFragmentFunction + strokePipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + strokePipelineDescriptor.colorAttachments[0].writeMask = [.all] + strokePipelineDescriptor.colorAttachments[0].isBlendingEnabled = true + strokePipelineDescriptor.colorAttachments[0].rgbBlendOperation = mergePipelineDescriptor.colorAttachments[0].rgbBlendOperation + strokePipelineDescriptor.colorAttachments[0].alphaBlendOperation = mergePipelineDescriptor.colorAttachments[0].alphaBlendOperation + strokePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor + strokePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor + strokePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor + strokePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor + strokePipelineDescriptor.rasterSampleCount = msaaSampleCount + + strokePipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm + strokePipelineDescriptor.colorAttachments[1].writeMask = [] + strokePipelineDescriptor.vertexFunction = strokeTerminalVertexFunction + guard let strokeTerminalPipelineState = try? device.makeRenderPipelineState(descriptor: strokePipelineDescriptor) else { + preconditionFailure() + } + self.strokeTerminalPipelineState = strokeTerminalPipelineState + + strokePipelineDescriptor.vertexFunction = strokeInnerVertexFunction + guard let strokeInnerPipelineState = try? device.makeRenderPipelineState(descriptor: strokePipelineDescriptor) else { + preconditionFailure() + } + self.strokeInnerPipelineState = strokeInnerPipelineState + + let drawOffscreenPipelineDescriptor = MTLRenderPipelineDescriptor() + drawOffscreenPipelineDescriptor.vertexFunction = quadVertexFunction + + drawOffscreenPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + drawOffscreenPipelineDescriptor.colorAttachments[0].writeMask = [.all] + drawOffscreenPipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm + drawOffscreenPipelineDescriptor.colorAttachments[1].writeMask = [] + drawOffscreenPipelineDescriptor.rasterSampleCount = msaaSampleCount + + drawOffscreenPipelineDescriptor.colorAttachments[0].isBlendingEnabled = true + drawOffscreenPipelineDescriptor.colorAttachments[0].rgbBlendOperation = mergePipelineDescriptor.colorAttachments[0].rgbBlendOperation + drawOffscreenPipelineDescriptor.colorAttachments[0].alphaBlendOperation = mergePipelineDescriptor.colorAttachments[0].alphaBlendOperation + drawOffscreenPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor + drawOffscreenPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor + drawOffscreenPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor + drawOffscreenPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = mergePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor + + drawOffscreenPipelineDescriptor.fragmentFunction = quadOffscreenFragmentFunction + guard let drawOffscreenPipelineState = try? device.makeRenderPipelineState(descriptor: drawOffscreenPipelineDescriptor) else { + preconditionFailure() + } + self.drawOffscreenPipelineState = drawOffscreenPipelineState + + drawOffscreenPipelineDescriptor.fragmentFunction = quadOffscreenWithMaskFragmentFunction + guard let drawOffscreenWithMaskPipelineState = try? device.makeRenderPipelineState(descriptor: drawOffscreenPipelineDescriptor) else { + preconditionFailure() + } + self.drawOffscreenWithMaskPipelineState = drawOffscreenWithMaskPipelineState + } +} + diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift new file mode 100644 index 00000000000..f7f22cdd56a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift @@ -0,0 +1,277 @@ +import Foundation +import MetalKit +import simd +import LottieCpp + +enum PathShading { + final class Gradient { + enum GradientType { + case linear + case radial + } + + struct ColorStop { + var color: LottieColor + var location: Float + + init(color: LottieColor, location: Float) { + self.color = color + self.location = location + } + } + + let gradientType: GradientType + let colorStops: [ColorStop] + let start: SIMD2 + let end: SIMD2 + + init(gradientType: GradientType, colorStops: [ColorStop], start: SIMD2, end: SIMD2) { + self.gradientType = gradientType + self.colorStops = colorStops + self.start = start + self.end = end + } + } + + case color(LottieColor) + case gradient(Gradient) +} + +final class PathRenderSubpathFillState { + private let buffer: PathRenderBuffer + private let bezierDataBuffer: PathRenderBuffer + let bufferOffset: Int + private(set) var vertexCount: Int = 0 + + private var firstPosition: SIMD2 + private var lastPosition: SIMD2 + + private(set) var minPosition: SIMD2 + private(set) var maxPosition: SIMD2 + + private var isClosed: Bool = false + + init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, point: SIMD2) { + self.buffer = buffer + self.bezierDataBuffer = bezierDataBuffer + self.bufferOffset = buffer.length + + self.firstPosition = point + self.lastPosition = point + self.minPosition = point + self.maxPosition = point + + self.add(point: point) + } + + func add(point: SIMD2) { + self.buffer.append(float2: point) + + self.minPosition.x = min(self.minPosition.x, point.x) + self.minPosition.y = min(self.minPosition.y, point.y) + self.maxPosition.x = max(self.maxPosition.x, point.x) + self.maxPosition.y = max(self.maxPosition.y, point.y) + + self.lastPosition = point + + self.vertexCount += 1 + } + + func addCurve(to point: SIMD2, cp1: SIMD2, cp2: SIMD2) { + let stepCount = 8 + self.bezierDataBuffer.appendBezierData( + bufferOffset: self.buffer.length / 4, + start: self.lastPosition, + end: point, + cp1: cp1, + cp2: cp2, + offset: 0.0 + ) + self.buffer.appendZero(count: 4 * 2 * stepCount) + self.vertexCount += stepCount + + let (curveMin, curveMax) = bezierBounds(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point) + + self.minPosition.x = min(self.minPosition.x, curveMin.x) + self.minPosition.y = min(self.minPosition.y, curveMin.y) + self.maxPosition.x = max(self.maxPosition.x, curveMax.x) + self.maxPosition.y = max(self.maxPosition.y, curveMax.y) + + self.lastPosition = point + } + + func close() { + if self.isClosed { + assert(false) + } else { + self.isClosed = true + + if self.lastPosition != self.firstPosition { + self.add(point: self.firstPosition) + } + } + } +} + +final class PathRenderFillState { + private let buffer: PathRenderBuffer + private let bezierDataBuffer: PathRenderBuffer + private let fillRule: LottieFillRule + private let shading: PathShading + private let transform: CATransform3D + + private var currentSubpath: PathRenderSubpathFillState? + private(set) var subpaths: [PathRenderSubpathFillState] = [] + + init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, fillRule: LottieFillRule, shading: PathShading, transform: CATransform3D) { + self.buffer = buffer + self.bezierDataBuffer = bezierDataBuffer + self.fillRule = fillRule + self.shading = shading + self.transform = transform + } + + func begin(point: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.close() + self.subpaths.append(currentSubpath) + self.currentSubpath = nil + } + + self.currentSubpath = PathRenderSubpathFillState(buffer: self.buffer, bezierDataBuffer: self.bezierDataBuffer, point: point) + } + + func addLine(to point: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.add(point: point) + } + } + + func addCurve(to point: SIMD2, cp1: SIMD2, cp2: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.addCurve(to: point, cp1: cp1, cp2: cp2) + } + } + + func close() { + if let currentSubpath = self.currentSubpath { + currentSubpath.close() + self.subpaths.append(currentSubpath) + self.currentSubpath = nil + } + } + + func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, buffer: MTLBuffer) { + if self.subpaths.isEmpty { + return + } + var minPosition: SIMD2 = self.subpaths[0].minPosition + var maxPosition: SIMD2 = self.subpaths[0].maxPosition + for subpath in self.subpaths { + minPosition.x = min(minPosition.x, subpath.minPosition.x) + minPosition.y = min(minPosition.y, subpath.minPosition.y) + maxPosition.x = max(maxPosition.x, subpath.maxPosition.x) + maxPosition.y = max(maxPosition.y, subpath.maxPosition.y) + } + + let localBoundingBox = CGRect(x: CGFloat(minPosition.x), y: CGFloat(minPosition.y), width: CGFloat(maxPosition.x - minPosition.x), height: CGFloat(maxPosition.y - minPosition.y)) + if localBoundingBox.isEmpty { + return + } + + var transformMatrix = simd_float4x4( + SIMD4(Float(transform.m11), Float(transform.m12), Float(transform.m13), Float(transform.m14)), + SIMD4(Float(transform.m21), Float(transform.m22), Float(transform.m23), Float(transform.m24)), + SIMD4(Float(transform.m31), Float(transform.m32), Float(transform.m33), Float(transform.m34)), + SIMD4(Float(transform.m41), Float(transform.m42), Float(transform.m43), Float(transform.m44)) + ) + + let identityTransform = CATransform3DIdentity + var identityTransformMatrix = SIMD16( + Float(identityTransform.m11), Float(identityTransform.m12), Float(identityTransform.m13), Float(identityTransform.m14), + Float(identityTransform.m21), Float(identityTransform.m22), Float(identityTransform.m23), Float(identityTransform.m24), + Float(identityTransform.m31), Float(identityTransform.m32), Float(identityTransform.m33), Float(identityTransform.m34), + Float(identityTransform.m41), Float(identityTransform.m42), Float(identityTransform.m43), Float(identityTransform.m44) + ) + + let transform = CATransform3DGetAffineTransform(self.transform) + let boundingBox = localBoundingBox.applying(transform) + let baseVertex = boundingBox.origin.applying(transform.inverted()) + + encoder.setRenderPipelineState(context.clearPipelineState) + + var quadVertices: [SIMD4] = [ + SIMD4(Float(boundingBox.minX), Float(boundingBox.minY), 0.0, 0.0), + SIMD4(Float(boundingBox.maxX), Float(boundingBox.minY), 1.0, 0.0), + SIMD4(Float(boundingBox.minX), Float(boundingBox.maxY), 0.0, 1.0), + + SIMD4(Float(boundingBox.maxX), Float(boundingBox.minY), 1.0, 0.0), + SIMD4(Float(boundingBox.minX), Float(boundingBox.maxY), 0.0, 1.0), + SIMD4(Float(boundingBox.maxX), Float(boundingBox.maxY), 1.0, 1.0) + ] + + encoder.setVertexBytes(&quadVertices, length: MemoryLayout>.size * quadVertices.count, index: 0) + encoder.setVertexBytes(&identityTransformMatrix, length: 4 * 4 * 4, index: 1) + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quadVertices.count) + + encoder.setRenderPipelineState(context.shapePipelineState) + encoder.setVertexBytes(&transformMatrix, length: 4 * 4 * 4, index: 1) + var baseVertexData = SIMD2(Float(baseVertex.x), Float(baseVertex.y)) + encoder.setVertexBytes(&baseVertexData, length: 4 * 2, index: 2) + + var modeBytes: Int32 = self.fillRule == .winding ? 0 : 1 + encoder.setFragmentBytes(&modeBytes, length: 4, index: 1) + + for subpath in self.subpaths { + encoder.setVertexBuffer(buffer, offset: subpath.bufferOffset, index: 0) + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: (subpath.vertexCount - 1) * 3) + } + + encoder.setVertexBytes(&quadVertices, length: MemoryLayout>.size * quadVertices.count, index: 0) + encoder.setVertexBytes(&identityTransformMatrix, length: 4 * 4 * 4, index: 1) + + switch self.shading { + case let .color(color): + encoder.setRenderPipelineState(context.mergeColorFillPipelineState) + + var colorVector = SIMD4(Float(color.r), Float(color.g), Float(color.b), Float(color.a)) + encoder.setFragmentBytes(&colorVector, length: MemoryLayout>.size, index: 0) + case let .gradient(gradient): + switch gradient.gradientType { + case .linear: + encoder.setRenderPipelineState(context.mergeLinearGradientFillPipelineState) + case .radial: + encoder.setRenderPipelineState(context.mergeRadialGradientFillPipelineState) + } + + var modeBytes: Int32 = self.fillRule == .winding ? 0 : 1 + encoder.setFragmentBytes(&modeBytes, length: 4, index: 1) + + let colorStopSize = 4 * 4 + 4 + var colorStopsData = Data(count: colorStopSize * gradient.colorStops.count) + colorStopsData.withUnsafeMutableBytes { buffer in + let bytes = buffer.baseAddress!.assumingMemoryBound(to: Float.self) + for i in 0 ..< gradient.colorStops.count { + let colorStop = gradient.colorStops[i] + bytes[i * 5 + 0] = Float(colorStop.color.r) + bytes[i * 5 + 1] = Float(colorStop.color.g) + bytes[i * 5 + 2] = Float(colorStop.color.b) + bytes[i * 5 + 3] = Float(colorStop.color.a) + bytes[i * 5 + 4] = colorStop.location + } + encoder.setFragmentBytes(buffer.baseAddress!, length: buffer.count, index: 0) + } + + var numColorStops: UInt32 = UInt32(gradient.colorStops.count) + encoder.setFragmentBytes(&numColorStops, length: 4, index: 2) + + var startPosition = transformMatrix * SIMD4(gradient.start.x, gradient.start.y, 0.0, 1.0) + encoder.setFragmentBytes(&startPosition, length: 4 * 2, index: 3) + var endPosition = transformMatrix * SIMD4(gradient.end.x, gradient.end.y, 0.0, 1.0) + encoder.setFragmentBytes(&endPosition, length: 4 * 2, index: 4) + } + + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quadVertices.count) + } +} + diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift new file mode 100644 index 00000000000..ce9accfe1fc --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift @@ -0,0 +1,425 @@ +import Foundation +import MetalKit +import LottieCpp + +func evaluateBezier(p0: SIMD2, p1: SIMD2, p2: SIMD2, p3: SIMD2, t: Float) -> SIMD2 { + let t2 = t * t + let t3 = t * t * t + + let A = (3 * t2 - 3 * t3) + let B = (3 * t3 - 6 * t2 + 3 * t) + let C = (3 * t2 - t3 - 3 * t + 1) + + let value = t3 * p3 + A * p2 + B * p1 + C * p0 + return value +} + +func evaluateBezier(p0: Float, p1: Float, p2: Float, p3: Float, t: Float) -> Float { + let oneMinusT = 1.0 - t + + let value = oneMinusT * oneMinusT * oneMinusT * p0 + 3.0 * t * oneMinusT * oneMinusT * p1 + 3.0 * t * t * oneMinusT * p2 + t * t * t * p3 + return value +} + +func solveQuadratic(p0: Float, p1: Float, p2: Float, p3: Float) -> (Float, Float) { + let i = p1 - p0 + let j = p2 - p1 + let k = p3 - p2 + + let a = (3 * i) - (6 * j) + (3 * k) + let b = (6 * j) - (6 * i) + let c = (3 * i) + + let sqrtPart = (b * b) - (4 * a * c) + let hasSolution = sqrtPart >= 0 + if !hasSolution { + return (.nan, .nan) + } + + let t1 = (-b + sqrt(sqrtPart)) / (2 * a) + let t2 = (-b - sqrt(sqrtPart)) / (2 * a) + + var s1: Float = .nan + var s2: Float = .nan + + if t1 >= 0.0 && t1 <= 1.0 { + s1 = evaluateBezier(p0: p0, p1: p1, p2: p2, p3: p3, t: t1) + } + + if t2 >= 0.0 && t2 <= 1.0 { + s2 = evaluateBezier(p0: p0, p1: p1, p2: p2, p3: p3, t: t2) + } + + return (s1, s2) +} + +func bezierBounds(p0: SIMD2, p1: SIMD2, p2: SIMD2, p3: SIMD2) -> (minPosition: SIMD2, maxPosition: SIMD2) { + let (solX1, solX2) = solveQuadratic(p0: p0.x, p1: p1.x, p2: p2.x, p3: p3.x) + let (solY1, solY2) = solveQuadratic(p0: p0.y, p1: p1.y, p2: p2.y, p3: p3.y) + + var minX = min(p0.x, p3.x) + var maxX = max(p0.x, p3.x) + + if !solX1.isNaN { + minX = min(minX, solX1) + maxX = max(maxX, solX1) + } + + if !solX2.isNaN { + minX = min(minX, solX2) + maxX = max(maxX, solX2) + } + + var minY = min(p0.y, p3.y) + var maxY = max(p0.y, p3.y) + + if !solY1.isNaN { + minY = min(minY, solY1) + maxY = max(maxY, solY1) + } + + if !solY2.isNaN { + minY = min(minY, solY2) + maxY = max(maxY, solY2) + } + + return (SIMD2(minX, minY), SIMD2(maxX, maxY)) +} + +final class PathRenderSubpathStrokeState { + struct TerminalState { + var bufferOffset: Int + var segmentCount: Int + } + + enum UnresolvedPosition { + case position(SIMD2) + case curve(p0: SIMD2, p1: SIMD2, p2: SIMD2, p3: SIMD2, t: Float) + + func resolve() -> SIMD2 { + switch self { + case let .position(value): + return value + case let .curve(p0, p1, p2, p3, t): + return evaluateBezier(p0: p0, p1: p1, p2: p2, p3: p3, t: t) + } + } + } + + private let buffer: PathRenderBuffer + private let bezierDataBuffer: PathRenderBuffer + let bufferOffset: Int + private(set) var vertexCount: Int = 0 + + private(set) var terminalState: TerminalState? + + private(set) var curveJoinVertexRanges: [Range] = [] + + private var firstPosition: SIMD2 + private var secondPosition: UnresolvedPosition + private var thirdPosition: UnresolvedPosition + + private var lastPosition: SIMD2 + private var lastMinus1Position: UnresolvedPosition + private var lastMinus2Position: UnresolvedPosition + + private(set) var isClosed: Bool = false + private(set) var isCompleted: Bool = false + + init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, point: SIMD2) { + self.buffer = buffer + self.bezierDataBuffer = bezierDataBuffer + self.bufferOffset = buffer.length + + self.firstPosition = point + self.secondPosition = .position(point) + self.thirdPosition = .position(point) + self.lastPosition = point + self.lastMinus1Position = .position(point) + self.lastMinus2Position = .position(point) + + self.add(point: point) + } + + func add(point: SIMD2) { + self.buffer.append(float2: point) + + self.lastMinus2Position = self.lastMinus1Position + self.lastMinus1Position = .position(self.lastPosition) + self.lastPosition = point + + self.vertexCount += 1 + if self.vertexCount == 2 { + self.secondPosition = .position(point) + } else if self.vertexCount == 3 { + self.thirdPosition = .position(point) + } + } + + func addCurve(to point: SIMD2, cp1: SIMD2, cp2: SIMD2) { + let stepCount = 8 + self.bezierDataBuffer.appendBezierData( + bufferOffset: self.buffer.length / 4, + start: self.lastPosition, + end: point, + cp1: cp1, + cp2: cp2, + offset: 0.0 + ) + self.buffer.appendZero(count: 4 * 2 * stepCount) + + if self.vertexCount == 1 { + self.secondPosition = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(1) / Float(stepCount)) + self.thirdPosition = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(2) / Float(stepCount)) + } + + self.vertexCount += stepCount + + self.lastMinus2Position = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(stepCount - 2) / Float(stepCount)) + self.lastMinus1Position = .curve(p0: self.lastPosition, p1: cp1, p2: cp2, p3: point, t: Float(stepCount - 1) / Float(stepCount)) + self.lastPosition = point + } + + func close() { + if self.isClosed { + assert(false) + } else { + self.isClosed = true + + if self.lastPosition != self.firstPosition { + self.add(point: self.firstPosition) + } + } + } + + func complete() { + if self.isCompleted { + assert(false) + } else { + if self.isClosed { + if self.vertexCount >= 3 { + self.buffer.append(float2: self.secondPosition.resolve()) + self.buffer.append(float2: self.thirdPosition.resolve()) + self.vertexCount += 2 + } + } else { + if self.vertexCount == 2 { + let terminalBufferOffset = self.buffer.length + + let resolvedSecond = self.secondPosition.resolve() + self.buffer.append(float2: self.firstPosition) + self.buffer.append(float2: self.firstPosition * 0.5 + resolvedSecond * 0.5) + self.buffer.append(float2: resolvedSecond) + + self.buffer.append(float2: resolvedSecond) + self.buffer.append(float2: self.firstPosition * 0.5 + resolvedSecond * 0.5) + self.buffer.append(float2: self.firstPosition) + + self.terminalState = TerminalState(bufferOffset: terminalBufferOffset, segmentCount: 2) + } else if self.vertexCount >= 3 { + let terminalBufferOffset = self.buffer.length + + self.buffer.append(float2: self.firstPosition) + self.buffer.append(float2: self.secondPosition.resolve()) + self.buffer.append(float2: self.thirdPosition.resolve()) + + self.buffer.append(float2: self.lastPosition) + self.buffer.append(float2: self.lastMinus1Position.resolve()) + self.buffer.append(float2: self.lastMinus2Position.resolve()) + + self.terminalState = TerminalState(bufferOffset: terminalBufferOffset, segmentCount: 2) + } + } + } + } +} + +final class PathRenderStrokeState { + private let buffer: PathRenderBuffer + private let bezierDataBuffer: PathRenderBuffer + private let lineWidth: Float + private let lineJoin: CGLineJoin + private let lineCap: CGLineCap + private let miterLimit: Float + private let color: LottieColor + private let transform: CATransform3D + + private var currentSubpath: PathRenderSubpathStrokeState? + private(set) var subpaths: [PathRenderSubpathStrokeState] = [] + + init(buffer: PathRenderBuffer, bezierDataBuffer: PathRenderBuffer, lineWidth: Float, lineJoin: CGLineJoin, lineCap: CGLineCap, miterLimit: Float, color: LottieColor, transform: CATransform3D) { + self.buffer = buffer + self.bezierDataBuffer = bezierDataBuffer + self.lineWidth = lineWidth + self.lineJoin = lineJoin + self.lineCap = lineCap + self.miterLimit = miterLimit + self.color = color + self.transform = transform + } + + func begin(point: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.complete() + self.subpaths.append(currentSubpath) + self.currentSubpath = nil + } + + self.currentSubpath = PathRenderSubpathStrokeState(buffer: self.buffer, bezierDataBuffer: self.bezierDataBuffer, point: point) + } + + func addLine(to point: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.add(point: point) + } + } + + func addCurve(to point: SIMD2, cp1: SIMD2, cp2: SIMD2) { + if let currentSubpath = self.currentSubpath { + currentSubpath.addCurve(to: point, cp1: cp1, cp2: cp2) + } + } + + func close() { + if let currentSubpath = self.currentSubpath { + currentSubpath.close() + currentSubpath.complete() + self.subpaths.append(currentSubpath) + self.currentSubpath = nil + } + } + + func complete() { + if let currentSubpath = self.currentSubpath { + currentSubpath.complete() + self.subpaths.append(currentSubpath) + self.currentSubpath = nil + } + } + + func encode(context: PathRenderContext, encoder: MTLRenderCommandEncoder, buffer: MTLBuffer) { + if self.subpaths.isEmpty { + return + } + + encoder.setVertexBuffer(buffer, offset: 0, index: 0) + + var colorVector = SIMD4(Float(color.r), Float(color.g), Float(color.b), Float(color.a)) + encoder.setFragmentBytes(&colorVector, length: MemoryLayout>.size, index: 0) + + var transformMatrix = SIMD16( + Float(transform.m11), Float(transform.m12), Float(transform.m13), Float(transform.m14), + Float(transform.m21), Float(transform.m22), Float(transform.m23), Float(transform.m24), + Float(transform.m31), Float(transform.m32), Float(transform.m33), Float(transform.m34), + Float(transform.m41), Float(transform.m42), Float(transform.m43), Float(transform.m44) + ) + encoder.setVertexBytes(&transformMatrix, length: 4 * 4 * 4, index: 1) + + let capRes2: Float + switch self.lineCap { + case .butt: + capRes2 = 2.0 + case .square: + capRes2 = 6.0 + case .round: + capRes2 = 24.0 + @unknown default: + capRes2 = 2.0 + } + let joinRes2: Float = self.lineJoin == .round ? 16.0 : 2.0 + + func computeCount(isEndpoints: Bool, insertCaps: Bool) -> SIMD2 { + if insertCaps { + if isEndpoints { + return SIMD2(capRes2, max(capRes2, joinRes2)) + } else { + return SIMD2(max(capRes2, joinRes2), max(capRes2, joinRes2)) + } + } else { + if isEndpoints { + return SIMD2(capRes2, joinRes2) + } else { + return SIMD2(joinRes2, joinRes2) + } + } + } + + var hasTerminalStates = false + + for subpath in self.subpaths { + let segmentCount = subpath.vertexCount - 1 + if segmentCount <= 0 { + continue + } + + if subpath.vertexCount >= 4 { + encoder.setRenderPipelineState(context.strokeInnerPipelineState) + + encoder.setVertexBufferOffset(subpath.bufferOffset, index: 0) + + var vertCnt2 = computeCount(isEndpoints: false, insertCaps: false) + encoder.setVertexBytes(&vertCnt2, length: 4 * 2, index: 2) + + var capJoinRes2 = SIMD2(capRes2, joinRes2) + encoder.setVertexBytes(&capJoinRes2, length: 4 * 2, index: 3) + + var isRoundJoinValue: UInt32 = self.lineJoin == .round ? 1 : 0 + encoder.setVertexBytes(&isRoundJoinValue, length: 4, index: 4) + + var isRoundCapValue: UInt32 = self.lineCap == .round ? 1 : 0 + encoder.setVertexBytes(&isRoundCapValue, length: 4, index: 5) + + var miterLimitValue: Float = self.lineJoin == .miter ? self.miterLimit : 1.0 + encoder.setVertexBytes(&miterLimitValue, length: 4, index: 6) + + var lineWidthValue: Float = self.lineWidth * 0.5 + encoder.setVertexBytes(&lineWidthValue, length: 4, index: 7) + + let vertexCount = 6 + Int(vertCnt2.x) + Int(vertCnt2.y) + 2 + encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertexCount, instanceCount: subpath.vertexCount - 4 + 1, baseInstance: 0) + } + + if subpath.terminalState != nil { + hasTerminalStates = true + } + } + + if hasTerminalStates { + encoder.setRenderPipelineState(context.strokeTerminalPipelineState) + + for subpath in self.subpaths { + let segmentCount = subpath.vertexCount - 1 + if segmentCount <= 0 { + continue + } + + if !subpath.isClosed { + if let terminalState = subpath.terminalState { + encoder.setVertexBufferOffset(terminalState.bufferOffset, index: 0) + + var vertCnt2 = computeCount(isEndpoints: true, insertCaps: false) + encoder.setVertexBytes(&vertCnt2, length: 4 * 2, index: 2) + + var capJoinRes2 = SIMD2(capRes2, joinRes2) + encoder.setVertexBytes(&capJoinRes2, length: 4 * 2, index: 3) + + var isRoundJoinValue: UInt32 = self.lineJoin == .round ? 1 : 0 + encoder.setVertexBytes(&isRoundJoinValue, length: 4, index: 4) + + var isRoundCapValue: UInt32 = self.lineCap == .round ? 1 : 0 + encoder.setVertexBytes(&isRoundCapValue, length: 4, index: 5) + + var miterLimitValue: Float = self.lineJoin == .miter ? self.miterLimit : 1.0 + encoder.setVertexBytes(&miterLimitValue, length: 4, index: 6) + + var lineWidthValue: Float = self.lineWidth * 0.5 + encoder.setVertexBytes(&lineWidthValue, length: 4, index: 7) + + let vertexCount = 6 + Int(vertCnt2.x) + Int(vertCnt2.y) + 2 + encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertexCount, instanceCount: terminalState.segmentCount, baseInstance: 0) + } + } + } + } + } +} diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift new file mode 100644 index 00000000000..c52b1769869 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift @@ -0,0 +1,519 @@ +import Foundation +import LottieCpp + +final class WriteBuffer { + private(set) var data: Data + private var capacity: Int + var length: Int + + init() { + self.capacity = 1024 + self.data = Data(count: self.capacity) + self.length = 0 + } + + func trim() { + self.data.count = self.length + self.capacity = self.data.count + } + + func write(bytes: UnsafeRawBufferPointer) { + if self.data.count < self.length + bytes.count { + self.data.count = self.data.count * 2 + } + self.data.withUnsafeMutableBytes { buffer -> Void in + memcpy(buffer.baseAddress!.advanced(by: self.length), bytes.baseAddress!, bytes.count) + } + self.length += bytes.count + } + + func write(uInt32 value: UInt32) { + var value = value + withUnsafeBytes(of: &value, { bytes in + self.write(bytes: bytes) + }) + } + + func write(uInt16 value: UInt16) { + var value = value + withUnsafeBytes(of: &value, { bytes in + self.write(bytes: bytes) + }) + } + + func write(uInt8 value: UInt8) { + var value = value + withUnsafeBytes(of: &value, { bytes in + self.write(bytes: bytes) + }) + } + + func write(float value: Float) { + var value = value + withUnsafeBytes(of: &value, { bytes in + self.write(bytes: bytes) + }) + } + + func write(point: CGPoint) { + self.write(float: Float(point.x)) + self.write(float: Float(point.y)) + } + + func write(size: CGSize) { + self.write(float: Float(size.width)) + self.write(float: Float(size.height)) + } + + func write(rect: CGRect) { + self.write(point: rect.origin) + self.write(size: rect.size) + } + + func write(transform: CATransform3D) { + self.write(float: Float(transform.m11)) + self.write(float: Float(transform.m12)) + self.write(float: Float(transform.m13)) + self.write(float: Float(transform.m14)) + self.write(float: Float(transform.m21)) + self.write(float: Float(transform.m22)) + self.write(float: Float(transform.m23)) + self.write(float: Float(transform.m24)) + self.write(float: Float(transform.m31)) + self.write(float: Float(transform.m32)) + self.write(float: Float(transform.m33)) + self.write(float: Float(transform.m34)) + self.write(float: Float(transform.m41)) + self.write(float: Float(transform.m42)) + self.write(float: Float(transform.m43)) + self.write(float: Float(transform.m44)) + } +} + +final class ReadBuffer { + private let data: Data + private var offset: Int + + init(data: Data) { + self.data = data + self.offset = 0 + } + + func read(bytes: UnsafeMutableRawBufferPointer) { + if self.offset + bytes.count <= self.data.count { + self.data.withUnsafeBytes { buffer -> Void in + memcpy(bytes.baseAddress!, buffer.baseAddress!.advanced(by: self.offset), bytes.count) + } + self.offset += bytes.count + } else { + preconditionFailure() + } + } + + func readUInt32() -> UInt32 { + var value: UInt32 = 0 + withUnsafeMutableBytes(of: &value, { bytes in + self.read(bytes: bytes) + }) + return value + } + + func readUInt16() -> UInt16 { + var value: UInt16 = 0 + withUnsafeMutableBytes(of: &value, { bytes in + self.read(bytes: bytes) + }) + return value + } + + func readUInt8() -> UInt8 { + var value: UInt8 = 0 + withUnsafeMutableBytes(of: &value, { bytes in + self.read(bytes: bytes) + }) + return value + } + + func readFloat() -> Float { + var value: Float = 0 + withUnsafeMutableBytes(of: &value, { bytes in + self.read(bytes: bytes) + }) + return value + } + + func readPoint() -> CGPoint { + return CGPoint(x: CGFloat(self.readFloat()), y: CGFloat(self.readFloat())) + } + + func readSize() -> CGSize { + return CGSize(width: CGFloat(self.readFloat()), height: CGFloat(self.readFloat())) + } + + func readRect() -> CGRect { + return CGRect(origin: self.readPoint(), size: self.readSize()) + } + + func readTransform() -> CATransform3D { + return CATransform3D( + m11: CGFloat(self.readFloat()), + m12: CGFloat(self.readFloat()), + m13: CGFloat(self.readFloat()), + m14: CGFloat(self.readFloat()), + m21: CGFloat(self.readFloat()), + m22: CGFloat(self.readFloat()), + m23: CGFloat(self.readFloat()), + m24: CGFloat(self.readFloat()), + m31: CGFloat(self.readFloat()), + m32: CGFloat(self.readFloat()), + m33: CGFloat(self.readFloat()), + m34: CGFloat(self.readFloat()), + m41: CGFloat(self.readFloat()), + m42: CGFloat(self.readFloat()), + m43: CGFloat(self.readFloat()), + m44: CGFloat(self.readFloat()) + ) + } +} + +private extension LottieColor { + init(argb: UInt32) { + self.init(r: CGFloat((argb >> 16) & 0xff) / 255.0, g: CGFloat((argb >> 8) & 0xff) / 255.0, b: CGFloat(argb & 0xff) / 255.0, a: CGFloat((argb >> 24) & 0xff) / 255.0) + } + + var argb: UInt32 { + return (UInt32(self.a * 255.0) << 24) | (UInt32(max(0.0, self.r) * 255.0) << 16) | (UInt32(max(0.0, self.g) * 255.0) << 8) | (UInt32(max(0.0, self.b) * 255.0)) + } +} + +private struct NodeFlags: OptionSet { + var rawValue: UInt8 + + init(rawValue: UInt8) { + self.rawValue = rawValue + } + + static let masksToBounds = NodeFlags(rawValue: 1 << 0) + static let isHidden = NodeFlags(rawValue: 1 << 1) + static let hasSimpleContents = NodeFlags(rawValue: 1 << 2) + static let isInvertedMatte = NodeFlags(rawValue: 1 << 3) + + static let hasRenderContent = NodeFlags(rawValue: 1 << 4) + static let hasSubnodes = NodeFlags(rawValue: 1 << 5) + static let hasMask = NodeFlags(rawValue: 1 << 6) +} + +private struct LottieContentFlags: OptionSet { + var rawValue: UInt8 + + init(rawValue: UInt8) { + self.rawValue = rawValue + } + + static let hasStroke = LottieContentFlags(rawValue: 1 << 0) + static let hasFill = LottieContentFlags(rawValue: 1 << 1) +} + +func serializePath(buffer: WriteBuffer, path: LottiePath) { + let lengthOffset = buffer.length + buffer.write(uInt32: 0) + + path.enumerateItems { pathItem in + switch pathItem.pointee.type { + case .moveTo: + let point = pathItem.pointee.points.0 + buffer.write(uInt8: 0) + buffer.write(point: point) + case .lineTo: + let point = pathItem.pointee.points.0 + buffer.write(uInt8: 1) + buffer.write(point: point) + case .curveTo: + let cp1 = pathItem.pointee.points.0 + let cp2 = pathItem.pointee.points.1 + let point = pathItem.pointee.points.2 + + buffer.write(uInt8: 2) + buffer.write(point: cp1) + buffer.write(point: cp2) + buffer.write(point: point) + case .close: + buffer.write(uInt8: 3) + @unknown default: + break + } + } + + let dataLength = buffer.length - lengthOffset - 4 + + let previousLength = buffer.length + buffer.length = lengthOffset + buffer.write(uInt32: UInt32(dataLength)) + buffer.length = previousLength +} + +func deserializePath(buffer: ReadBuffer) -> LottiePath { + let itemDataLength = Int(buffer.readUInt32()) + var itemData = Data(count: itemDataLength) + itemData.withUnsafeMutableBytes { bytes in + buffer.read(bytes: bytes) + } + + return LottiePath(customData: itemData) +} + +func serializeContentShading(buffer: WriteBuffer, shading: LottieRenderContentShading) { + if let shading = shading as? LottieRenderContentSolidShading { + buffer.write(uInt8: 0) + buffer.write(uInt32: shading.color.argb) + buffer.write(uInt8: UInt8(clamping: Int(shading.opacity * 255.0))) + } else if let shading = shading as? LottieRenderContentGradientShading { + buffer.write(uInt8: 1) + buffer.write(uInt8: UInt8(clamping: Int(shading.opacity * 255.0))) + buffer.write(uInt8: UInt8(shading.gradientType.rawValue)) + let colorStopCount = min(shading.colorStops.count, 255) + buffer.write(uInt8: UInt8(colorStopCount)) + for i in 0 ..< colorStopCount { + buffer.write(uInt32: shading.colorStops[i].color.argb) + buffer.write(float: Float(shading.colorStops[i].location)) + } + buffer.write(point: shading.start) + buffer.write(point: shading.end) + } else { + buffer.write(uInt8: 0) + buffer.write(uInt8: UInt8(clamping: Int(1.0 * 255.0))) + } +} + +func deserializeContentShading(buffer: ReadBuffer) -> LottieRenderContentShading { + switch buffer.readUInt8() { + case 0: + return LottieRenderContentSolidShading( + color: LottieColor(argb: buffer.readUInt32()), + opacity: CGFloat(buffer.readUInt8()) / 255.0 + ) + case 1: + let opacity = CGFloat(buffer.readUInt8()) / 255.0 + let gradientType = LottieGradientType(rawValue: UInt(buffer.readUInt8()))! + + var colorStops: [LottieColorStop] = [] + let colorStopCount = Int(buffer.readUInt8()) + for _ in 0 ..< colorStopCount { + colorStops.append(LottieColorStop( + color: LottieColor(argb: buffer.readUInt32()), + location: CGFloat(buffer.readFloat()) + )) + } + + let start = buffer.readPoint() + let end = buffer.readPoint() + + return LottieRenderContentGradientShading( + opacity: opacity, + gradientType: gradientType, + colorStops: colorStops, + start: start, + end: end + ) + default: + preconditionFailure() + } +} + +func serializeStroke(buffer: WriteBuffer, stroke: LottieRenderContentStroke) { + serializeContentShading(buffer: buffer, shading: stroke.shading) + buffer.write(float: Float(stroke.lineWidth)) + buffer.write(uInt8: UInt8(stroke.lineJoin.rawValue)) + buffer.write(uInt8: UInt8(stroke.lineCap.rawValue)) + buffer.write(float: Float(stroke.miterLimit)) +} + +func deserializeStroke(buffer: ReadBuffer) -> LottieRenderContentStroke { + return LottieRenderContentStroke( + shading: deserializeContentShading(buffer: buffer), + lineWidth: CGFloat(buffer.readFloat()), + lineJoin: CGLineJoin(rawValue: Int32(buffer.readUInt8()))!, + lineCap: CGLineCap(rawValue: Int32(buffer.readUInt8()))!, + miterLimit: CGFloat(buffer.readFloat()), + dashPhase: 0.0, + dashPattern: nil + ) +} + +func serializeFill(buffer: WriteBuffer, fill: LottieRenderContentFill) { + serializeContentShading(buffer: buffer, shading: fill.shading) + buffer.write(uInt8: UInt8(fill.fillRule.rawValue)) +} + +func deserializeFill(buffer: ReadBuffer) -> LottieRenderContentFill { + return LottieRenderContentFill( + shading: deserializeContentShading(buffer: buffer), + fillRule: LottieFillRule(rawValue: UInt(buffer.readUInt8()))! + ) +} + +func serializeRenderContent(buffer: WriteBuffer, renderContent: LottieRenderContent) { + var flags: LottieContentFlags = [] + if renderContent.stroke != nil { + flags.insert(.hasStroke) + } + if renderContent.fill != nil { + flags.insert(.hasFill) + } + buffer.write(uInt8: flags.rawValue) + + serializePath(buffer: buffer, path: renderContent.path) + if let stroke = renderContent.stroke { + serializeStroke(buffer: buffer, stroke: stroke) + } + if let fill = renderContent.fill { + serializeFill(buffer: buffer, fill: fill) + } +} + +func deserializeRenderContent(buffer: ReadBuffer) -> LottieRenderContent { + let flags = LottieContentFlags(rawValue: buffer.readUInt8()) + + let path = deserializePath(buffer: buffer) + + var stroke: LottieRenderContentStroke? + if flags.contains(.hasStroke) { + stroke = deserializeStroke(buffer: buffer) + } + + var fill: LottieRenderContentFill? + if flags.contains(.hasFill) { + fill = deserializeFill(buffer: buffer) + } + + return LottieRenderContent( + path: path, + stroke: stroke, + fill: fill + ) +} + +func serializeNode(buffer: WriteBuffer, node: LottieRenderNode) { + var flags: NodeFlags = [] + if node.masksToBounds { + flags.insert(.masksToBounds) + } + if node.isHidden { + flags.insert(.isHidden) + } + if node.hasSimpleContents { + flags.insert(.hasSimpleContents) + } + if node.isInvertedMatte { + flags.insert(.isInvertedMatte) + } + if node.renderContent != nil { + flags.insert(.hasRenderContent) + } + if !node.subnodes.isEmpty { + flags.insert(.hasSubnodes) + } + if node.mask != nil { + flags.insert(.hasMask) + } + + buffer.write(uInt8: flags.rawValue) + + buffer.write(point: node.position) + buffer.write(rect: node.bounds) + buffer.write(transform: node.transform) + buffer.write(uInt8: UInt8(clamping: Int(node.opacity * 255.0))) + buffer.write(rect: node.globalRect) + buffer.write(transform: node.globalTransform) + + if let renderContent = node.renderContent { + serializeRenderContent(buffer: buffer, renderContent: renderContent) + } + if !node.subnodes.isEmpty { + let count = min(node.subnodes.count, 4095) + buffer.write(uInt16: UInt16(count)) + for i in 0 ..< count { + serializeNode(buffer: buffer, node: node.subnodes[i]) + } + } + if let mask = node.mask { + serializeNode(buffer: buffer, node: mask) + } +} + +func deserializeNode(buffer: ReadBuffer) -> LottieRenderNode { + let flags = NodeFlags(rawValue: buffer.readUInt8()) + + let position = buffer.readPoint() + let bounds = buffer.readRect() + let transform = buffer.readTransform() + let opacity = CGFloat(buffer.readUInt8()) / 255.0 + let globalRect = buffer.readRect() + let globalTransform = buffer.readTransform() + + var renderContent: LottieRenderContent? + if flags.contains(.hasRenderContent) { + renderContent = deserializeRenderContent(buffer: buffer) + } + var subnodes: [LottieRenderNode] = [] + if flags.contains(.hasSubnodes) { + let count = Int(buffer.readUInt16()) + for _ in 0 ..< count { + subnodes.append(deserializeNode(buffer: buffer)) + } + } + var mask: LottieRenderNode? + if flags.contains(.hasMask) { + mask = deserializeNode(buffer: buffer) + } + + return LottieRenderNode( + position: position, + bounds: bounds, + transform: transform, + opacity: opacity, + masksToBounds: flags.contains(.masksToBounds), + isHidden: flags.contains(.isHidden), + globalRect: globalRect, + globalTransform: globalTransform, + renderContent: renderContent, + hasSimpleContents: flags.contains(.hasSimpleContents), + isInvertedMatte: flags.contains(.isInvertedMatte), + subnodes: subnodes, + mask: mask + ) +} + +public struct SerializedLottieMetalFrameMapping { + var size: CGSize = CGSize() + var frameCount: Int = 0 + var framesPerSecond: Int = 0 + var frameRanges: [Int: Range] = [:] +} + +func serializeFrameMapping(buffer: WriteBuffer, frameMapping: SerializedLottieMetalFrameMapping) { + buffer.write(size: frameMapping.size) + buffer.write(uInt32: UInt32(frameMapping.frameCount)) + buffer.write(uInt32: UInt32(frameMapping.framesPerSecond)) + for (frame, range) in frameMapping.frameRanges.sorted(by: { $0.key < $1.key }) { + buffer.write(uInt32: UInt32(frame)) + buffer.write(uInt32: UInt32(range.lowerBound)) + buffer.write(uInt32: UInt32(range.upperBound)) + } +} + +func deserializeFrameMapping(buffer: ReadBuffer) -> SerializedLottieMetalFrameMapping { + var frameMapping = SerializedLottieMetalFrameMapping() + + frameMapping.size = buffer.readSize() + frameMapping.frameCount = Int(buffer.readUInt32()) + frameMapping.framesPerSecond = Int(buffer.readUInt32()) + for _ in 0 ..< frameMapping.frameCount { + let frame = Int(buffer.readUInt32()) + let lowerBound = Int(buffer.readUInt32()) + let upperBound = Int(buffer.readUInt32()) + frameMapping.frameRanges[frame] = lowerBound ..< upperBound + } + + return frameMapping +} diff --git a/submodules/TelegramUI/Components/MediaEditor/BUILD b/submodules/TelegramUI/Components/MediaEditor/BUILD index 122c5558600..fe8088f1820 100644 --- a/submodules/TelegramUI/Components/MediaEditor/BUILD +++ b/submodules/TelegramUI/Components/MediaEditor/BUILD @@ -47,7 +47,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":MediaEditorBundle", @@ -72,6 +72,8 @@ swift_library( "//submodules/ImageTransparency", "//submodules/FFMpegBinding", "//submodules/TelegramUI/Components/AnimationCache/ImageDCT", + "//submodules/FileMediaResourceStatus", + "//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/BUILD b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/BUILD new file mode 100644 index 00000000000..0fc27dc460f --- /dev/null +++ b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ImageObjectSeparation", + module_name = "ImageObjectSeparation", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/ImageTransparency", + "//submodules/TelegramUI/Components/AnimationCache/ImageDCT", + "//submodules/FileMediaResourceStatus", + "//third-party/ZipArchive:ZipArchive", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift new file mode 100644 index 00000000000..1cf01906e03 --- /dev/null +++ b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift @@ -0,0 +1,436 @@ +import Foundation +import UIKit +import Display +import Vision +import CoreImage +import CoreImage.CIFilterBuiltins +import VideoToolbox +import SwiftSignalKit +import Postbox +import TelegramCore +import AccountContext +import FileMediaResourceStatus +import ZipArchive +import ImageTransparency + +private let queue = Queue() + +public enum CutoutAvailability { + case available + case progress(Float) + case unavailable +} + +private var forceCoreMLVariant: Bool { +#if targetEnvironment(simulator) + return true +#else + return false +#endif +} + +private func modelPath() -> String { + return NSTemporaryDirectory() + "u2netp.mlmodelc" +} + +public func cutoutAvailability(context: AccountContext) -> Signal { + if #available(iOS 17.0, *), !forceCoreMLVariant { + return .single(.available) + } else if #available(iOS 14.0, *) { + let compiledModelPath = modelPath() + + if FileManager.default.fileExists(atPath: compiledModelPath) { + return .single(.available) + } + return context.engine.peers.resolvePeerByName(name: "stickersbackgroundseparation") + |> mapToSignal { result -> Signal in + guard case let .result(maybePeer) = result else { + return .complete() + } + guard let peer = maybePeer else { + return .single(.unavailable) + } + + return context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peer.id, threadId: nil), index: .lowerBound, anchorIndex: .lowerBound, count: 5, fixedCombinedReadStates: nil) + |> mapToSignal { view -> Signal<(TelegramMediaFile, EngineMessage)?, NoError> in + if !view.0.isLoading { + if let message = view.0.entries.last?.message, let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile { + return .single((file, EngineMessage(message))) + } else { + return .single(nil) + } + } else { + return .complete() + } + } + |> take(1) + |> mapToSignal { maybeFileAndMessage -> Signal in + if let (file, message) = maybeFileAndMessage { + let fetchedData = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .file, reference: FileMediaReference.message(message: MessageReference(message._asMessage()), media: file).resourceReference(file.resource)) + + enum FetchStatus { + case completed(String) + case progress(Float) + case failed + } + + let fetchStatus = Signal { subscriber in + let fetchedDisposable = fetchedData.start() + let resourceDataDisposable = context.account.postbox.mediaBox.resourceData(file.resource, attemptSynchronously: false).start(next: { next in + if next.complete { + SSZipArchive.unzipFile(atPath: next.path, toDestination: NSTemporaryDirectory()) + subscriber.putNext(.completed(compiledModelPath)) + subscriber.putCompletion() + } + }, error: subscriber.putError, completed: subscriber.putCompletion) + let progressDisposable = messageFileMediaResourceStatus(context: context, file: file, message: message, isRecentActions: false).start(next: { status in + switch status.fetchStatus { + case let .Remote(progress), let .Fetching(_, progress), let .Paused(progress): + subscriber.putNext(.progress(progress)) + default: + break + } + }) + return ActionDisposable { + fetchedDisposable.dispose() + resourceDataDisposable.dispose() + progressDisposable.dispose() + } + } + return fetchStatus + |> mapToSignal { status -> Signal in + switch status { + case .completed: + return .single(.available) + case let .progress(progress): + return .single(.progress(progress)) + case .failed: + return .single(.unavailable) + } + } + } else { + return .single(.unavailable) + } + } + } + } else { + return .single(.unavailable) + } +} + +public func cutoutStickerImage(from image: UIImage, context: AccountContext? = nil, onlyCheck: Bool = false) -> Signal { + guard let cgImage = image.cgImage else { + return .single(nil) + } + if #available(iOS 17.0, *), !forceCoreMLVariant { + return Signal { subscriber in + let ciContext = CIContext(options: nil) + let inputImage = CIImage(cgImage: cgImage) + let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) + let request = VNGenerateForegroundInstanceMaskRequest { [weak handler] request, error in + guard let handler, let result = request.results?.first as? VNInstanceMaskObservation else { + subscriber.putNext(nil) + subscriber.putCompletion() + return + } + if onlyCheck { + subscriber.putNext(UIImage()) + subscriber.putCompletion() + } else { + let instances = instances(atPoint: nil, inObservation: result) + if let mask = try? result.generateScaledMaskForImage(forInstances: instances, from: handler) { + let filter = CIFilter.blendWithMask() + filter.inputImage = inputImage + filter.backgroundImage = CIImage(color: .clear) + filter.maskImage = CIImage(cvPixelBuffer: mask) + if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) { + let image = UIImage(cgImage: cgImage) + subscriber.putNext(image) + subscriber.putCompletion() + return + } + } + subscriber.putNext(nil) + subscriber.putCompletion() + } + } + try? handler.perform([request]) + return ActionDisposable { + request.cancel() + } + } + |> runOn(queue) + } else if #available(iOS 14.0, *), onlyCheck { + return Signal { subscriber in + U2netp.load(contentsOf: URL(fileURLWithPath: modelPath()), completionHandler: { result in + switch result { + case let .success(model): + let modelImageSize = CGSize(width: 320, height: 320) + if let squareImage = scaleImageToPixelSize(image: image, size: modelImageSize), + let pixelBuffer = buffer(from: squareImage), + let result = try? model.prediction(in_0: pixelBuffer), + let resultImage = UIImage(pixelBuffer: result.out_p1), + imageHasSubject(resultImage) { + subscriber.putNext(UIImage()) + } else { + subscriber.putNext(nil) + } + subscriber.putCompletion() + case .failure: + subscriber.putNext(nil) + subscriber.putCompletion() + } + }) + return EmptyDisposable + } + |> runOn(queue) + } else { + return .single(nil) + } +} + +public struct CutoutResult { + public enum Image { + case image(UIImage, CIImage) + case pixelBuffer(CVPixelBuffer) + } + + public let index: Int + public let extractedImage: Image? + public let edgesMaskImage: Image? + public let maskImage: Image? + public let backgroundImage: Image? +} + +public enum CutoutTarget { + case point(CGPoint?) + case index(Int) + case all +} + + +func refineEdges(_ maskImage: CIImage) -> CIImage? { + let maskImage = maskImage.clampedToExtent() + + let blurFilter = CIFilter(name: "CIGaussianBlur")! + blurFilter.setValue(maskImage, forKey: kCIInputImageKey) + blurFilter.setValue(11.4, forKey: kCIInputRadiusKey) + + let controlsFilter = CIFilter(name: "CIColorControls")! + controlsFilter.setValue(blurFilter.outputImage, forKey: kCIInputImageKey) + controlsFilter.setValue(6.61, forKey: kCIInputContrastKey) + + let sharpenFilter = CIFilter(name: "CISharpenLuminance")! + sharpenFilter.setValue(controlsFilter.outputImage, forKey: kCIInputImageKey) + sharpenFilter.setValue(250.0, forKey: kCIInputSharpnessKey) + + return sharpenFilter.outputImage?.cropped(to: maskImage.extent) +} + +public func cutoutImage( + from image: UIImage, + editedImage: UIImage? = nil, + crop: (offset: CGPoint, rotation: CGFloat, scale: CGFloat)?, + target: CutoutTarget, + includeExtracted: Bool = true, + completion: @escaping ([CutoutResult]) -> Void +) { + guard #available(iOS 14.0, *), let cgImage = image.cgImage else { + completion([]) + return + } + + let ciContext = CIContext(options: nil) + let inputImage = CIImage(cgImage: cgImage) + var results: [CutoutResult] = [] + + func process(instance: Int, mask originalMaskImage: CIImage) { + let extractedImage: CutoutResult.Image? + if includeExtracted { + let filter = CIFilter.blendWithMask() + filter.backgroundImage = CIImage(color: .clear) + + let dimensions: CGSize + var maskImage = originalMaskImage + if let editedImage = editedImage?.cgImage.flatMap({ CIImage(cgImage: $0) }) { + filter.inputImage = editedImage + dimensions = editedImage.extent.size + + if let (cropOffset, cropRotation, cropScale) = crop { + let initialScale: CGFloat + if maskImage.extent.height > maskImage.extent.width { + initialScale = dimensions.width / maskImage.extent.width + } else { + initialScale = dimensions.width / maskImage.extent.height + } + + let dimensions = editedImage.extent.size + maskImage = maskImage.transformed(by: CGAffineTransform(translationX: -maskImage.extent.width / 2.0, y: -maskImage.extent.height / 2.0)) + + var transform = CGAffineTransform.identity + transform = transform.translatedBy(x: dimensions.width / 2.0 + cropOffset.x, y: dimensions.height / 2.0 + cropOffset.y * -1.0) + transform = transform.rotated(by: -cropRotation) + transform = transform.scaledBy(x: cropScale * initialScale, y: cropScale * initialScale) + maskImage = maskImage.transformed(by: transform) + } + } else { + filter.inputImage = inputImage + dimensions = inputImage.extent.size + } + filter.maskImage = maskImage + + if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: CGRect(origin: .zero, size: dimensions)) { + extractedImage = .image(UIImage(cgImage: cgImage), output) + } else { + extractedImage = nil + } + } else { + extractedImage = nil + } + + let whiteImage = CIImage(color: .white) + let blackImage = CIImage(color: .black) + + let maskFilter = CIFilter.blendWithMask() + maskFilter.inputImage = whiteImage + maskFilter.backgroundImage = blackImage + maskFilter.maskImage = originalMaskImage + + let refinedMaskFilter = CIFilter.blendWithMask() + refinedMaskFilter.inputImage = whiteImage + refinedMaskFilter.backgroundImage = blackImage + refinedMaskFilter.maskImage = refineEdges(originalMaskImage) + + let edgesMaskImage: CutoutResult.Image? + let maskImage: CutoutResult.Image? + if let maskOutput = maskFilter.outputImage?.cropped(to: inputImage.extent), let maskCgImage = ciContext.createCGImage(maskOutput, from: inputImage.extent), let refinedMaskOutput = refinedMaskFilter.outputImage?.cropped(to: inputImage.extent), let refinedMaskCgImage = ciContext.createCGImage(refinedMaskOutput, from: inputImage.extent) { + edgesMaskImage = .image(UIImage(cgImage: maskCgImage), maskOutput) + maskImage = .image(UIImage(cgImage: refinedMaskCgImage), refinedMaskOutput) + } else { + edgesMaskImage = nil + maskImage = nil + } + + if extractedImage != nil || maskImage != nil { + results.append(CutoutResult(index: instance, extractedImage: extractedImage, edgesMaskImage: edgesMaskImage, maskImage: maskImage, backgroundImage: nil)) + } + } + + if #available(iOS 17.0, *), !forceCoreMLVariant { + queue.async { + let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) + let request = VNGenerateForegroundInstanceMaskRequest { [weak handler] request, error in + guard let handler, let result = request.results?.first as? VNInstanceMaskObservation else { + completion([]) + return + } + + let targetInstances: IndexSet + switch target { + case let .point(point): + targetInstances = instances(atPoint: point, inObservation: result) + case let .index(index): + targetInstances = IndexSet([index]) + case .all: + targetInstances = result.allInstances + } + + for instance in targetInstances { + if let mask = try? result.generateScaledMaskForImage(forInstances: IndexSet(integer: instance), from: handler) { + process(instance: instance, mask: CIImage(cvPixelBuffer: mask)) + } + } + completion(results) + } + + try? handler.perform([request]) + } + } else { + U2netp.load(contentsOf: URL(fileURLWithPath: modelPath()), completionHandler: { result in + switch result { + case let .success(model): + let modelImageSize = CGSize(width: 320, height: 320) + if let squareImage = scaleImageToPixelSize(image: image, size: modelImageSize), let pixelBuffer = buffer(from: squareImage), let result = try? model.prediction(in_0: pixelBuffer), let maskImage = UIImage(pixelBuffer: result.out_p1), let scaledMaskImage = scaleImageToPixelSize(image: maskImage, size: image.size), let ciImage = CIImage(image: scaledMaskImage) { + process(instance: 0, mask: ciImage) + } + case .failure: + break + } + completion(results) + }) + } +} + +@available(iOS 17.0, *) +private func instances(atPoint maybePoint: CGPoint?, inObservation observation: VNInstanceMaskObservation) -> IndexSet { + guard let point = maybePoint else { + return observation.allInstances + } + + let instanceMap = observation.instanceMask + let coords = VNImagePointForNormalizedPoint(point, CVPixelBufferGetWidth(instanceMap) - 1, CVPixelBufferGetHeight(instanceMap) - 1) + + CVPixelBufferLockBaseAddress(instanceMap, .readOnly) + guard let pixels = CVPixelBufferGetBaseAddress(instanceMap) else { + fatalError() + } + let bytesPerRow = CVPixelBufferGetBytesPerRow(instanceMap) + let instanceLabel = pixels.load(fromByteOffset: Int(coords.y) * bytesPerRow + Int(coords.x), as: UInt8.self) + CVPixelBufferUnlockBaseAddress(instanceMap, .readOnly) + + return instanceLabel == 0 ? observation.allInstances : [Int(instanceLabel)] +} + +private extension UIImage { + convenience init?(pixelBuffer: CVPixelBuffer) { + var cgImage: CGImage? + VTCreateCGImageFromCVPixelBuffer(pixelBuffer, options: nil, imageOut: &cgImage) + + guard let cgImage = cgImage else { + return nil + } + + self.init(cgImage: cgImage) + } +} + +private func scaleImageToPixelSize(image: UIImage, size: CGSize) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(size, true, 1.0) + image.draw(in: CGRect(origin: CGPoint(), size: size), blendMode: .copy, alpha: 1.0) + let result = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return result +} + +private func buffer(from image: UIImage) -> CVPixelBuffer? { + let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary + var pixelBuffer : CVPixelBuffer? + let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(image.size.width), Int(image.size.height), kCVPixelFormatType_32ARGB, attrs, &pixelBuffer) + guard (status == kCVReturnSuccess) else { + return nil + } + + guard let pixelBufferUnwrapped = pixelBuffer else { + return nil + } + + CVPixelBufferLockBaseAddress(pixelBufferUnwrapped, CVPixelBufferLockFlags(rawValue: 0)) + let pixelData = CVPixelBufferGetBaseAddress(pixelBufferUnwrapped) + + let rgbColorSpace = CGColorSpaceCreateDeviceRGB() + + guard let context = CGContext(data: pixelData, width: Int(image.size.width), height: Int(image.size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBufferUnwrapped), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) else { + return nil + } + + context.translateBy(x: 0, y: image.size.height) + context.scaleBy(x: 1.0, y: -1.0) + + UIGraphicsPushContext(context) + image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) + UIGraphicsPopContext() + CVPixelBufferUnlockBaseAddress(pixelBufferUnwrapped, CVPixelBufferLockFlags(rawValue: 0)) + + return pixelBufferUnwrapped +} diff --git a/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/U2netp.swift b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/U2netp.swift new file mode 100644 index 00000000000..42bcc0b13a8 --- /dev/null +++ b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/U2netp.swift @@ -0,0 +1,252 @@ +import CoreML + +/// Model Prediction Input Type +@available(macOS 13.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) +class U2netpInput : MLFeatureProvider { + + /// in_0 as color (kCVPixelFormatType_32BGRA) image buffer, 320 pixels wide by 320 pixels high + var in_0: CVPixelBuffer + + var featureNames: Set { + get { + return ["in_0"] + } + } + + func featureValue(for featureName: String) -> MLFeatureValue? { + if (featureName == "in_0") { + return MLFeatureValue(pixelBuffer: in_0) + } + return nil + } + + init(in_0: CVPixelBuffer) { + self.in_0 = in_0 + } + + convenience init(in_0With in_0: CGImage) throws { + self.init(in_0: try MLFeatureValue(cgImage: in_0, pixelsWide: 320, pixelsHigh: 320, pixelFormatType: kCVPixelFormatType_32ARGB, options: nil).imageBufferValue!) + } + + convenience init(in_0At in_0: URL) throws { + self.init(in_0: try MLFeatureValue(imageAt: in_0, pixelsWide: 320, pixelsHigh: 320, pixelFormatType: kCVPixelFormatType_32ARGB, options: nil).imageBufferValue!) + } + + func setIn_0(with in_0: CGImage) throws { + self.in_0 = try MLFeatureValue(cgImage: in_0, pixelsWide: 320, pixelsHigh: 320, pixelFormatType: kCVPixelFormatType_32ARGB, options: nil).imageBufferValue! + } + + func setIn_0(with in_0: URL) throws { + self.in_0 = try MLFeatureValue(imageAt: in_0, pixelsWide: 320, pixelsHigh: 320, pixelFormatType: kCVPixelFormatType_32ARGB, options: nil).imageBufferValue! + } + +} + + +/// Model Prediction Output Type +@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) +class U2netpOutput : MLFeatureProvider { + + /// Source provided by CoreML + private let provider : MLFeatureProvider + + /// out_p0 as grayscale (kCVPixelFormatType_OneComponent8) image buffer, 320 pixels wide by 320 pixels high + lazy var out_p0: CVPixelBuffer = { + [unowned self] in return self.provider.featureValue(for: "out_p0")!.imageBufferValue + }()! + + /// out_p1 as grayscale (kCVPixelFormatType_OneComponent8) image buffer, 320 pixels wide by 320 pixels high + lazy var out_p1: CVPixelBuffer = { + [unowned self] in return self.provider.featureValue(for: "out_p1")!.imageBufferValue + }()! + + /// out_p2 as grayscale (kCVPixelFormatType_OneComponent8) image buffer, 320 pixels wide by 320 pixels high + lazy var out_p2: CVPixelBuffer = { + [unowned self] in return self.provider.featureValue(for: "out_p2")!.imageBufferValue + }()! + + /// out_p3 as grayscale (kCVPixelFormatType_OneComponent8) image buffer, 320 pixels wide by 320 pixels high + lazy var out_p3: CVPixelBuffer = { + [unowned self] in return self.provider.featureValue(for: "out_p3")!.imageBufferValue + }()! + + /// out_p4 as grayscale (kCVPixelFormatType_OneComponent8) image buffer, 320 pixels wide by 320 pixels high + lazy var out_p4: CVPixelBuffer = { + [unowned self] in return self.provider.featureValue(for: "out_p4")!.imageBufferValue + }()! + + /// out_p5 as grayscale (kCVPixelFormatType_OneComponent8) image buffer, 320 pixels wide by 320 pixels high + lazy var out_p5: CVPixelBuffer = { + [unowned self] in return self.provider.featureValue(for: "out_p5")!.imageBufferValue + }()! + + /// out_p6 as grayscale (kCVPixelFormatType_OneComponent8) image buffer, 320 pixels wide by 320 pixels high + lazy var out_p6: CVPixelBuffer = { + [unowned self] in return self.provider.featureValue(for: "out_p6")!.imageBufferValue + }()! + + var featureNames: Set { + return self.provider.featureNames + } + + func featureValue(for featureName: String) -> MLFeatureValue? { + return self.provider.featureValue(for: featureName) + } + + init(out_p0: CVPixelBuffer, out_p1: CVPixelBuffer, out_p2: CVPixelBuffer, out_p3: CVPixelBuffer, out_p4: CVPixelBuffer, out_p5: CVPixelBuffer, out_p6: CVPixelBuffer) { + self.provider = try! MLDictionaryFeatureProvider(dictionary: ["out_p0" : MLFeatureValue(pixelBuffer: out_p0), "out_p1" : MLFeatureValue(pixelBuffer: out_p1), "out_p2" : MLFeatureValue(pixelBuffer: out_p2), "out_p3" : MLFeatureValue(pixelBuffer: out_p3), "out_p4" : MLFeatureValue(pixelBuffer: out_p4), "out_p5" : MLFeatureValue(pixelBuffer: out_p5), "out_p6" : MLFeatureValue(pixelBuffer: out_p6)]) + } + + init(features: MLFeatureProvider) { + self.provider = features + } +} + + +/// Class for model loading and prediction +@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) +class U2netp { + let model: MLModel + + /** + Construct U2netp instance with an existing MLModel object. + + Usually the application does not use this initializer unless it makes a subclass of U2netp. + Such application may want to use `MLModel(contentsOfURL:configuration:)` and `U2netp.urlOfModelInThisBundle` to create a MLModel object to pass-in. + + - parameters: + - model: MLModel object + */ + init(model: MLModel) { + self.model = model + } + + /** + Construct U2netp instance with explicit path to mlmodelc file + - parameters: + - modelURL: the file url of the model + + - throws: an NSError object that describes the problem + */ + convenience init(contentsOf modelURL: URL) throws { + try self.init(model: MLModel(contentsOf: modelURL)) + } + + /** + Construct a model with URL of the .mlmodelc directory and configuration + + - parameters: + - modelURL: the file url of the model + - configuration: the desired model configuration + + - throws: an NSError object that describes the problem + */ + convenience init(contentsOf modelURL: URL, configuration: MLModelConfiguration) throws { + try self.init(model: MLModel(contentsOf: modelURL, configuration: configuration)) + } + + /** + Construct U2netp instance asynchronously with URL of the .mlmodelc directory with optional configuration. + + Model loading may take time when the model content is not immediately available (e.g. encrypted model). Use this factory method especially when the caller is on the main thread. + + - parameters: + - modelURL: the URL to the model + - configuration: the desired model configuration + - handler: the completion handler to be called when the model loading completes successfully or unsuccessfully + */ + class func load(contentsOf modelURL: URL, configuration: MLModelConfiguration = MLModelConfiguration(), completionHandler handler: @escaping (Swift.Result) -> Void) { + MLModel.load(contentsOf: modelURL, configuration: configuration) { result in + switch result { + case .failure(let error): + handler(.failure(error)) + case .success(let model): + handler(.success(U2netp(model: model))) + } + } + } + + /** + Construct U2netp instance asynchronously with URL of the .mlmodelc directory with optional configuration. + + Model loading may take time when the model content is not immediately available (e.g. encrypted model). Use this factory method especially when the caller is on the main thread. + + - parameters: + - modelURL: the URL to the model + - configuration: the desired model configuration + */ + @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) + class func load(contentsOf modelURL: URL, configuration: MLModelConfiguration = MLModelConfiguration()) async throws -> U2netp { + let model = try await MLModel.load(contentsOf: modelURL, configuration: configuration) + return U2netp(model: model) + } + + /** + Make a prediction using the structured interface + + - parameters: + - input: the input to the prediction as U2netpInput + + - throws: an NSError object that describes the problem + + - returns: the result of the prediction as U2netpOutput + */ + func prediction(input: U2netpInput) throws -> U2netpOutput { + return try self.prediction(input: input, options: MLPredictionOptions()) + } + + /** + Make a prediction using the structured interface + + - parameters: + - input: the input to the prediction as U2netpInput + - options: prediction options + + - throws: an NSError object that describes the problem + + - returns: the result of the prediction as U2netpOutput + */ + func prediction(input: U2netpInput, options: MLPredictionOptions) throws -> U2netpOutput { + let outFeatures = try model.prediction(from: input, options:options) + return U2netpOutput(features: outFeatures) + } + + /** + Make a prediction using the convenience interface + + - parameters: + - in_0 as color (kCVPixelFormatType_32BGRA) image buffer, 320 pixels wide by 320 pixels high + + - throws: an NSError object that describes the problem + + - returns: the result of the prediction as U2netpOutput + */ + func prediction(in_0: CVPixelBuffer) throws -> U2netpOutput { + let input_ = U2netpInput(in_0: in_0) + return try self.prediction(input: input_) + } + + /** + Make a batch prediction using the structured interface + + - parameters: + - inputs: the inputs to the prediction as [U2netpInput] + - options: prediction options + + - throws: an NSError object that describes the problem + + - returns: the result of the prediction as [U2netpOutput] + */ + func predictions(inputs: [U2netpInput], options: MLPredictionOptions = MLPredictionOptions()) throws -> [U2netpOutput] { + let batchIn = MLArrayBatchProvider(array: inputs) + let batchOut = try model.predictions(from: batchIn, options: options) + var results : [U2netpOutput] = [] + results.reserveCapacity(inputs.count) + for i in 0.. DrawingAnimatedImage? { + guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { + return nil + } + + let count = CGImageSourceGetCount(source) + var images = [UIImage]() + var duration = 0.0 + + for i in 0.. Double { + var delay = 0.0 + guard #available(iOS 13.0, *) else { + return delay + } + + let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil) + let gifPropertiesPointer = UnsafeMutablePointer.allocate(capacity: 0) + if CFDictionaryGetValueIfPresent(cfProperties, Unmanaged.passUnretained(kCGImagePropertyHEICSDictionary).toOpaque(), gifPropertiesPointer) == false { + return delay + } + + let gifProperties:CFDictionary = unsafeBitCast(gifPropertiesPointer.pointee, to: CFDictionary.self) + + var delayObject: AnyObject = unsafeBitCast(CFDictionaryGetValue(gifProperties, Unmanaged.passUnretained(kCGImagePropertyHEICSUnclampedDelayTime).toOpaque()), to: AnyObject.self) + if delayObject.doubleValue == 0 { + delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties, Unmanaged.passUnretained(kCGImagePropertyHEICSDelayTime).toOpaque()), to: AnyObject.self) + } + + delay = delayObject as? Double ?? 0 + + return delay + } +} + +public final class DrawingAnimatedImage { + public let images: [UIImage] + public let duration: Double + + init(images: [UIImage], duration: Double) { + self.images = images + self.duration = duration + } +} diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift index 9a37d1bb59c..085b757790a 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift @@ -81,7 +81,7 @@ public final class DrawingWallpaperRenderer { } public final class DrawingMessageRenderer { - class ContainerNode: ASDisplayNode { + final class ContainerNode: ASDisplayNode { private let context: AccountContext private let messages: [Message] private let isNight: Bool @@ -115,8 +115,9 @@ public final class DrawingMessageRenderer { let layout = ContainerViewLayout(size: CGSize(width: 360.0, height: 640.0), metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: .portrait), deviceMetrics: .iPhoneX, intrinsicInsets: .zero, safeInsets: .zero, additionalInsets: .zero, statusBarHeight: 0.0, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false) let size = self.updateMessagesLayout(layout: layout, presentationData: mockPresentationData) + let _ = self.updateMessagesLayout(layout: layout, presentationData: mockPresentationData) - Queue.mainQueue().after(0.05, { + Queue.mainQueue().after(0.2, { var mediaRect: CGRect? if let messageNode = self.messageNodes?.first { if self.isOverlay { @@ -157,6 +158,7 @@ public final class DrawingMessageRenderer { } } } + self.generate(size: size) { image in completion(size, image, mediaRect) } @@ -304,64 +306,74 @@ public final class DrawingMessageRenderer { private let nightContainerNode: ContainerNode private let overlayContainerNode: ContainerNode - public init(context: AccountContext, messages: [Message]) { + public init(context: AccountContext, messages: [Message], parentView: UIView) { self.context = context self.messages = messages self.dayContainerNode = ContainerNode(context: context, messages: messages) self.nightContainerNode = ContainerNode(context: context, messages: messages, isNight: true) self.overlayContainerNode = ContainerNode(context: context, messages: messages, isOverlay: true) + + parentView.addSubview(self.dayContainerNode.view) + parentView.addSubview(self.nightContainerNode.view) + parentView.addSubview(self.overlayContainerNode.view) } public func render(completion: @escaping (Result) -> Void) { - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - let defaultPresentationData = defaultPresentationData() + Queue.mainQueue().after(0.1) { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let defaultPresentationData = defaultPresentationData() + + let mockPresentationData = PresentationData( + strings: presentationData.strings, + theme: defaultPresentationTheme, + autoNightModeTriggered: false, + chatWallpaper: presentationData.chatWallpaper, + chatFontSize: defaultPresentationData.chatFontSize, + chatBubbleCorners: defaultPresentationData.chatBubbleCorners, + listsFontSize: defaultPresentationData.listsFontSize, + dateTimeFormat: presentationData.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, + nameSortOrder: presentationData.nameSortOrder, + reduceMotion: false, + largeEmoji: true + ) + + var finalSize: CGSize = .zero + var dayImage: UIImage? + var nightImage: UIImage? + var overlayImage: UIImage? + var mediaRect: CGRect? + + let completeIfReady = { + if let dayImage, let nightImage, let overlayImage { + var cornerRadius: CGFloat = defaultPresentationData.chatBubbleCorners.mainRadius + if let mediaRect, mediaRect.width == mediaRect.height, mediaRect.width == 240.0 { + cornerRadius = mediaRect.width / 2.0 + } else if let rect = mediaRect { + mediaRect = CGRect(x: rect.minX + 4.0, y: rect.minY, width: rect.width - 6.0, height: rect.height - 1.0) + } + completion(Result(size: finalSize, dayImage: dayImage, nightImage: nightImage, overlayImage: overlayImage, mediaFrame: mediaRect.flatMap { Result.MediaFrame(rect: $0, cornerRadius: cornerRadius) })) - let mockPresentationData = PresentationData( - strings: presentationData.strings, - theme: defaultPresentationTheme, - autoNightModeTriggered: false, - chatWallpaper: presentationData.chatWallpaper, - chatFontSize: defaultPresentationData.chatFontSize, - chatBubbleCorners: defaultPresentationData.chatBubbleCorners, - listsFontSize: defaultPresentationData.listsFontSize, - dateTimeFormat: presentationData.dateTimeFormat, - nameDisplayOrder: presentationData.nameDisplayOrder, - nameSortOrder: presentationData.nameSortOrder, - reduceMotion: false, - largeEmoji: true - ) - - var finalSize: CGSize = .zero - var dayImage: UIImage? - var nightImage: UIImage? - var overlayImage: UIImage? - var mediaRect: CGRect? - - let completeIfReady = { - if let dayImage, let nightImage, let overlayImage { - var cornerRadius: CGFloat = defaultPresentationData.chatBubbleCorners.mainRadius - if let mediaRect, mediaRect.width == mediaRect.height, mediaRect.width == 240.0 { - cornerRadius = mediaRect.width / 2.0 - } else if let rect = mediaRect { - mediaRect = CGRect(x: rect.minX + 4.0, y: rect.minY, width: rect.width - 6.0, height: rect.height - 1.0) + self.dayContainerNode.view.removeFromSuperview() + self.nightContainerNode.view.removeFromSuperview() + self.overlayContainerNode.view.removeFromSuperview() } - completion(Result(size: finalSize, dayImage: dayImage, nightImage: nightImage, overlayImage: overlayImage, mediaFrame: mediaRect.flatMap { Result.MediaFrame(rect: $0, cornerRadius: cornerRadius) })) } - } - self.dayContainerNode.render(presentationData: mockPresentationData) { size, image, rect in - finalSize = size - dayImage = image - mediaRect = rect - completeIfReady() - } - self.nightContainerNode.render(presentationData: mockPresentationData) { size, image, _ in - nightImage = image - completeIfReady() - } - self.overlayContainerNode.render(presentationData: mockPresentationData) { size, image, _ in - overlayImage = image - completeIfReady() + self.dayContainerNode.render(presentationData: mockPresentationData) { size, image, rect in + finalSize = size + dayImage = image + mediaRect = rect + completeIfReady() + } + self.nightContainerNode.render(presentationData: mockPresentationData) { size, image, _ in + nightImage = image + completeIfReady() + } + self.overlayContainerNode.render(presentationData: mockPresentationData) { size, image, _ in + overlayImage = image + completeIfReady() + } } } } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift deleted file mode 100644 index a69c64878fa..00000000000 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift +++ /dev/null @@ -1,245 +0,0 @@ -import Foundation -import UIKit -import Display -import Vision -import CoreImage -import CoreImage.CIFilterBuiltins -import SwiftSignalKit -import VideoToolbox - -private let queue = Queue() - -public func cutoutStickerImage(from image: UIImage, onlyCheck: Bool = false) -> Signal { - if #available(iOS 17.0, *) { - guard let cgImage = image.cgImage else { - return .single(nil) - } - return Signal { subscriber in - let ciContext = CIContext(options: nil) - let inputImage = CIImage(cgImage: cgImage) - let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) - let request = VNGenerateForegroundInstanceMaskRequest { [weak handler] request, error in - guard let handler, let result = request.results?.first as? VNInstanceMaskObservation else { - subscriber.putNext(nil) - subscriber.putCompletion() - return - } - if onlyCheck { - subscriber.putNext(UIImage()) - subscriber.putCompletion() - } else { - let instances = instances(atPoint: nil, inObservation: result) - if let mask = try? result.generateScaledMaskForImage(forInstances: instances, from: handler) { - let filter = CIFilter.blendWithMask() - filter.inputImage = inputImage - filter.backgroundImage = CIImage(color: .clear) - filter.maskImage = CIImage(cvPixelBuffer: mask) - if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) { - let image = UIImage(cgImage: cgImage) - subscriber.putNext(image) - subscriber.putCompletion() - return - } - } - subscriber.putNext(nil) - subscriber.putCompletion() - } - } - try? handler.perform([request]) - return ActionDisposable { - request.cancel() - } - } - |> runOn(queue) - } else { - return .single(nil) - } -} - -public struct CutoutResult { - public enum Image { - case image(UIImage, CIImage) - case pixelBuffer(CVPixelBuffer) - } - - public let index: Int - public let extractedImage: Image? - public let edgesMaskImage: Image? - public let maskImage: Image? - public let backgroundImage: Image? -} - -public enum CutoutTarget { - case point(CGPoint?) - case index(Int) - case all -} - - -func refineEdges(_ maskImage: CIImage) -> CIImage? { - let maskImage = maskImage.clampedToExtent() - - let blurFilter = CIFilter(name: "CIGaussianBlur")! - blurFilter.setValue(maskImage, forKey: kCIInputImageKey) - blurFilter.setValue(11.4, forKey: kCIInputRadiusKey) - - let controlsFilter = CIFilter(name: "CIColorControls")! - controlsFilter.setValue(blurFilter.outputImage, forKey: kCIInputImageKey) - controlsFilter.setValue(6.61, forKey: kCIInputContrastKey) - - let sharpenFilter = CIFilter(name: "CISharpenLuminance")! - sharpenFilter.setValue(controlsFilter.outputImage, forKey: kCIInputImageKey) - sharpenFilter.setValue(250.0, forKey: kCIInputSharpnessKey) - - return sharpenFilter.outputImage?.cropped(to: maskImage.extent) -} - -public func cutoutImage( - from image: UIImage, - editedImage: UIImage? = nil, - values: MediaEditorValues?, - target: CutoutTarget, - includeExtracted: Bool = true, - completion: @escaping ([CutoutResult]) -> Void -) { - if #available(iOS 17.0, *), let cgImage = image.cgImage { - let ciContext = CIContext(options: nil) - let inputImage = CIImage(cgImage: cgImage) - - queue.async { - let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) - let request = VNGenerateForegroundInstanceMaskRequest { [weak handler] request, error in - guard let handler, let result = request.results?.first as? VNInstanceMaskObservation else { - completion([]) - return - } - - let targetInstances: IndexSet - switch target { - case let .point(point): - targetInstances = instances(atPoint: point, inObservation: result) - case let .index(index): - targetInstances = IndexSet([index]) - case .all: - targetInstances = result.allInstances - } - - var results: [CutoutResult] = [] - for instance in targetInstances { - if let mask = try? result.generateScaledMaskForImage(forInstances: IndexSet(integer: instance), from: handler) { - let extractedImage: CutoutResult.Image? - if includeExtracted { - let filter = CIFilter.blendWithMask() - filter.backgroundImage = CIImage(color: .clear) - - let dimensions: CGSize - var maskImage = CIImage(cvPixelBuffer: mask) - if let editedImage = editedImage?.cgImage.flatMap({ CIImage(cgImage: $0) }) { - filter.inputImage = editedImage - dimensions = editedImage.extent.size - - if let values { - let initialScale: CGFloat - if maskImage.extent.height > maskImage.extent.width { - initialScale = dimensions.width / maskImage.extent.width - } else { - initialScale = dimensions.width / maskImage.extent.height - } - - let dimensions = editedImage.extent.size - maskImage = maskImage.transformed(by: CGAffineTransform(translationX: -maskImage.extent.width / 2.0, y: -maskImage.extent.height / 2.0)) - - var transform = CGAffineTransform.identity - let position = values.cropOffset - let rotation = values.cropRotation - let scale = values.cropScale - transform = transform.translatedBy(x: dimensions.width / 2.0 + position.x, y: dimensions.height / 2.0 + position.y * -1.0) - transform = transform.rotated(by: -rotation) - transform = transform.scaledBy(x: scale * initialScale, y: scale * initialScale) - maskImage = maskImage.transformed(by: transform) - } - } else { - filter.inputImage = inputImage - dimensions = inputImage.extent.size - } - filter.maskImage = maskImage - - if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: CGRect(origin: .zero, size: dimensions)) { - extractedImage = .image(UIImage(cgImage: cgImage), output) - } else { - extractedImage = nil - } - } else { - extractedImage = nil - } - - let whiteImage = CIImage(color: .white) - let blackImage = CIImage(color: .black) - - let maskFilter = CIFilter.blendWithMask() - maskFilter.inputImage = whiteImage - maskFilter.backgroundImage = blackImage - maskFilter.maskImage = CIImage(cvPixelBuffer: mask) - - let refinedMaskFilter = CIFilter.blendWithMask() - refinedMaskFilter.inputImage = whiteImage - refinedMaskFilter.backgroundImage = blackImage - refinedMaskFilter.maskImage = refineEdges(CIImage(cvPixelBuffer: mask)) - - let edgesMaskImage: CutoutResult.Image? - let maskImage: CutoutResult.Image? - if let maskOutput = maskFilter.outputImage?.cropped(to: inputImage.extent), let maskCgImage = ciContext.createCGImage(maskOutput, from: inputImage.extent), let refinedMaskOutput = refinedMaskFilter.outputImage?.cropped(to: inputImage.extent), let refinedMaskCgImage = ciContext.createCGImage(refinedMaskOutput, from: inputImage.extent) { - edgesMaskImage = .image(UIImage(cgImage: maskCgImage), maskOutput) - maskImage = .image(UIImage(cgImage: refinedMaskCgImage), refinedMaskOutput) - } else { - edgesMaskImage = nil - maskImage = nil - } - - if extractedImage != nil || maskImage != nil { - results.append(CutoutResult(index: instance, extractedImage: extractedImage, edgesMaskImage: edgesMaskImage, maskImage: maskImage, backgroundImage: nil)) - } - } - } - completion(results) - } - - try? handler.perform([request]) - } - } else { - completion([]) - } -} - -@available(iOS 17.0, *) -private func instances(atPoint maybePoint: CGPoint?, inObservation observation: VNInstanceMaskObservation) -> IndexSet { - guard let point = maybePoint else { - return observation.allInstances - } - - let instanceMap = observation.instanceMask - let coords = VNImagePointForNormalizedPoint(point, CVPixelBufferGetWidth(instanceMap) - 1, CVPixelBufferGetHeight(instanceMap) - 1) - - CVPixelBufferLockBaseAddress(instanceMap, .readOnly) - guard let pixels = CVPixelBufferGetBaseAddress(instanceMap) else { - fatalError() - } - let bytesPerRow = CVPixelBufferGetBytesPerRow(instanceMap) - let instanceLabel = pixels.load(fromByteOffset: Int(coords.y) * bytesPerRow + Int(coords.x), as: UInt8.self) - CVPixelBufferUnlockBaseAddress(instanceMap, .readOnly) - - return instanceLabel == 0 ? observation.allInstances : [Int(instanceLabel)] -} - -private extension UIImage { - convenience init?(pixelBuffer: CVPixelBuffer) { - var cgImage: CGImage? - VTCreateCGImageFromCVPixelBuffer(pixelBuffer, options: nil, imageOut: &cgImage) - - guard let cgImage = cgImage else { - return nil - } - - self.init(cgImage: cgImage) - } -} diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 37d5717a945..8cbf706e034 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -12,6 +12,7 @@ import TelegramPresentationData import FastBlur import AccountContext import ImageTransparency +import ImageObjectSeparation public struct MediaEditorPlayerState: Equatable { public struct Track: Equatable { @@ -190,8 +191,27 @@ public final class MediaEditor { } } - public private(set) var canCutout: Bool = false - public var canCutoutUpdated: (Bool, Bool) -> Void = { _, _ in } + public enum CutoutStatus: Equatable { + public enum Availability: Equatable { + case available + case preparing(progress: Float) + case unavailable + } + case unknown + case known(canCutout: Bool, availability: Availability, hasTransparency: Bool) + } + + private let cutoutDisposable = MetaDisposable() + private var cutoutStatusValue: CutoutStatus = .unknown { + didSet { + self.cutoutStatusPromise.set(self.cutoutStatusValue) + } + } + private let cutoutStatusPromise = ValuePromise(.unknown) + public var cutoutStatus: Signal { + return self.cutoutStatusPromise.get() + } + public var maskUpdated: (UIImage, Bool) -> Void = { _, _ in } public var classificationUpdated: ([(String, Float)]) -> Void = { _ in } @@ -482,6 +502,7 @@ public final class MediaEditor { } deinit { + self.cutoutDisposable.dispose() self.textureSourceDisposable?.dispose() self.invalidateTimeObservers() } @@ -726,19 +747,29 @@ public final class MediaEditor { if case .sticker = self.mode { if !imageHasTransparency(image) { - let _ = (cutoutStickerImage(from: image, onlyCheck: true) - |> deliverOnMainQueue).start(next: { [weak self] result in + self.cutoutDisposable.set((cutoutAvailability(context: self.context) + |> mapToSignal { availability -> Signal in + switch availability { + case .available: + return cutoutStickerImage(from: image, context: context, onlyCheck: true) + |> map { result in + return .known(canCutout: result != nil, availability: .available, hasTransparency: false) + } + case let .progress(progress): + return .single(.known(canCutout: false, availability: .preparing(progress: progress), hasTransparency: false)) + case .unavailable: + return .single(.known(canCutout: false, availability: .unavailable, hasTransparency: false)) + } + } + |> deliverOnMainQueue).start(next: { [weak self] status in guard let self else { return } - let canCutout = result != nil - self.canCutout = canCutout - self.canCutoutUpdated(canCutout, false) - }) + self.cutoutStatusValue = status + })) self.maskUpdated(image, false) } else { - self.canCutout = false - self.canCutoutUpdated(false, true) + self.cutoutStatusValue = .known(canCutout: false, availability: .unavailable, hasTransparency: true) if let maskImage = generateTintedImage(image: image, color: .white, backgroundColor: .black) { self.maskUpdated(maskImage, true) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift index c41ecc71a79..99dc63546ef 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift @@ -67,14 +67,19 @@ func composerEntitiesForDrawingEntity(postbox: Postbox, textScale: CGFloat, enti return [] } else { let content: MediaEditorComposerStickerEntity.Content + var scale = entity.scale switch entity.content { case let .file(file, _): content = .file(file.media) case let .image(image, _): content = .image(image) case let .animatedImage(data, _): - let _ = data - return [] + if let animatedImage = UIImage.animatedImageFromData(data: data) { + content = .animatedImage(animatedImage.images, animatedImage.duration) + scale *= 1.0 + } else { + return [] + } case let .video(file): content = .video(file) case .dualVideoReference: @@ -93,7 +98,7 @@ func composerEntitiesForDrawingEntity(postbox: Postbox, textScale: CGFloat, enti return [] } } - return [MediaEditorComposerStickerEntity(postbox: postbox, content: content, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: entity.mirrored, colorSpace: colorSpace, tintColor: tintColor, isStatic: entity.isExplicitlyStatic)] + return [MediaEditorComposerStickerEntity(postbox: postbox, content: content, position: entity.position, scale: scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: entity.mirrored, colorSpace: colorSpace, tintColor: tintColor, isStatic: entity.isExplicitlyStatic)] } } else if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) { if let entity = entity as? DrawingBubbleEntity { @@ -296,8 +301,9 @@ final class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { self.imagePromise.set(.single(image)) case let .animatedImage(images, duration): self.isAnimated = true - let _ = images - let _ = duration + self.videoFrameRate = Float(images.count) / Float(duration) + self.totalDuration = duration + self.durationPromise.set(.single(duration)) case .video: self.isAnimated = true } @@ -338,7 +344,37 @@ final class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { func image(for time: CMTime, frameRate: Float, context: CIContext, completion: @escaping (CIImage?) -> Void) { let currentTime = CMTimeGetSeconds(time) - if case .video = self.content { + if case let .animatedImage(images, _) = self.content { + var frameAdvancement: Int = 0 + if let frameRate = self.videoFrameRate, frameRate > 0 { + let frameTime = 1.0 / Double(frameRate) + let frameIndex = Int(floor(currentTime / frameTime)) + + let currentFrameIndex = self.currentFrameIndex + if currentFrameIndex != frameIndex { + let previousFrameIndex = currentFrameIndex + self.currentFrameIndex = frameIndex + + var delta = 1 + if let previousFrameIndex = previousFrameIndex { + delta = max(1, frameIndex - previousFrameIndex) + } + frameAdvancement = delta + } + } + if frameAdvancement == 0, let image = self.image { + completion(image) + return + } else if let currentFrameIndex = self.currentFrameIndex { + let index = currentFrameIndex % images.count + var image = images[index] + image = generateScaledImage(image: images[index], size: image.size.aspectFitted(CGSize(width: 384, height: 384)), opaque: false, scale: 1.0)! + let ciImage = CIImage(image: image) + self.image = ciImage + completion(ciImage) + return + } + } else if case .video = self.content { if self.videoOutput == nil { self.setupVideoOutput() } @@ -475,7 +511,6 @@ final class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { let options = NSMutableDictionary() options.setObject(ioSurfaceProperties, forKey: kCVPixelBufferIOSurfacePropertiesKey as NSString) - var pixelBuffer: CVPixelBuffer? CVPixelBufferCreate( kCFAllocatorDefault, diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift index 417c9b2cf11..deec4efc3d0 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift @@ -448,6 +448,10 @@ public final class MediaEditorValues: Codable, Equatable { return self.qualityPreset == .sticker } + public var cropValues: (offset: CGPoint, rotation: CGFloat, scale: CGFloat) { + return (self.cropOffset, self.cropRotation, self.cropScale) + } + public init( peerId: EnginePeer.Id, originalDimensions: PixelDimensions, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD index f051ad34ebb..9d69ebf7d55 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD +++ b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", @@ -58,6 +58,8 @@ swift_library( "//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController", "//submodules/TelegramUI/Components/StickerPickerScreen", "//submodules/UIKitRuntimeUtils", + "//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation", + "//submodules/Components/HierarchyTrackingLayer", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift new file mode 100644 index 00000000000..c45a76b7c1d --- /dev/null +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift @@ -0,0 +1,274 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import AccountContext +import TextFormat +import SaveToCameraRoll +import ImageCompression +import LocalMediaResources + +public extension MediaEditorScreen { + static func makeEditStoryController( + context: AccountContext, + peer: EnginePeer, + storyItem: EngineStoryItem, + videoPlaybackPosition: Double?, + repost: Bool, + transitionIn: MediaEditorScreen.TransitionIn, + transitionOut: MediaEditorScreen.TransitionOut?, + completed: @escaping () -> Void = {}, + willDismiss: @escaping () -> Void = {}, + update: @escaping (Disposable?) -> Void + ) -> MediaEditorScreen? { + guard let peerReference = PeerReference(peer._asPeer()) else { + return nil + } + let subject: Signal + subject = getStorySource(engine: context.engine, peerId: peer.id, id: Int64(storyItem.id)) + |> mapToSignal { source in + if !repost, let source { + return .single(.draft(source, Int64(storyItem.id))) + } else { + let media = storyItem.media._asMedia() + return fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: storyItem.id, media: media)) + |> mapToSignal { (value, isImage) -> Signal in + guard case let .data(data) = value, data.complete else { + return .complete() + } + if let image = UIImage(contentsOfFile: data.path) { + return .single(nil) + |> then( + .single(.image(image, PixelDimensions(image.size), nil, .bottomRight)) + |> delay(0.1, queue: Queue.mainQueue()) + ) + } else { + var duration: Double? + if let file = media as? TelegramMediaFile { + duration = file.duration + } + let symlinkPath = data.path + ".mp4" + if fileSize(symlinkPath) == nil { + let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath) + } + return .single(nil) + |> then( + .single(.video(symlinkPath, nil, false, nil, nil, PixelDimensions(width: 720, height: 1280), duration ?? 0.0, [], .bottomRight)) + ) + } + } + } + } + + let initialCaption: NSAttributedString? + let initialPrivacy: EngineStoryPrivacy? + let initialMediaAreas: [MediaArea] + if repost { + initialCaption = nil + initialPrivacy = nil + initialMediaAreas = [] + } else { + initialCaption = chatInputStateStringWithAppliedEntities(storyItem.text, entities: storyItem.entities) + initialPrivacy = storyItem.privacy + initialMediaAreas = storyItem.mediaAreas + } + + let externalState = MediaEditorTransitionOutExternalState( + storyTarget: nil, + isForcedTarget: false, + isPeerArchived: false, + transitionOut: nil + ) + + var updateProgressImpl: ((Float) -> Void)? + let controller = MediaEditorScreen( + context: context, + mode: .storyEditor, + subject: subject, + isEditing: !repost, + forwardSource: repost ? (peer, storyItem) : nil, + initialCaption: initialCaption, + initialPrivacy: initialPrivacy, + initialMediaAreas: initialMediaAreas, + initialVideoPosition: videoPlaybackPosition, + transitionIn: transitionIn, + transitionOut: { finished, isNew in + if repost && finished { + if let transitionOut = externalState.transitionOut?(externalState.storyTarget, externalState.isPeerArchived), let destinationView = transitionOut.destinationView { + return MediaEditorScreen.TransitionOut( + destinationView: destinationView, + destinationRect: transitionOut.destinationRect, + destinationCornerRadius: transitionOut.destinationCornerRadius + ) + } else { + return nil + } + } else { + return transitionOut + } + }, + completion: { result, commit in + let entities = generateChatInputTextEntities(result.caption) + + if repost { + let target: Stories.PendingTarget + let targetPeerId: EnginePeer.Id + if let sendAsPeerId = result.options.sendAsPeerId { + target = .peer(sendAsPeerId) + targetPeerId = sendAsPeerId + } else { + target = .myStories + targetPeerId = context.account.peerId + } + externalState.storyTarget = target + + completed() + + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: targetPeerId)) + |> deliverOnMainQueue).startStandalone(next: { peer in + guard let peer else { + return + } + + if case let .user(user) = peer { + externalState.isPeerArchived = user.storiesHidden ?? false + + } else if case let .channel(channel) = peer { + externalState.isPeerArchived = channel.storiesHidden ?? false + } + + let forwardInfo = Stories.PendingForwardInfo(peerId: peerReference.id, storyId: storyItem.id, isModified: result.media != nil) + + if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface { + var existingMedia: EngineMedia? + if let _ = result.media { + } else { + existingMedia = storyItem.media + } + rootController.proceedWithStoryUpload(target: target, result: result as! MediaEditorScreenResult, existingMedia: existingMedia, forwardInfo: forwardInfo, externalState: externalState, commit: commit) + } + }) + } else { + var updatedText: String? + var updatedEntities: [MessageTextEntity]? + if result.caption.string != storyItem.text || entities != storyItem.entities { + updatedText = result.caption.string + updatedEntities = entities + } + + if let mediaResult = result.media { + switch mediaResult { + case let .image(image, dimensions): + updateProgressImpl?(0.0) + + let tempFile = TempBox.shared.tempFile(fileName: "file") + defer { + TempBox.shared.dispose(tempFile) + } + if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) { + update((context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: .image(dimensions: dimensions, data: imageData, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil) + |> deliverOnMainQueue).startStrict(next: { result in + switch result { + case let .progress(progress): + updateProgressImpl?(progress) + case .completed: + Queue.mainQueue().after(0.1) { + willDismiss() + + HapticFeedback().success() + + commit({}) + } + } + })) + } + case let .video(content, firstFrameImage, values, duration, dimensions): + updateProgressImpl?(0.0) + + if let valuesData = try? JSONEncoder().encode(values) { + let data = MemoryBuffer(data: valuesData) + let digest = MemoryBuffer(data: data.md5Digest()) + let adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true) + + let resource: TelegramMediaResource + switch content { + case let .imageFile(path): + resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments) + case let .videoFile(path): + resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments) + case let .asset(localIdentifier): + resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments)) + } + + let tempFile = TempBox.shared.tempFile(fileName: "file") + defer { + TempBox.shared.dispose(tempFile) + } + let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6, tempFilePath: tempFile.path) } + let firstFrameFile = firstFrameImageData.flatMap { data -> TempBoxFile? in + let file = TempBox.shared.tempFile(fileName: "image.jpg") + if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) { + return file + } else { + return nil + } + } + + update((context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil) + |> deliverOnMainQueue).startStrict(next: { result in + switch result { + case let .progress(progress): + updateProgressImpl?(progress) + case .completed: + Queue.mainQueue().after(0.1) { + willDismiss() + + HapticFeedback().success() + + commit({}) + } + } + })) + } + default: + break + } + } else if updatedText != nil { + let _ = (context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: nil, mediaAreas: nil, text: updatedText, entities: updatedEntities, privacy: nil) + |> deliverOnMainQueue).startStandalone(next: { result in + switch result { + case .completed: + Queue.mainQueue().after(0.1) { + willDismiss() + + HapticFeedback().success() + commit({}) + } + default: + break + } + }) + } else { + willDismiss() + + HapticFeedback().success() + + commit({}) + } + } + } + ) + controller.willDismiss = willDismiss + controller.navigationPresentation = .flatModal + + updateProgressImpl = { [weak controller] progress in + controller?.updateEditProgress(progress, cancel: { + update(nil) + }) + } + + return controller + } +} diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift index 0562d0fb63b..10987de836b 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift @@ -17,6 +17,7 @@ import LottieAnimationComponent import MessageInputPanelComponent import DustEffect import PlainButtonComponent +import ImageObjectSeparation private final class MediaCutoutScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -118,7 +119,7 @@ private final class MediaCutoutScreenComponent: Component { } component.mediaEditor.processImage { [weak self] originalImage, _ in - cutoutImage(from: originalImage, values: nil, target: .point(point), includeExtracted: false, completion: { [weak self] results in + cutoutImage(from: originalImage, crop: nil, target: .point(point), includeExtracted: false, completion: { [weak self] results in Queue.mainQueue().async { if let self, let _ = self.component, let result = results.first, let maskImage = result.maskImage, let controller = self.environment?.controller() as? MediaCutoutScreen { if case let .image(mask, _) = maskImage { @@ -427,7 +428,7 @@ private final class MediaCutoutScreenComponent: Component { if isFirstTime { let values = component.mediaEditor.values component.mediaEditor.processImage { originalImage, editedImage in - cutoutImage(from: originalImage, editedImage: editedImage, values: values, target: .all, completion: { results in + cutoutImage(from: originalImage, editedImage: editedImage, crop: values.cropValues, target: .all, completion: { results in Queue.mainQueue().async { if !results.isEmpty { for result in results { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 712bd98407f..1f7551ba8bb 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -47,6 +47,7 @@ import StickerPeekUI import StickerPackEditTitleController import StickerPickerScreen import UIKitRuntimeUtils +import ImageObjectSeparation private let playbackButtonTag = GenericComponentViewTag() private let muteButtonTag = GenericComponentViewTag() @@ -2008,7 +2009,7 @@ final class MediaEditorScreenComponent: Component { if let subject = controller.node.subject, case .empty = subject { - } else if let canCutout = controller.node.canCutout { + } else if case let .known(canCutout, _, hasTransparency) = controller.node.stickerCutoutStatus { if controller.node.isCutout || controller.node.stickerMaskDrawingView?.internalState.canUndo == true { hasUndoButton = true } @@ -2020,7 +2021,7 @@ final class MediaEditorScreenComponent: Component { hasRestoreButton = true } } - if hasUndoButton || controller.node.hasTransparency { + if hasUndoButton || hasTransparency { hasOutlineButton = true } } @@ -2409,6 +2410,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate case generic case addingToPack case editing + case businessIntro } case storyEditor @@ -2537,8 +2539,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate private var isDismissed = false private var isDismissBySwipeSuppressed = false - fileprivate var canCutout: Bool? - fileprivate var hasTransparency = false + fileprivate var stickerCutoutStatus: MediaEditor.CutoutStatus = .unknown + private var stickerCutoutStatusDisposable: Disposable? fileprivate var isCutout = false private (set) var hasAnyChanges = false @@ -2807,6 +2809,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.appInForegroundDisposable?.dispose() self.playbackPositionDisposable?.dispose() self.availableReactionsDisposable?.dispose() + self.stickerCutoutStatusDisposable?.dispose() } private func setup(with subject: MediaEditorScreen.Subject) { @@ -2939,15 +2942,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut)) } - } - mediaEditor.canCutoutUpdated = { [weak self] canCutout, hasTransparency in + } + self.stickerCutoutStatusDisposable = (mediaEditor.cutoutStatus + |> deliverOnMainQueue).start(next: { [weak self] cutoutStatus in guard let self else { return } - self.canCutout = canCutout - self.hasTransparency = hasTransparency + self.stickerCutoutStatus = cutoutStatus self.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.25)) - } + }) mediaEditor.maskUpdated = { [weak self] mask, apply in guard let self else { return @@ -3036,7 +3039,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate messageFile = nil } - let renderer = DrawingMessageRenderer(context: self.context, messages: messages) + let renderer = DrawingMessageRenderer(context: self.context, messages: messages, parentView: self.view) renderer.render(completion: { result in if case .draft = subject, let existingEntityView = self.entitiesView.getView(where: { entityView in if let stickerEntityView = entityView as? DrawingStickerEntityView, case .message = (stickerEntityView.entity as! DrawingStickerEntity).content { @@ -3106,6 +3109,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if controller.isEmbeddedEditor == true { mediaEditor.onFirstDisplay = { [weak self] in if let self { + if let transitionInView = self.transitionInView { + self.transitionInView = nil + transitionInView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak transitionInView] _ in + transitionInView?.removeFromSuperview() + }) + } + if effectiveSubject.isPhoto { self.previewContainerView.layer.allowsGroupOpacity = true self.previewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in @@ -3762,6 +3772,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if let transitionOut = controller.transitionOut(finished, isNew), let destinationView = transitionOut.destinationView { var destinationTransitionView: UIView? + var destinationTransitionRect: CGRect = .zero if !finished { if let transitionIn = controller.transitionIn, case let .gallery(galleryTransitionIn) = transitionIn, let sourceImage = galleryTransitionIn.sourceImage, isNew != true { let sourceSuperView = galleryTransitionIn.sourceView?.superview?.superview @@ -3771,6 +3782,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate destinationTransitionOutView.frame = self.previewContainerView.convert(self.previewContainerView.bounds, to: sourceSuperView) sourceSuperView?.addSubview(destinationTransitionOutView) destinationTransitionView = destinationTransitionOutView + destinationTransitionRect = galleryTransitionIn.sourceRect } if let view = self.componentHost.view as? MediaEditorScreenComponent.View { view.animateOut(to: .gallery) @@ -3850,7 +3862,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if let destinationTransitionView { self.previewContainerView.layer.allowsGroupOpacity = true self.previewContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - destinationTransitionView.layer.animateFrame(from: destinationTransitionView.frame, to: destinationView.convert(destinationView.bounds, to: destinationTransitionView.superview), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak destinationTransitionView] _ in + destinationTransitionView.layer.animateFrame(from: destinationTransitionView.frame, to: destinationView.convert(destinationTransitionRect, to: destinationTransitionView.superview), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak destinationTransitionView] _ in destinationTransitionView?.removeFromSuperview() }) } @@ -4922,8 +4934,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } } }, - cutoutUndo: { [weak self, weak controller] in - if let self, let controller, let mediaEditor = self.mediaEditor, let stickerMaskDrawingView = self.stickerMaskDrawingView { + cutoutUndo: { [weak self] in + if let self, let mediaEditor = self.mediaEditor, let stickerMaskDrawingView = self.stickerMaskDrawingView { if self.entitiesView.hasSelection { self.entitiesView.selectEntity(nil) } @@ -4934,12 +4946,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate mediaEditor.setSegmentationMask(drawingImage) } - if self.isDisplayingTool == .cutoutRestore && !stickerMaskDrawingView.internalState.canUndo && !controller.node.isCutout { + if self.isDisplayingTool == .cutoutRestore && !stickerMaskDrawingView.internalState.canUndo && !self.isCutout { self.cutoutScreen?.mode = .erase self.isDisplayingTool = .cutoutErase self.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.25)) } - } else if controller.node.isCutout { + } else if self.isCutout { let action = { [weak self, weak mediaEditor] in guard let self, let mediaEditor else { return @@ -6492,6 +6504,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate var file = stickerFile(resource: resource, thumbnailResource: thumbnailResource, size: Int64(0), dimensions: PixelDimensions(image.size), duration: self.preferredStickerDuration(), isVideo: isVideo) var menuItems: [ContextMenuItem] = [] + var hasEmojiSelection = true if case let .stickerEditor(mode) = self.mode { switch mode { case .generic: @@ -6558,7 +6571,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate contextItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in - c.popItems() + c?.popItems() }))) contextItems.append(.separator) @@ -6619,7 +6632,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate tipSignal: nil, dismissed: nil ) - c.pushItems(items: .single(items)) + c?.pushItems(items: .single(items)) }))) case .editing: menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_ReplaceSticker, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in @@ -6650,6 +6663,24 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } f(.default) + let _ = (imagesReady.get() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let self else { + return + } + self.uploadSticker(file, action: .upload) + }) + }))) + case .businessIntro: + hasEmojiSelection = false + menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_SetAsIntroSticker, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in + guard let self else { + return + } + f(.default) + let _ = (imagesReady.get() |> filter { $0 } |> take(1) @@ -6680,14 +6711,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate theme: presentationData.theme, strings: presentationData.strings, item: .portal(portalView), - isCreating: true, + isCreating: hasEmojiSelection, selectedEmoji: self.stickerSelectedEmoji, selectedEmojiUpdated: { [weak self] selectedEmoji in if let self { self.stickerSelectedEmoji = selectedEmoji } }, - recommendedEmoji: stickerRecommendedEmoji, + recommendedEmoji: self.stickerRecommendedEmoji, menu: menuItems, openPremiumIntro: {} ), @@ -6934,7 +6965,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate case .addToStickerPack, .createStickerPack: if let (packReference, packTitle) = packReferenceAndTitle, let navigationController = self.navigationController as? NavigationController { Queue.mainQueue().after(0.2) { - let controller = self.context.sharedContext.makeStickerPackScreen(context: self.context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], isEditing: false, expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: self.sendSticker) + let controller = self.context.sharedContext.makeStickerPackScreen(context: self.context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], isEditing: false, expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: self.sendSticker, actionPerformed: nil) (navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root)) Queue.mainQueue().after(0.1) { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift index df261499171..c0f5a74ee81 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift @@ -135,10 +135,7 @@ final class StickerCutoutOutlineView: UIView { private func animateBump(path: BezierPath) { let boundingBox = path.path.cgPath.boundingBox let pathCenter = CGPoint(x: boundingBox.midX, y: boundingBox.midY) - -// let originalPosition = self.imageLayer.position -// let originalAnchorPoint = self.imageLayer.anchorPoint - + let layerPathCenter = self.imageLayer.convert(pathCenter, from: self.imageLayer.superlayer) self.imageLayer.anchorPoint = CGPoint(x: layerPathCenter.x / layer.bounds.width, y: layerPathCenter.y / layer.bounds.height) self.imageLayer.position = layerPathCenter diff --git a/submodules/TelegramUI/Components/MediaScrubberComponent/BUILD b/submodules/TelegramUI/Components/MediaScrubberComponent/BUILD index 06766ec3c30..435dcfdf42a 100644 --- a/submodules/TelegramUI/Components/MediaScrubberComponent/BUILD +++ b/submodules/TelegramUI/Components/MediaScrubberComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD index 2cacc7517f7..02923e71bc0 100644 --- a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD index 1963f35d45f..a5f4c2e1919 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index e8fcbc188c3..16102b62511 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -781,6 +781,7 @@ public final class MessageInputPanelComponent: Component { externalState: self.textFieldExternalState, fontSize: 17.0, textColor: UIColor(rgb: 0xffffff), + accentColor: UIColor(rgb: 0xffffff), insets: UIEdgeInsets(top: 9.0, left: 8.0, bottom: 10.0, right: 48.0), hideKeyboard: component.hideKeyboard, customInputView: component.customInputView, @@ -791,7 +792,7 @@ public final class MessageInputPanelComponent: Component { } }, isOneLineWhenUnfocused: component.style == .media, - formatMenuAvailability: component.isFormattingLocked ? .locked : .available, + formatMenuAvailability: component.isFormattingLocked ? .locked : .available(TextFieldComponent.FormatMenuAvailability.Action.all), lockedFormatAction: { component.presentTextFormattingTooltip?() }, diff --git a/submodules/TelegramUI/Components/MoreHeaderButton/BUILD b/submodules/TelegramUI/Components/MoreHeaderButton/BUILD index 9b24a6f0c0c..3198bdf9010 100644 --- a/submodules/TelegramUI/Components/MoreHeaderButton/BUILD +++ b/submodules/TelegramUI/Components/MoreHeaderButton/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/MultiAnimationRenderer/BUILD b/submodules/TelegramUI/Components/MultiAnimationRenderer/BUILD index 4f5a66267d3..83c851579d2 100644 --- a/submodules/TelegramUI/Components/MultiAnimationRenderer/BUILD +++ b/submodules/TelegramUI/Components/MultiAnimationRenderer/BUILD @@ -50,7 +50,7 @@ swift_library( ":MultiAnimationRendererBundle", ], copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift b/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift index bde9a112065..0d010bdde49 100644 --- a/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift +++ b/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift @@ -532,7 +532,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { if itemContext.targets.isEmpty { strongSelf.itemContexts.removeValue(forKey: itemKey) } - } + }.strict() } func loadFirstFrameSynchronously(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize) -> Bool { @@ -598,7 +598,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { completion(false, true) } } - }) + }).strict() } func loadFirstFrameAsImage(cache: AnimationCache, itemId: String, size: CGSize, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (CGImage?) -> Void) -> Disposable { @@ -626,7 +626,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { completion(nil) } } - }) + }).strict() } func setFrameIndex(itemId: String, size: CGSize, frameIndex: Int, placeholder: UIImage) { @@ -729,7 +729,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { return ActionDisposable { disposable.dispose() - } + }.strict() } public func loadFirstFrameSynchronously(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize) -> Bool { @@ -763,7 +763,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { self.groupContext = groupContext } - return groupContext.loadFirstFrame(target: target, cache: cache, itemId: itemId, size: size, fetch: fetch, completion: completion) + return groupContext.loadFirstFrame(target: target, cache: cache, itemId: itemId, size: size, fetch: fetch, completion: completion).strict() } public func loadFirstFrameAsImage(cache: AnimationCache, itemId: String, size: CGSize, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (CGImage?) -> Void) -> Disposable { @@ -780,7 +780,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { self.groupContext = groupContext } - return groupContext.loadFirstFrameAsImage(cache: cache, itemId: itemId, size: size, fetch: fetch, completion: completion) + return groupContext.loadFirstFrameAsImage(cache: cache, itemId: itemId, size: size, fetch: fetch, completion: completion).strict() } public func setFrameIndex(itemId: String, size: CGSize, frameIndex: Int, placeholder: UIImage) { diff --git a/submodules/TelegramUI/Components/MultiScaleTextNode/BUILD b/submodules/TelegramUI/Components/MultiScaleTextNode/BUILD index 1885ed1df94..3386e77164f 100644 --- a/submodules/TelegramUI/Components/MultiScaleTextNode/BUILD +++ b/submodules/TelegramUI/Components/MultiScaleTextNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD b/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD index dbbd2364a66..02a12aa8a5d 100644 --- a/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD +++ b/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/NavigationSearchComponent/BUILD b/submodules/TelegramUI/Components/NavigationSearchComponent/BUILD index 858a5eb52af..7cb36c0408a 100644 --- a/submodules/TelegramUI/Components/NavigationSearchComponent/BUILD +++ b/submodules/TelegramUI/Components/NavigationSearchComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/NotificationExceptionsScreen/BUILD b/submodules/TelegramUI/Components/NotificationExceptionsScreen/BUILD index c561d70b864..1cbe4cb87b5 100644 --- a/submodules/TelegramUI/Components/NotificationExceptionsScreen/BUILD +++ b/submodules/TelegramUI/Components/NotificationExceptionsScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/NotificationPeerExceptionController/BUILD b/submodules/TelegramUI/Components/NotificationPeerExceptionController/BUILD index a7990a39786..a2da95f0575 100644 --- a/submodules/TelegramUI/Components/NotificationPeerExceptionController/BUILD +++ b/submodules/TelegramUI/Components/NotificationPeerExceptionController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/OptionButtonComponent/BUILD b/submodules/TelegramUI/Components/OptionButtonComponent/BUILD index ffb367f27e1..df32635132f 100644 --- a/submodules/TelegramUI/Components/OptionButtonComponent/BUILD +++ b/submodules/TelegramUI/Components/OptionButtonComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/BUILD b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/BUILD index 99f9217acf9..b9d603c9b42 100644 --- a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display:Display", diff --git a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiListInputComponent.swift b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiListInputComponent.swift index 1d7a582d772..df49f1a2642 100644 --- a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiListInputComponent.swift +++ b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiListInputComponent.swift @@ -105,7 +105,7 @@ final class EmojiListInputComponent: Component { private var component: EmojiListInputComponent? private weak var state: EmptyComponentState? - private var itemLayers: [Int64: EmojiPagerContentComponent.View.ItemLayer] = [:] + private var itemLayers: [Int64: EmojiKeyboardItemLayer] = [:] private let trailingPlaceholder = ComponentView() private let caretIndicator: CaretIndicatorView @@ -239,7 +239,7 @@ final class EmojiListInputComponent: Component { var itemTransition = transition var animateIn = false - let itemLayer: EmojiPagerContentComponent.View.ItemLayer + let itemLayer: EmojiKeyboardItemLayer if let current = self.itemLayers[itemKey] { itemLayer = current } else { @@ -249,7 +249,7 @@ final class EmojiListInputComponent: Component { let animationData = EntityKeyboardAnimationData( file: item.file ) - itemLayer = EmojiPagerContentComponent.View.ItemLayer( + itemLayer = EmojiKeyboardItemLayer( item: EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/BUILD index 586c694ff34..838f7bc6c2a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift index 0c44d4b0d30..c3333137e26 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift @@ -402,7 +402,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, AS if self.chatController == nil { let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(id: self.context.account.peerId), subject: nil, botStart: nil, mode: .standard(.embedded(invertDirection: false))) chatController.alwaysShowSearchResultsAsList = true - + chatController.includeSavedPeersInSearchResults = true self.chatController = chatController chatController.navigation_setNavigationController(self.navigationController()) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD index 067d9150189..7129c9924fd 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD index 36d9e699d32..e20da0b5ca2 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/BUILD index 1e8570712a3..2936b06232c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index 12b514ecbbd..b6ef64e16f8 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -18,7 +18,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = NGDEPS + [ "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift index f8edc3f4a6b..94d2ba03b25 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift @@ -271,6 +271,18 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { } } + self.containerNode.shouldBegin = { [weak self] point in + guard let self else { + return false + } + + if self.linkItemAtPoint(point) != nil { + return false + } + + return true + } + self.containerNode.activated = { [weak self] gesture, _ in guard let strongSelf = self, let item = strongSelf.item, let contextAction = item.contextAction else { gesture.cancel() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift index b4cf661eabb..0d013cd5c5e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift @@ -141,7 +141,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable { sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, - peerMode: .peer, + peerMode: .memberList, peer: .peer(peer: EnginePeer(member.peer), chatPeer: EnginePeer(member.peer)), status: .presence(presence, presentationData.dateTimeFormat), rightLabelText: label, @@ -153,7 +153,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable { actionIcon: .none, index: nil, header: nil, - action: { _ in + action: member.peer.id == context.account.peerId ? nil : { _ in action(member, .open) }, disabledAction: nil, @@ -396,7 +396,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { items.append(.action(ContextMenuActionItem(text: presentationData.strings.GroupInfo_ActionPromote, icon: { _ in return nil }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { action(member, .promote) }) }))) @@ -406,7 +406,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { items.append(.action(ContextMenuActionItem(text: presentationData.strings.GroupInfo_ActionRestrict, icon: { _ in return nil }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { action(member, .restrict) }) }))) @@ -414,7 +414,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Delete, textColor: .destructive, icon: { _ in return nil }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { action(member, .remove) }) }))) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 163f3b754bc..46a626b9510 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -351,6 +351,7 @@ final class PeerInfoScreenData { let hasSavedMessageTags: Bool let isPremiumRequiredForStoryPosting: Bool let personalChannel: PeerInfoPersonalChannelData? + let starsState: StarsContext.State? let _isContact: Bool var forceIsContact: Bool = false @@ -390,7 +391,8 @@ final class PeerInfoScreenData { accountIsPremium: Bool, hasSavedMessageTags: Bool, isPremiumRequiredForStoryPosting: Bool, - personalChannel: PeerInfoPersonalChannelData? + personalChannel: PeerInfoPersonalChannelData?, + starsState: StarsContext.State? ) { self.peer = peer self.chatPeer = chatPeer @@ -419,6 +421,7 @@ final class PeerInfoScreenData { self.hasSavedMessageTags = hasSavedMessageTags self.isPremiumRequiredForStoryPosting = isPremiumRequiredForStoryPosting self.personalChannel = personalChannel + self.starsState = starsState } } @@ -678,7 +681,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, @@ -797,6 +800,19 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, } } + let starsState: Signal + if let starsContext { + starsState = starsContext.state + |> map { state in + if let state, state.balance > 0 || !state.transactions.isEmpty { + return state + } + return nil + } + } else { + starsState = .single(nil) + } + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), accountsAndPeers, @@ -821,9 +837,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 @@ -867,7 +884,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, bots: bots, hasPassport: hasPassport, hasWatchApp: hasWatchApp, - enableQRLogin: enableQRLogin) + enableQRLogin: enableQRLogin + ) return PeerInfoScreenData( peer: peer, @@ -896,12 +914,13 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, accountIsPremium: peer?.isPremium ?? false, hasSavedMessageTags: false, isPremiumRequiredForStoryPosting: true, - personalChannel: personalChannel + personalChannel: personalChannel, + starsState: starsState ) } } -func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic) -> Signal { +func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, privacySettings: Signal) -> Signal { return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: isSettings) |> mapToSignal { inputData -> Signal in let wasUpgradedGroup = Atomic(value: nil) @@ -935,7 +954,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? @@ -1165,9 +1185,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, - peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: false) + peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: false), + privacySettings ) - |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, personalChannel -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, personalChannel, privacySettings -> PeerInfoScreenData in var availablePanes = availablePanes if isMyProfile { @@ -1208,6 +1229,32 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen let peer = peerView.peers[userPeerId] + var globalSettings: TelegramGlobalSettings? + if let privacySettings { + globalSettings = TelegramGlobalSettings( + suggestPhoneNumberConfirmation: false, + suggestPasswordConfirmation: false, + suggestPasswordSetup: false, + premiumGracePeriod: false, + accountsAndPeers: [], + activeSessionsContext: nil, + webSessionsContext: nil, + otherSessionsCount: nil, + proxySettings: ProxySettings(enabled: false, servers: [], activeServer: nil, useForCalls: false), + notificationAuthorizationStatus: .notDetermined, + notificationWarningSuppressed: false, + notificationExceptions: nil, + inAppNotificationSettings: InAppNotificationSettings(playSounds: false, vibrate: false, displayPreviews: false, totalUnreadCountDisplayStyle: .filtered, totalUnreadCountDisplayCategory: .chats, totalUnreadCountIncludeTags: .all, displayNameOnLockscreen: false, displayNotificationsFromAllAccounts: false, customSound: nil), + privacySettings: privacySettings, + unreadTrendingStickerPacks: 0, + archivedStickerPacks: nil, + userLimits: context.userLimits, + bots: [], + hasPassport: false, + hasWatchApp: false, + enableQRLogin: false) + } + return PeerInfoScreenData( peer: peer, chatPeer: peerView.peers[peerId], @@ -1225,7 +1272,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen storyListContext: storyListContext, storyArchiveListContext: storyArchiveListContext, encryptionKeyFingerprint: encryptionKeyFingerprint, - globalSettings: nil, + globalSettings: globalSettings, invitations: nil, requests: nil, requestsContext: nil, @@ -1235,7 +1282,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen accountIsPremium: accountIsPremium, hasSavedMessageTags: hasSavedMessageTags, isPremiumRequiredForStoryPosting: false, - personalChannel: personalChannel + personalChannel: personalChannel, + starsState: nil ) } case .channel: @@ -1406,7 +1454,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): @@ -1700,7 +1749,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/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index 5ebd0e709ab..79ed70241b2 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -2247,6 +2247,12 @@ final class PeerInfoHeaderNode: ASDisplayNode { return result } + if self.isSettings { + if self.subtitleNodeRawContainer.bounds.contains(self.view.convert(point, to: self.subtitleNodeRawContainer.view)) { + return self.subtitleNodeRawContainer.view + } + } + if let result = self.buttonsContainerNode.view.hitTest(self.view.convert(point, to: self.buttonsContainerNode.view), with: event) { return result } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index ca3183dc1e6..5983f32a6bd 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -419,7 +419,7 @@ private final class PeerInfoPendingPane { } } - let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, chatLocation: chatLocation, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: key == .storyArchive, isProfileEmbedded: true, canManageStories: canManage, navigationController: chatControllerInteraction.navigationController, listContext: key == .storyArchive ? data.storyArchiveListContext : data.storyListContext) + let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: key == .storyArchive, isProfileEmbedded: true, canManageStories: canManage, navigationController: chatControllerInteraction.navigationController, listContext: key == .storyArchive ? data.storyArchiveListContext : data.storyListContext) paneNode = visualPaneNode visualPaneNode.openCurrentDate = { openMediaCalendar() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 9f2aac0a7d5..f71c23a5af1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -48,8 +48,11 @@ import TelegramNotices import SaveToCameraRoll import PeerInfoUI // MARK: Nicegram Imports -import FeatPremiumUI +import struct FeatPremiumUI.PremiumUITgHelper +import FeatWallet import NGAiChatUI +import NGAssistantUI +import NGRepoUser import NGWebUtils import NGStrings import NGUI @@ -57,6 +60,7 @@ import NGCore import NGData import NGEnv import NGLab +import NicegramWallet import UndoUI // import ListMessageItem @@ -542,6 +546,7 @@ private enum PeerInfoSettingsSection { case businessSetup case profile case premiumManagement + case stars } private enum PeerInfoReportType { @@ -780,6 +785,7 @@ private enum SettingsSection: Int, CaseIterable { case myProfile case proxy case nicegram + case nicegramWallet case apps case shortcuts case advanced @@ -971,6 +977,29 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p interaction.openSettings(.nicegram) })) + // MARK: Nicegram Wallet + let getWalletAvailabilityUseCase = WalletContainer.shared.getWalletAvailabilityUseCase() + if #available(iOS 15.0, *), getWalletAvailabilityUseCase() { + items[.nicegramWallet]?.append(PeerInfoScreenHeaderItem(id: 0, text: "Multichain, Non-custodial")) + items[.nicegramWallet]!.append(PeerInfoScreenDisclosureItem(id: 1, text: "Nicegram Wallet", icon: PresentationResourcesSettings.nicegramIcon, action: { + Task { @MainActor in + let getCurrentUserUseCase = RepoUserContainer.shared.getCurrentUserUseCase() + let getCurrentWalletUseCase = WalletStorageModule.shared.getCurrentWalletUseCase() + + if getCurrentUserUseCase.isAuthorized(), + await getCurrentWalletUseCase() != nil { + let verificationManager = SecurityModule.shared.verificationManager() + verificationManager.doAfterVerification { + WalletEntryPoints.openHome() + } + } else { + AssistantUITgHelper.routeToAssistant(source: .generic) + } + } + })) + } + // + var appIndex = 1000 if let settings = data.globalSettings { for bot in settings.bots { @@ -988,7 +1017,18 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p iconSignal = .single(UIImage(bundleImageName: "Settings/Menu/Websites")!) } let label: PeerInfoScreenDisclosureItem.Label = bot.flags.contains(.notActivated) || bot.flags.contains(.showInSettingsDisclaimer) ? .titleBadge(presentationData.strings.Settings_New, presentationData.theme.list.itemAccentColor) : .none - items[.apps]!.append(PeerInfoScreenDisclosureItem(id: bot.peer.id.id._internalGetInt64Value(), label: label, text: bot.shortName, icon: nil, iconSignal: iconSignal, action: { + + // MARK: Nicegram Wallet + let text: String + if bot.peer.id.id._internalGetInt64Value() == 1985737506 { + text = "TON Wallet Bot" + } else { + text = bot.shortName + } + // + + // MARK: Nicegram Wallet, change "text: bot.shortName" to "text: text" + items[.apps]!.append(PeerInfoScreenDisclosureItem(id: bot.peer.id.id._internalGetInt64Value(), label: label, text: text, icon: nil, iconSignal: iconSignal, action: { interaction.openBotApp(bot) })) appIndex += 1 @@ -1053,13 +1093,23 @@ 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: { + if let starsState = data.starsState { + let balanceText: String + if starsState.balance > 0 { + balanceText = presentationStringsFormattedNumber(Int32(starsState.balance), presentationData.dateTimeFormat.groupingSeparator) + } else { + balanceText = "" + } + items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 102, label: .text(balanceText), text: presentationData.strings.Settings_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) })) - // MARK: Nicegram, comment this item (hide "Gift Premium") /* - 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) })) */ @@ -2084,7 +2134,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL } } - if let cachedData = data.cachedData as? CachedChannelData, isCreator || cachedData.flags.contains(.canViewStats) { + if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) { items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStats, label: .none, text: presentationData.strings.Channel_Info_Stats, icon: UIImage(bundleImageName: "Chat/Info/StatsIcon"), action: { interaction.openStats(false) })) @@ -2671,7 +2721,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 @@ -3013,10 +3063,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro var items: [ContextMenuItem] = [] items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController { if let channel = currentPeer as? TelegramChannel, channel.flags.contains(.isForum), let threadId = message.threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, keepStack: .default).startStandalone() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default).startStandalone() } else { let targetLocation: NavigateToChatControllerParams.Location if case let .replyThread(message) = strongSelf.chatLocation { @@ -3055,7 +3105,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if let linkForCopying = linkForCopying { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss(completion: {}) + c?.dismiss(completion: {}) UIPasteboard.general.string = linkForCopying let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -3067,7 +3117,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } else if message.id.peerId.namespace != Namespaces.Peer.SecretChat && message.minAutoremoveOrClearTimeout == nil { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self { strongSelf.forwardMessages(messageIds: Set([message.id])) } @@ -3079,7 +3129,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let presentationData = strongSelf.presentationData let peerId = strongSelf.peerId items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { c, _ in - c.setItems(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId)) + c?.setItems(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId)) |> map { peer -> ContextController.Items in var items: [ContextMenuItem] = [] let messageIds = [message.id] @@ -3103,7 +3153,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro globalTitle = presentationData.strings.Conversation_DeleteMessagesForEveryone } items.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self { strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() @@ -3122,7 +3172,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } items.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self { strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).startStandalone() @@ -3140,7 +3190,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items.append(.separator) items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self { strongSelf.chatInterfaceInteraction.toggleMessagesSelection([message.id], true) strongSelf.expandTabs(animated: true) @@ -3175,10 +3225,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro var items: [ContextMenuItem] = [] items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController { if let channel = currentPeer as? TelegramChannel, channel.flags.contains(.isForum), let threadId = message.threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, keepStack: .default).startStandalone() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default).startStandalone() } else { let targetLocation: NavigateToChatControllerParams.Location if case let .replyThread(message) = strongSelf.chatLocation { @@ -3219,7 +3269,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } else if message.id.peerId.namespace != Namespaces.Peer.SecretChat { items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self { strongSelf.forwardMessages(messageIds: [message.id]) } @@ -3229,7 +3279,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if actions.options.contains(.deleteLocally) || actions.options.contains(.deleteGlobally) { items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { c, f in - c.setItems(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId)) + c?.setItems(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId)) |> map { peer -> ContextController.Items in var items: [ContextMenuItem] = [] let messageIds = [message.id] @@ -3253,7 +3303,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone } items.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self { strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() @@ -3272,7 +3322,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } items.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { c, f in - c.dismiss(completion: { + c?.dismiss(completion: { if let strongSelf = self { strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).startStandalone() @@ -3337,7 +3387,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring), additive: false) } strongSelf.paneContainerNode.updateSelectedMessageIds(strongSelf.state.selectedMessageIds, animated: true) - }, sendCurrentMessage: { _ in + }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false @@ -3453,9 +3503,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, requestSelectMessagePollOptions: { _, _ in }, requestOpenMessagePollResults: { _, _ in }, openAppStorePage: { - }, displayMessageTooltip: { _, _, _, _ in + }, displayMessageTooltip: { _, _, _, _, _ in }, seekToTimecode: { _, _, _ in - }, scheduleCurrentMessage: { + }, scheduleCurrentMessage: { _ in }, sendScheduledMessagesNow: { _ in }, editScheduledMessagesTime: { _ in }, performTextSelectionAction: { _, _, _, _ in @@ -3486,7 +3536,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in }, openWebView: { _, _, _, _ in - }, activateAdAction: { _ in + }, activateAdAction: { _, _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { @@ -3495,7 +3545,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, openPremiumStatusInfo: { _, _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in }, openGroupBoostInfo: { _, _ in - }, openStickerEditor: { + }, openStickerEditor: { + }, openPhoneContextMenu: { _ in + }, openAgeRestrictedMessageMedia: { _, _ in + }, playMessageEffect: { _ in + }, editMessageFactCheck: { _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { @@ -4325,7 +4379,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 @@ -4421,7 +4475,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.controller?.present(emojiStatusSelectionController, in: .window(.root)) } } else { - screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder) + if peerId == context.account.peerId { + self.privacySettings.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init))) + } else { + self.privacySettings.set(.single(nil)) + } + + screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, privacySettings: self.privacySettings.get()) var previousTimestamp: Double? self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFileAndPack, white in @@ -5207,27 +5267,28 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } switch navigation { - case let .chat(inputState, subject, peekData): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: subject, updateTextInputState: inputState, activateInput: inputState != nil ? .text : nil, keepStack: .always, peekData: peekData)) - case .info: - if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { - strongSelf.controller?.push(infoController) - } - } - case let .withBotStartPayload(startPayload): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botStart: startPayload, keepStack: .always)) - case let .withAttachBot(attachBotStart): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), attachBotStart: attachBotStart)) - case let .withBotApp(botAppStart): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botAppStart: botAppStart)) - default: - break + case let .chat(inputState, subject, peekData): + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: subject, updateTextInputState: inputState, activateInput: inputState != nil ? .text : nil, keepStack: .always, peekData: peekData)) + case .info: + if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + strongSelf.controller?.push(infoController) + } } - }, sendFile: nil, - sendSticker: { _, _, _ in - return false - }, requestMessageActionUrlAuth: nil, + case let .withBotStartPayload(startPayload): + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botStart: startPayload, keepStack: .always)) + case let .withAttachBot(attachBotStart): + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), attachBotStart: attachBotStart)) + case let .withBotApp(botAppStart): + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botAppStart: botAppStart)) + default: + break + } + }, + sendFile: nil, + sendSticker: nil, + sendEmoji: nil, + requestMessageActionUrlAuth: nil, joinVoiceChat: { peerId, invite, call in }, present: { [weak self] c, a in @@ -5252,6 +5313,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro commit() }, sendFile: nil, sendSticker: nil, + sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: { peerId, invite, call in @@ -5609,7 +5671,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in - c.popItems() + c?.popItems() }))) subItems.append(.separator) @@ -5640,7 +5702,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self?.openCustomMute() }))) - c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) }))) items.append(.separator) @@ -6152,7 +6214,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in - c.popItems() + c?.popItems() }))) subItems.append(.separator) @@ -6200,15 +6262,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let self else { return } - self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in + self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in guard let self else { return } self.controller?.view.endEditing(true) }, contentContext: nil, progress: nil, completion: nil) - }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + }, action: nil as ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) - c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) }))) } @@ -6217,7 +6279,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: user, chatPeer: user) + if let c { + self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: user, chatPeer: user) + } }))) } @@ -6352,7 +6416,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in - c.popItems() + c?.popItems() }))) subItems.append(.separator) @@ -6407,15 +6471,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let self else { return } - self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in + self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in guard let self else { return } self.controller?.view.endEditing(true) }, contentContext: nil, progress: nil, completion: nil) - }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + }, action: nil as ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) - c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) }))) } @@ -6424,7 +6488,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: channel, chatPeer: channel) + if let c { + self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: channel, chatPeer: channel) + } }))) } @@ -6487,7 +6553,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in - c.popItems() + c?.popItems() }))) subItems.append(.separator) @@ -6535,15 +6601,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let self else { return } - self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in + self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in guard let self else { return } self.controller?.view.endEditing(true) }, contentContext: nil, progress: nil, completion: nil) - }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + }, action: nil as ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) - c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) }))) } @@ -6573,7 +6639,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: group, chatPeer: group) + if let c { + self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: group, chatPeer: group) + } }))) } @@ -6849,7 +6917,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro subItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in - c.popItems() + c?.popItems() }))) subItems.append(.separator) @@ -6867,7 +6935,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro subItems.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil - }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + }, action: nil as ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) let beginClear: (InteractiveHistoryClearingType) -> Void = { [weak self] type in guard let strongSelf = self else { @@ -7204,7 +7272,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if self.isMyProfile { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_UsernameActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss { + c?.dismiss { guard let self else { return } @@ -7214,7 +7282,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_UsernameActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { + c?.dismiss { copyAction() } }))) @@ -7226,82 +7294,118 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } private func openBioContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { - guard let sourceNode = node as? ContextExtractedContentContainingNode else { - return - } - guard let cachedData = self.data?.cachedData else { - return - } - - var bioText: String? - if let cachedData = cachedData as? CachedUserData { - bioText = cachedData.about - } else if let cachedData = cachedData as? CachedChannelData { - bioText = cachedData.about - } else if let cachedData = cachedData as? CachedGroupData { - bioText = cachedData.about - } - - guard let bioText, !bioText.isEmpty else { - return - } - - let copyAction = { [weak self] in + let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] sharedData in guard let self else { return } - UIPasteboard.general.string = bioText - let toastText: String - if let _ = self.data?.peer as? TelegramUser { - toastText = self.presentationData.strings.MyProfile_ToastBioCopied + let translationSettings: TranslationSettings + if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { + translationSettings = current } else { - toastText = self.presentationData.strings.ChannelProfile_ToastAboutCopied + translationSettings = TranslationSettings.defaultSettings } - self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: toastText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - - var items: [ContextMenuItem] = [] - - if self.isMyProfile { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_BioActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss { - guard let self else { - return - } - self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) - - for (_, section) in self.editingSections { - for (id, itemNode) in section.itemNodes { - if id == AnyHashable("bio_edit") { - if let itemNode = itemNode as? PeerInfoScreenMultilineInputItemNode { - itemNode.focus() + guard let sourceNode = node as? ContextExtractedContentContainingNode else { + return + } + guard let cachedData = self.data?.cachedData else { + return + } + + var bioText: String? + if let cachedData = cachedData as? CachedUserData { + bioText = cachedData.about + } else if let cachedData = cachedData as? CachedChannelData { + bioText = cachedData.about + } else if let cachedData = cachedData as? CachedGroupData { + bioText = cachedData.about + } + + guard let bioText, !bioText.isEmpty else { + return + } + + let copyAction = { [weak self] in + guard let self else { + return + } + UIPasteboard.general.string = bioText + + let toastText: String + if let _ = self.data?.peer as? TelegramUser { + toastText = self.presentationData.strings.MyProfile_ToastBioCopied + } else { + toastText = self.presentationData.strings.ChannelProfile_ToastAboutCopied + } + + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: toastText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + + var items: [ContextMenuItem] = [] + + if self.isMyProfile { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_BioActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) + + for (_, section) in self.editingSections { + for (id, itemNode) in section.itemNodes { + if id == AnyHashable("bio_edit") { + if let itemNode = itemNode as? PeerInfoScreenMultilineInputItemNode { + itemNode.focus() + } + break } - break } } } + }))) + } + + let copyText: String + if let _ = self.data?.peer as? TelegramUser { + copyText = self.presentationData.strings.MyProfile_BioActionCopy + } else { + copyText = self.presentationData.strings.ChannelProfile_AboutActionCopy + } + items.append(.action(ContextMenuActionItem(text: copyText, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + copyAction() } }))) - } - - let copyText: String - if let _ = self.data?.peer as? TelegramUser { - copyText = self.presentationData.strings.MyProfile_BioActionCopy - } else { - copyText = self.presentationData.strings.ChannelProfile_AboutActionCopy - } - items.append(.action(ContextMenuActionItem(text: copyText, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { - copyAction() + + let (canTranslate, language) = canTranslateText(context: self.context, text: bioText, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages) + if canTranslate { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + + let controller = TranslateScreen(context: self.context, text: bioText, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) + controller.pushController = { [weak self] c in + (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true + self?.controller?.push(c) + } + controller.presentController = { [weak self] c in + self?.controller?.present(c, in: .window(.root)) + } + self.controller?.present(controller, in: .window(.root)) + } + }))) } - }))) - - let actions = ContextController.Items(content: .list(items)) - - let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) - self.controller?.present(contextController, in: .window(.root)) + + let actions = ContextController.Items(content: .list(items)) + + let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) + self.controller?.present(contextController, in: .window(.root)) + }) } private func openWorkingHoursContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { @@ -7334,7 +7438,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if self.isMyProfile { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss { + c?.dismiss { guard let self else { return } @@ -7345,7 +7449,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { + c?.dismiss { copyAction() } }))) @@ -7367,14 +7471,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro action: noAction ))) subItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursRemoveConfirmation_Action, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, _ in - c.dismiss { + c?.dismiss { guard let self else { return } let _ = self.context.engine.accountData.updateAccountBusinessHours(businessHours: nil).startStandalone() } }))) - c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) }))) } @@ -7414,7 +7518,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if businessLocation.coordinates != nil { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionOpen, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LocationSmall"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let self else { return } @@ -7425,7 +7529,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if !businessLocation.address.isEmpty { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { + c?.dismiss { copyAction() } }))) @@ -7433,7 +7537,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if self.isMyProfile { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss { + c?.dismiss { guard let self else { return } @@ -7457,14 +7561,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro action: noAction ))) subItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationRemoveConfirmation_Action, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, _ in - c.dismiss { + c?.dismiss { guard let self else { return } let _ = self.context.engine.accountData.updateAccountBusinessLocation(businessLocation: nil).startStandalone() } }))) - c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) }))) } @@ -7507,7 +7611,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if self.isMyProfile { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_BirthdayActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss { + c?.dismiss { guard let self else { return } @@ -7519,7 +7623,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_BirthdayActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { + c?.dismiss { copyAction() } }))) @@ -7606,7 +7710,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if strongSelf.isMyProfile { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.MyProfile_PhoneActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss { + c?.dismiss { guard let self else { return } @@ -7618,12 +7722,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if case let .user(peer) = peer, let peerPhoneNumber = peer.phone, formattedPhoneNumber == formatPhoneNumber(context: strongSelf.context, number: peerPhoneNumber) { if !strongSelf.isMyProfile { items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_TelegramCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { + c?.dismiss { telegramCallAction(false) } }))) items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_TelegramVideoCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoCall"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { + c?.dismiss { telegramCallAction(true) } }))) @@ -7631,7 +7735,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if !formattedPhoneNumber.hasPrefix("+888") { if !strongSelf.isMyProfile { items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_PhoneCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PhoneCall"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { + c?.dismiss { phoneCallAction() } }))) @@ -7640,7 +7744,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro isAnonymousNumber = true } items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.MyProfile_PhoneActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { + c?.dismiss { copyAction() } }))) @@ -7649,7 +7753,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if !strongSelf.isMyProfile { items.append( .action(ContextMenuActionItem(text: presentationData.strings.UserInfo_PhoneCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PhoneCall"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { + c?.dismiss { phoneCallAction() } })) @@ -7660,7 +7764,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } items.append( .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.MyProfile_PhoneActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss { + c?.dismiss { copyAction() } })) @@ -8217,7 +8321,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) }, iconPosition: .left, action: { (c, _) in - if let backAction = backAction { + if let c, let backAction = backAction { backAction(c) } }))) @@ -8394,8 +8498,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } self.context.sharedContext.openResolvedUrl(.groupBotStart(peerId: peerId, payload: "", adminRights: nil), context: self.context, urlContext: .generic, navigationController: controller.navigationController as? NavigationController, forceExternal: false, openPeer: { id, navigation in - }, sendFile: nil, + }, + sendFile: nil, sendSticker: nil, + sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, a in @@ -9554,7 +9660,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if (isPremium()) { self.controller?.push(premiumController(context: self.context)) } else { - PremiumUITgHelper.routeToPremium() + PremiumUITgHelper.routeToPremium( + source: .settings + ) } } @@ -10264,6 +10372,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)) + } } } @@ -10307,7 +10419,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro resolvedUrl = .instantView(webPage, customAnchor) } strongSelf.context.sharedContext.openResolvedUrl(resolvedUrl, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { peer, navigation in - }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak self] controller, arguments in + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak self] controller, arguments in self?.controller?.push(controller) }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) } @@ -10481,7 +10593,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro func forwardMessages(messageIds: Set?) { if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty { let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, filter: [.onlyWriteable, .excludeDisabled], hasFilters: true, multipleSelection: true, selectForumThreads: true)) - peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in + peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in guard let strongSelf = self, let strongController = peerSelectionController else { return } @@ -12019,7 +12131,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro hasBirthdayToday = true } - if hasBirthdayToday, let age = ageForBirthday(birthday), age > 0 { + if hasBirthdayToday { Queue.mainQueue().after(0.3) { var birthdayItemFrame: CGRect? if let section = self.regularSections[InfoSection.peerInfo] { @@ -12100,6 +12212,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) @@ -12178,6 +12291,12 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc self.chatLocation = .peer(id: peerId) } + if isSettings { + self.starsContext = context.starsContext + } else { + self.starsContext = nil + } + self.presentationData = updatedPresentationData?.0 ?? context.sharedContext.currentPresentationData.with { $0 } let baseNavigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) @@ -12504,7 +12623,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/PeerInfo/PeerInfoStoryGridScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD index 5b58e2600aa..a41fa15c29c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift index 4f810d6f336..290b7a26204 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift @@ -456,7 +456,6 @@ final class PeerInfoStoryGridScreenComponent: Component { paneNode = PeerInfoStoryPaneNode( context: component.context, peerId: component.peerId, - chatLocation: .peer(id: component.peerId), contentType: .photoOrVideo, captureProtected: false, isSaved: true, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift new file mode 100644 index 00000000000..7a8cde1f26c --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift @@ -0,0 +1,261 @@ +import Foundation +import AsyncDisplayKit +import Display +import SwiftSignalKit +import TelegramPresentationData +import AccountContext +import ComponentFlow +import TelegramCore +import PeerInfoVisualMediaPaneNode +import ViewControllerComponent +import ChatListHeaderComponent +import ContextUI +import ChatTitleView +import BottomButtonPanelComponent +import UndoUI +import MoreHeaderButton +import MediaEditorScreen +import SaveToCameraRoll + +final class StorySearchGridScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let searchQuery: String + + init( + context: AccountContext, + searchQuery: String + ) { + self.context = context + self.searchQuery = searchQuery + } + + static func ==(lhs: StorySearchGridScreenComponent, rhs: StorySearchGridScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.searchQuery != rhs.searchQuery { + return false + } + + return true + } + + final class View: UIView { + private var component: StorySearchGridScreenComponent? + private(set) weak var state: EmptyComponentState? + private var environment: EnvironmentType? + + private(set) var paneNode: PeerInfoStoryPaneNode? + private var paneStatusDisposable: Disposable? + private(set) var paneStatusText: String? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.paneStatusDisposable?.dispose() + } + + func scrollToTop() { + guard let paneNode = self.paneNode else { + return + } + let _ = paneNode.scrollToTop() + } + + private var isUpdating = false + func update(component: StorySearchGridScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + self.component = component + self.state = state + + let sideInset: CGFloat = 14.0 + let _ = sideInset + + let environment = environment[EnvironmentType.self].value + + let themeUpdated = self.environment?.theme !== environment.theme + + self.environment = environment + + if themeUpdated { + self.backgroundColor = environment.theme.list.plainBackgroundColor + } + + let bottomInset: CGFloat = environment.safeInsets.bottom + + let paneNode: PeerInfoStoryPaneNode + if let current = self.paneNode { + paneNode = current + } else { + paneNode = PeerInfoStoryPaneNode( + context: component.context, + peerId: nil, + searchQuery: component.searchQuery, + contentType: .photoOrVideo, + captureProtected: false, + isSaved: false, + isArchive: false, + isProfileEmbedded: false, + canManageStories: false, + navigationController: { [weak self] in + guard let self else { + return nil + } + return self.environment?.controller()?.navigationController as? NavigationController + }, + listContext: nil + ) + paneNode.isEmptyUpdated = { [weak self] _ in + guard let self else { + return + } + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + } + self.paneNode = paneNode + self.addSubview(paneNode.view) + + self.paneStatusDisposable = (paneNode.status + |> deliverOnMainQueue).start(next: { [weak self] status in + guard let self else { + return + } + if self.paneStatusText != status?.text { + self.paneStatusText = status?.text + (self.environment?.controller() as? StorySearchGridScreen)?.updateTitle() + } + }) + } + + paneNode.update( + size: availableSize, + topInset: environment.navigationHeight, + sideInset: environment.safeInsets.left, + bottomInset: bottomInset, + deviceMetrics: environment.deviceMetrics, + visibleHeight: availableSize.height, + isScrollingLockedAtTop: false, + expandProgress: 1.0, + navigationHeight: 0.0, + presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }), + synchronous: false, + transition: transition.containedViewLayoutTransition + ) + transition.setFrame(view: paneNode.view, frame: CGRect(origin: CGPoint(), size: availableSize)) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + 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 class StorySearchGridScreen: ViewControllerComponentContainer { + private let context: AccountContext + private let searchQuery: String + private var isDismissed: Bool = false + + private var titleView: ChatTitleView? + + public init( + context: AccountContext, + searchQuery: String + ) { + self.context = context + self.searchQuery = searchQuery + + super.init(context: context, component: StorySearchGridScreenComponent( + context: context, + searchQuery: searchQuery + ), navigationBarAppearance: .default, theme: .default) + + let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + + self.titleView = ChatTitleView( + context: context, theme: + presentationData.theme, + strings: presentationData.strings, + dateTimeFormat: presentationData.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer + ) + self.titleView?.disableAnimations = true + + self.navigationItem.titleView = self.titleView + + self.updateTitle() + + self.scrollToTop = { [weak self] in + guard let self, let componentView = self.node.hostView.componentView as? StorySearchGridScreenComponent.View else { + return + } + componentView.scrollToTop() + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func updateTitle() { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let _ = presentationData + + guard let componentView = self.node.hostView.componentView as? StorySearchGridScreenComponent.View, let paneNode = componentView.paneNode else { + return + } + let _ = paneNode + + let title: String? + if let paneStatusText = componentView.paneStatusText, !paneStatusText.isEmpty { + title = paneStatusText + } else { + title = nil + } + //TODO:localize + self.titleView?.titleContent = .custom("\(self.searchQuery)", title, false) + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.titleView?.layout = layout + } +} + +private final class PeerInfoContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceNode: ContextReferenceContentNode + + init(controller: ViewController, sourceNode: ContextReferenceContentNode) { + self.controller = controller + self.sourceNode = sourceNode + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD index f7b3a2de84b..b3c0e5015eb 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", @@ -43,6 +43,7 @@ swift_library( "//submodules/UndoUI", "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/MediaEditorScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index a0f7854d508..3a5051bada6 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -33,6 +33,8 @@ import ShareController import UndoUI import PlainButtonComponent import ComponentDisplayAdapters +import MediaEditorScreen +import AvatarNode private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) private let mediaBadgeTextColor = UIColor.white @@ -87,6 +89,7 @@ private final class VisualMediaItem: SparseItemGrid.Item { let localMonthTimestamp: Int32 let peer: PeerReference let story: EngineStoryItem + let authorPeer: EnginePeer? let isPinned: Bool override var id: AnyHashable { @@ -101,10 +104,11 @@ private final class VisualMediaItem: SparseItemGrid.Item { return VisualMediaHoleAnchor(index: self.index, storyId: self.story.id, localMonthTimestamp: self.localMonthTimestamp) } - init(index: Int, peer: PeerReference, story: EngineStoryItem, isPinned: Bool, localMonthTimestamp: Int32) { + init(index: Int, peer: PeerReference, story: EngineStoryItem, authorPeer: EnginePeer?, isPinned: Bool, localMonthTimestamp: Int32) { self.indexValue = index self.peer = peer self.story = story + self.authorPeer = authorPeer self.isPinned = isPinned self.localMonthTimestamp = localMonthTimestamp } @@ -141,6 +145,10 @@ private let durationFont: UIFont = { Font.semibold(11.0) }() +private let avatarFont: UIFont = { + avatarPlaceholderFont(size: 10.0) +}() + private let minDurationImage: UIImage = { let image = generateImage(CGSize(width: 20.0, height: 20.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -199,6 +207,22 @@ private let topRightShadowImage: UIImage = { return image! }() +private let topLeftShadowImage: UIImage = { + let baseImage = UIImage(bundleImageName: "Peer Info/MediaGridShadow")! + let image = generateImage(baseImage.size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: -1.0, y: -1.0) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + UIGraphicsPushContext(context) + baseImage.draw(in: CGRect(origin: CGPoint(), size: size)) + UIGraphicsPopContext() + }) + return image! +}() + private let viewCountImage: UIImage = { let baseImage = UIImage(bundleImageName: "Peer Info/MediaGridViewCount")! let image = generateImage(baseImage.size, rotatedContext: { size, context in @@ -274,7 +298,11 @@ private enum ItemTopRightIcon { case pinned } -private final class DurationLayer: CALayer { +private final class DurationLayer: SimpleLayer { + private var authorPeerId: EnginePeer.Id? + private var avatarLayer: SimpleLayer? + private var disposable: Disposable? + override init() { super.init() @@ -285,6 +313,10 @@ private final class DurationLayer: CALayer { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + deinit { + self.disposable?.dispose() + } override func action(forKey event: String) -> CAAction? { return nullAction @@ -384,34 +416,97 @@ private final class DurationLayer: CALayer { self.contents = image?.cgImage } } -} - -private protocol ItemLayer: SparseItemGridLayer { - var item: VisualMediaItem? { get set } - var durationLayer: DurationLayer? { get set } - var minFactor: CGFloat { get set } - var selectionLayer: GridMessageSelectionLayer? { get set } - var disposable: Disposable? { get set } - - var hasContents: Bool { get set } - func setSpoilerContents(_ contents: Any?) - func updateDuration(viewCount: Int32?, duration: Int32?, topRightIcon: ItemTopRightIcon?, isMin: Bool, minFactor: CGFloat) - func updateSelection(theme: CheckNodeTheme, isSelected: Bool?, animated: Bool) - func updateHasSpoiler(hasSpoiler: Bool) + func copyAuthor(from other: DurationLayer) { + self.contents = other.contents + + let avatarLayer: SimpleLayer + if let current = self.avatarLayer { + avatarLayer = current + } else { + avatarLayer = SimpleLayer() + self.avatarLayer = avatarLayer + self.addSublayer(avatarLayer) + + avatarLayer.frame = CGRect(origin: CGPoint(x: -11.0, y: 2.0), size: CGSize(width: 13.0, height: 13.0)) + avatarLayer.cornerRadius = 13.0 * 0.5 + avatarLayer.masksToBounds = true + } + avatarLayer.contents = other.avatarLayer?.contents + } - func bind(item: VisualMediaItem) - func unbind() + func update(directMediaImageCache: DirectMediaImageCache, author: EnginePeer, synchronous: SparseItemGrid.Synchronous) { + let avatarLayer: SimpleLayer + if let current = self.avatarLayer { + avatarLayer = current + } else { + avatarLayer = SimpleLayer() + self.avatarLayer = avatarLayer + self.addSublayer(avatarLayer) + + avatarLayer.frame = CGRect(origin: CGPoint(x: -11.0, y: 2.0), size: CGSize(width: 13.0, height: 13.0)) + avatarLayer.cornerRadius = 13.0 * 0.5 + avatarLayer.masksToBounds = true + } + + if self.authorPeerId != author.id { + let string = NSAttributedString(string: author.debugDisplayTitle, font: durationFont, textColor: .white) + let bounds = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + let textSize = CGSize(width: ceil(bounds.width), height: ceil(bounds.height)) + let sideInset: CGFloat = 6.0 + let verticalInset: CGFloat = 2.0 + let image = generateImage(CGSize(width: textSize.width + sideInset * 2.0, height: textSize.height + verticalInset * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setBlendMode(.normal) + + context.setShadow(offset: CGSize(width: 0.0, height: 0.0), blur: 2.5, color: UIColor(rgb: 0x000000, alpha: 0.22).cgColor) + + UIGraphicsPushContext(context) + string.draw(in: bounds.offsetBy(dx: sideInset, dy: verticalInset)) + UIGraphicsPopContext() + }) + self.contents = image?.cgImage + + if let smallProfileImage = author.smallProfileImage, let peerReference = PeerReference(author._asPeer()) { + if let result = directMediaImageCache.getAvatarImage(peer: peerReference, resource: MediaResourceReference.avatar(peer: peerReference, resource: smallProfileImage.resource), immediateThumbnail: smallProfileImage.immediateThumbnailData, size: 24, includeBlurred: true, synchronous: synchronous == .full) { + if let image = result.image { + avatarLayer.contents = image.cgImage + } else if let image = result.blurredImage { + avatarLayer.contents = image.cgImage + } + if let loadSignal = result.loadSignal { + self.disposable?.dispose() + self.disposable = (loadSignal + |> deliverOnMainQueue).start(next: { [weak self] image in + guard let self else { + return + } + self.avatarLayer?.contents = image?.cgImage + }) + } + } + } else { + self.avatarLayer?.contents = generateImage(CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + drawPeerAvatarLetters(context: context, size: size, font: avatarFont, letters: author.displayLetters, peerId: author.id, nameColor: author.nameColor) + })?.cgImage + } + } + } } -private final class GenericItemLayer: CALayer, ItemLayer { +private final class ItemLayer: CALayer, SparseItemGridLayer { var item: VisualMediaItem? var viewCountLayer: DurationLayer? var durationLayer: DurationLayer? var privacyTypeLayer: DurationLayer? + var authorLayer: DurationLayer? var leftShadowLayer: SimpleLayer? var rightShadowLayer: SimpleLayer? var topRightShadowLayer: SimpleLayer? + var topLeftShadowLayer: SimpleLayer? var minFactor: CGFloat = 1.0 var selectionLayer: GridMessageSelectionLayer? var dustLayer: MediaDustLayer? @@ -457,7 +552,7 @@ private final class GenericItemLayer: CALayer, ItemLayer { self.item = item } - func updateDuration(viewCount: Int32?, duration: Int32?, topRightIcon: ItemTopRightIcon?, isMin: Bool, minFactor: CGFloat) { + func updateDuration(viewCount: Int32?, duration: Int32?, topRightIcon: ItemTopRightIcon?, author: EnginePeer?, isMin: Bool, minFactor: CGFloat, directMediaImageCache: DirectMediaImageCache, synchronous: SparseItemGrid.Synchronous) { self.minFactor = minFactor if let viewCount { @@ -510,6 +605,23 @@ private final class GenericItemLayer: CALayer, ItemLayer { privacyTypeLayer.removeFromSuperlayer() } + if let author { + if let authorLayer = self.authorLayer { + authorLayer.update(directMediaImageCache: directMediaImageCache, author: author, synchronous: synchronous) + } else { + let authorLayer = DurationLayer() + authorLayer.contentsGravity = .bottomLeft + authorLayer.update(directMediaImageCache: directMediaImageCache, author: author, synchronous: synchronous) + self.addSublayer(authorLayer) + authorLayer.frame = CGRect(origin: CGPoint(x: 17.0, y: 3.0), size: CGSize()) + authorLayer.transform = CATransform3DMakeScale(minFactor, minFactor, 1.0) + self.authorLayer = authorLayer + } + } else if let authorLayer = self.authorLayer { + self.authorLayer = nil + authorLayer.removeFromSuperlayer() + } + let size = self.bounds.size if self.viewCountLayer != nil { @@ -559,6 +671,22 @@ private final class GenericItemLayer: CALayer, ItemLayer { topRightShadowLayer.removeFromSuperlayer() } } + + if self.authorLayer != nil { + if self.topLeftShadowLayer == nil { + let topLeftShadowLayer = SimpleLayer() + self.topLeftShadowLayer = topLeftShadowLayer + self.insertSublayer(topLeftShadowLayer, at: 0) + topLeftShadowLayer.contents = topLeftShadowImage.cgImage + let shadowSize = CGSize(width: min(size.width, topLeftShadowImage.size.width), height: min(size.height, topLeftShadowImage.size.height)) + topLeftShadowLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: shadowSize) + } + } else { + if let topLeftShadowLayer = self.topLeftShadowLayer { + self.topLeftShadowLayer = nil + topLeftShadowLayer.removeFromSuperlayer() + } + } } func updateSelection(theme: CheckNodeTheme, isSelected: Bool?, animated: Bool) { @@ -597,6 +725,15 @@ private final class GenericItemLayer: CALayer, ItemLayer { privacyTypeLayer.animateAlpha(from: CGFloat(previousAlpha), to: CGFloat(privacyAlpha), duration: 0.2) } } + + if let authorLayer = self.authorLayer { + let authorAlpha: Float = isSelected == nil ? 1.0 : 0.0 + if authorAlpha != authorLayer.opacity { + let previousAlpha = authorLayer.opacity + authorLayer.opacity = authorAlpha + authorLayer.animateAlpha(from: CGFloat(previousAlpha), to: CGFloat(authorAlpha), duration: 0.2) + } + } } func updateHasSpoiler(hasSpoiler: Bool) { @@ -635,6 +772,9 @@ private final class GenericItemLayer: CALayer, ItemLayer { if let privacyTypeLayer = self.privacyTypeLayer { privacyTypeLayer.frame = CGRect(origin: CGPoint(x: size.width - 2.0, y: 3.0), size: CGSize()) } + if let authorLayer = self.authorLayer { + authorLayer.frame = CGRect(origin: CGPoint(x: 17.0, y: 3.0), size: CGSize()) + } if let leftShadowLayer = self.leftShadowLayer { let shadowSize = CGSize(width: min(size.width, leftShadowImage.size.width), height: min(size.height, leftShadowImage.size.height)) @@ -651,6 +791,11 @@ private final class GenericItemLayer: CALayer, ItemLayer { topRightShadowLayer.frame = CGRect(origin: CGPoint(x: size.width - shadowSize.width, y: 0.0), size: shadowSize) } + if let topLeftShadowLayer = self.topLeftShadowLayer { + let shadowSize = CGSize(width: min(size.width, topLeftShadowImage.size.width), height: min(size.height, topLeftShadowImage.size.height)) + topLeftShadowLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: shadowSize) + } + if let binding = binding as? SparseItemGridBindingImpl, let item = item as? VisualMediaItem, let previousItem = self.item { if previousItem.story.media.id != item.story.media.id { binding.bindLayers(items: [item], layers: [displayItem], size: size, insets: insets, synchronous: .none) @@ -664,7 +809,7 @@ private final class GenericItemLayer: CALayer, ItemLayer { } if let selectedMedia { - binding.updateLayerData(story: item.story, item: item, selectedMedia: selectedMedia, layer: layer) + binding.updateLayerData(story: item.story, item: item, selectedMedia: selectedMedia, layer: layer, synchronous: .none) } } } @@ -677,13 +822,16 @@ private final class ItemTransitionView: UIView { private var copyDurationLayer: SimpleLayer? private var copyViewCountLayer: SimpleLayer? private var copyPrivacyTypeLayer: SimpleLayer? + private var copyAuthorLayer: SimpleLayer? private var copyLeftShadowLayer: SimpleLayer? private var copyRightShadowLayer: SimpleLayer? private var copyTopRightShadowLayer: SimpleLayer? + private var copyTopLeftShadowLayer: SimpleLayer? private var viewCountLayerBottomLeftPosition: CGPoint? private var durationLayerBottomLeftPosition: CGPoint? private var privacyTypeLayerTopRightPosition: CGPoint? + private var authorLayerTopLeftPosition: CGPoint? var selectionLayer: GridMessageSelectionLayer? @@ -698,16 +846,20 @@ private final class ItemTransitionView: UIView { var viewCountLayer: CALayer? var durationLayer: CALayer? var privacyTypeLayer: CALayer? + var authorLayer: CALayer? var leftShadowLayer: CALayer? var rightShadowLayer: CALayer? var topRightShadowLayer: CALayer? - if let itemLayer = itemLayer as? GenericItemLayer { + var topLeftShadowLayer: CALayer? + if let itemLayer = itemLayer as? ItemLayer { viewCountLayer = itemLayer.viewCountLayer durationLayer = itemLayer.durationLayer privacyTypeLayer = itemLayer.privacyTypeLayer + authorLayer = itemLayer.authorLayer leftShadowLayer = itemLayer.leftShadowLayer rightShadowLayer = itemLayer.rightShadowLayer topRightShadowLayer = itemLayer.topRightShadowLayer + topLeftShadowLayer = itemLayer.topLeftShadowLayer self.layer.contents = itemLayer.contents } @@ -744,6 +896,17 @@ private final class ItemTransitionView: UIView { self.copyTopRightShadowLayer = copyLayer } + if let topLeftShadowLayer { + let copyLayer = SimpleLayer() + copyLayer.contents = topLeftShadowLayer.contents + copyLayer.contentsRect = topLeftShadowLayer.contentsRect + copyLayer.contentsGravity = topLeftShadowLayer.contentsGravity + copyLayer.contentsScale = topLeftShadowLayer.contentsScale + copyLayer.frame = topLeftShadowLayer.frame + self.layer.addSublayer(copyLayer) + self.copyTopLeftShadowLayer = copyLayer + } + if let viewCountLayer { let copyViewCountLayer = SimpleLayer() copyViewCountLayer.contents = viewCountLayer.contents @@ -770,6 +933,19 @@ private final class ItemTransitionView: UIView { self.privacyTypeLayerTopRightPosition = CGPoint(x: itemLayer.bounds.width - privacyTypeLayer.frame.maxX, y: privacyTypeLayer.frame.minY) } + if let authorLayer = authorLayer as? DurationLayer { + let copyAuthorLayer = DurationLayer() + copyAuthorLayer.contentsRect = authorLayer.contentsRect + copyAuthorLayer.contentsGravity = authorLayer.contentsGravity + copyAuthorLayer.contentsScale = authorLayer.contentsScale + copyAuthorLayer.frame = authorLayer.frame + copyAuthorLayer.copyAuthor(from: authorLayer) + self.layer.addSublayer(copyAuthorLayer) + self.copyAuthorLayer = copyAuthorLayer + + self.authorLayerTopLeftPosition = CGPoint(x: authorLayer.frame.minX, y: authorLayer.frame.minY) + } + if let durationLayer { let copyDurationLayer = SimpleLayer() copyDurationLayer.contents = durationLayer.contents @@ -804,6 +980,10 @@ private final class ItemTransitionView: UIView { transition.setFrame(layer: privacyTypeLayer, frame: CGRect(origin: CGPoint(x: size.width - privacyTypeLayerTopRightPosition.x, y: privacyTypeLayerTopRightPosition.y), size: privacyTypeLayer.bounds.size)) } + if let authorLayer = self.copyAuthorLayer, let authorLayerTopLeftPosition = self.authorLayerTopLeftPosition { + transition.setFrame(layer: authorLayer, frame: CGRect(origin: CGPoint(x: authorLayerTopLeftPosition.x, y: authorLayerTopLeftPosition.y), size: authorLayer.bounds.size)) + } + if let copyLeftShadowLayer = self.copyLeftShadowLayer { transition.setFrame(layer: copyLeftShadowLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - copyLeftShadowLayer.bounds.height), size: copyLeftShadowLayer.bounds.size)) } @@ -815,6 +995,10 @@ private final class ItemTransitionView: UIView { if let copyTopRightShadowLayer = self.copyTopRightShadowLayer { transition.setFrame(layer: copyTopRightShadowLayer, frame: CGRect(origin: CGPoint(x: size.width - copyTopRightShadowLayer.bounds.width, y: 0.0), size: copyTopRightShadowLayer.bounds.size)) } + + if let copyTopLeftShadowLayer = self.copyTopLeftShadowLayer { + transition.setFrame(layer: copyTopLeftShadowLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: copyTopLeftShadowLayer.bounds.size)) + } } func updateSelection(theme: CheckNodeTheme, isSelected: Bool?, animated: Bool) { @@ -853,12 +1037,20 @@ private final class ItemTransitionView: UIView { copyPrivacyTypeLayer.animateAlpha(from: CGFloat(previousAlpha), to: CGFloat(privacyAlpha), duration: 0.2) } } + + if let copyAuthorLayer = self.copyAuthorLayer { + let privacyAlpha: Float = isSelected == nil ? 1.0 : 0.0 + if privacyAlpha != copyAuthorLayer.opacity { + let previousAlpha = copyAuthorLayer.opacity + copyAuthorLayer.opacity = privacyAlpha + copyAuthorLayer.animateAlpha(from: CGFloat(previousAlpha), to: CGFloat(privacyAlpha), duration: 0.2) + } + } } } private final class SparseItemGridBindingImpl: SparseItemGridBinding { let context: AccountContext - let chatLocation: ChatLocation let directMediaImageCache: DirectMediaImageCache let captureProtected: Bool let displayPrivacy: Bool @@ -880,9 +1072,8 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding { private var shimmerImages: [CGFloat: UIImage] = [:] - init(context: AccountContext, chatLocation: ChatLocation, directMediaImageCache: DirectMediaImageCache, captureProtected: Bool, displayPrivacy: Bool) { + init(context: AccountContext, directMediaImageCache: DirectMediaImageCache, captureProtected: Bool, displayPrivacy: Bool) { self.context = context - self.chatLocation = chatLocation self.directMediaImageCache = directMediaImageCache self.captureProtected = false self.displayPrivacy = displayPrivacy @@ -911,11 +1102,11 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding { func createLayer(item: SparseItemGrid.Item) -> SparseItemGridLayer? { if let item = item as? VisualMediaItem, item.story.isForwardingDisabled { - let layer = GenericItemLayer() + let layer = ItemLayer() setLayerDisableScreenshots(layer, true) return layer } else { - return GenericItemLayer() + return ItemLayer() } } @@ -1013,7 +1204,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding { } if let contents = layer.getContents(), !synchronousValue { - let copyLayer = GenericItemLayer() + let copyLayer = ItemLayer() copyLayer.contents = contents copyLayer.contentsRect = layer.contentsRect copyLayer.frame = layer.bounds @@ -1057,7 +1248,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding { } } - self.updateLayerData(story: story, item: item, selectedMedia: selectedMedia, layer: layer) + self.updateLayerData(story: story, item: item, selectedMedia: selectedMedia, layer: layer, synchronous: synchronous) } var isSelected: Bool? @@ -1070,7 +1261,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding { } } - func updateLayerData(story: EngineStoryItem, item: VisualMediaItem, selectedMedia: Media, layer: ItemLayer) { + func updateLayerData(story: EngineStoryItem, item: VisualMediaItem, selectedMedia: Media, layer: ItemLayer, synchronous: SparseItemGrid.Synchronous) { var viewCount: Int32? if let value = story.views?.seenCount { viewCount = Int32(value) @@ -1101,7 +1292,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding { isMin = layer.bounds.width < 80.0 } - layer.updateDuration(viewCount: viewCount, duration: duration, topRightIcon: topRightIcon, isMin: isMin, minFactor: min(1.0, layer.bounds.height / 74.0)) + layer.updateDuration(viewCount: viewCount, duration: duration, topRightIcon: topRightIcon, author: item.authorPeer, isMin: isMin, minFactor: min(1.0, layer.bounds.height / 74.0), directMediaImageCache: self.directMediaImageCache, synchronous: synchronous) } func unbindLayer(layer: SparseItemGridLayer) { @@ -1180,8 +1371,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } private let context: AccountContext - private let peerId: PeerId - private let chatLocation: ChatLocation + private let peerId: PeerId? + private let searchQuery: String? private let isSaved: Bool private let isArchive: Bool private let isProfileEmbedded: Bool @@ -1264,8 +1455,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr return self.itemGrid.coveringInsetOffset } - private let listDisposable = MetaDisposable() + private var listDisposable: Disposable? private var hiddenMediaDisposable: Disposable? + private let updateDisposable = MetaDisposable() private var numberOfItemsToRequest: Int = 50 private var isRequestingView: Bool = false @@ -1299,10 +1491,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr private weak var contextControllerToDismissOnSelection: ContextControllerProtocol? private weak var tempContextContentItemNode: TempExtractedItemNode? - public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, isProfileEmbedded: Bool, canManageStories: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) { + public init(context: AccountContext, peerId: PeerId?, searchQuery: String? = nil, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, isProfileEmbedded: Bool, canManageStories: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) { self.context = context self.peerId = peerId - self.chatLocation = chatLocation + self.searchQuery = searchQuery self.contentType = contentType self.contentTypePromise = ValuePromise(contentType) self.navigationController = navigationController @@ -1321,31 +1513,32 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.itemGridBinding = SparseItemGridBindingImpl( context: context, - chatLocation: .peer(id: peerId), directMediaImageCache: self.directMediaImageCache, captureProtected: captureProtected, displayPrivacy: isProfileEmbedded ) - self.listSource = listContext ?? PeerStoryListContext(account: context.account, peerId: peerId, isArchived: self.isArchive) + self.listSource = listContext ?? PeerStoryListContext(account: context.account, peerId: peerId ?? context.account.peerId, isArchived: self.isArchive) self.calendarSource = nil super.init() - let _ = (ApplicationSpecificNotice.getSharedMediaScrollingTooltip(accountManager: context.sharedContext.accountManager) - |> deliverOnMainQueue).start(next: { [weak self] count in - guard let strongSelf = self else { - return - } - if count < 1 { - strongSelf.itemGrid.updateScrollingAreaTooltip(tooltip: SparseItemGridScrollingArea.DisplayTooltip(animation: "anim_infotip", text: strongSelf.itemGridBinding.chatPresentationData.strings.SharedMedia_FastScrollTooltip, completed: { - guard let strongSelf = self else { - return - } - let _ = ApplicationSpecificNotice.incrementSharedMediaScrollingTooltip(accountManager: strongSelf.context.sharedContext.accountManager, count: 1).start() - })) - } - }) + if self.peerId != nil { + let _ = (ApplicationSpecificNotice.getSharedMediaScrollingTooltip(accountManager: context.sharedContext.accountManager) + |> deliverOnMainQueue).start(next: { [weak self] count in + guard let strongSelf = self else { + return + } + if count < 1 { + strongSelf.itemGrid.updateScrollingAreaTooltip(tooltip: SparseItemGridScrollingArea.DisplayTooltip(animation: "anim_infotip", text: strongSelf.itemGridBinding.chatPresentationData.strings.SharedMedia_FastScrollTooltip, completed: { + guard let strongSelf = self else { + return + } + let _ = ApplicationSpecificNotice.incrementSharedMediaScrollingTooltip(accountManager: strongSelf.context.sharedContext.accountManager, count: 1).start() + })) + } + }) + } self.itemGridBinding.loadHoleImpl = { [weak self] hole, location in guard let strongSelf = self else { @@ -1377,10 +1570,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr return } - //TODO:selection + //TODO:localize let listContext = PeerStoryListContentContextImpl( context: self.context, - peerId: self.peerId, + peerId: self.peerId ?? self.context.account.peerId, listContext: self.listSource, initialId: item.story.id ) @@ -1756,15 +1949,16 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: false) if peerId == context.account.peerId && !isArchive { - self.preloadArchiveListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: true) + self.preloadArchiveListContext = PeerStoryListContext(account: context.account, peerId: context.account.peerId, isArchived: true) } } deinit { - self.listDisposable.dispose() + self.listDisposable?.dispose() self.hiddenMediaDisposable?.dispose() self.animationTimer?.invalidate() self.presentationDataDisposable?.dispose() + self.updateDisposable.dispose() } public func loadHole(anchor: SparseItemGrid.HoleAnchor, at location: SparseItemGrid.HoleLocation) -> Signal { @@ -1783,197 +1977,227 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } private func openContextMenu(item: EngineStoryItem, itemLayer: ItemLayer, rect: CGRect, gesture: ContextGesture?) { - let _ = (self.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId) - ) - |> deliverOnMainQueue).start(next: { [weak self] _ in - guard let self else { - return - } - guard let parentController = self.parentController else { - return - } - - let canManage = self.canManageStories - - var items: [ContextMenuItem] = [] - - if canManage { - items.append(.action(ContextMenuActionItem(text: !self.isArchive ? self.presentationData.strings.StoryList_ItemAction_Archive : self.presentationData.strings.StoryList_ItemAction_Unarchive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: self.isArchive ? "Chat/Context Menu/Archive" : "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + guard let parentController = self.parentController else { + return + } + + let canManage = self.canManageStories + + var items: [ContextMenuItem] = [] + + if canManage, let peerId = self.peerId { + items.append(.action(ContextMenuActionItem(text: !self.isArchive ? self.presentationData.strings.StoryList_ItemAction_Archive : self.presentationData.strings.StoryList_ItemAction_Unarchive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: self.isArchive ? "Chat/Context Menu/Archive" : "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + guard let self else { + f(.default) + return + } + + if self.isArchive { + f(.default) + } else { + f(.dismissWithoutContent) + } + + let _ = self.context.engine.messages.updateStoriesArePinned(peerId: peerId, ids: [item.id: item], isPinned: self.isArchive ? true : false).startStandalone() + self.parentController?.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.isArchive ? self.presentationData.strings.StoryList_ToastUnarchived_Text(1) : self.presentationData.strings.StoryList_ToastArchived_Text(1), cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + }))) + + if !self.isArchive { + let isPinned = self.pinnedIds.contains(item.id) + items.append(.action(ContextMenuActionItem(text: isPinned ? self.presentationData.strings.StoryList_ItemAction_Unpin : self.presentationData.strings.StoryList_ItemAction_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak itemLayer] _, f in + itemLayer?.isHidden = false guard let self else { f(.default) return } - if self.isArchive { + if !isPinned && self.pinnedIds.count >= 3 { f(.default) + + let presentationData = self.presentationData + self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.StoryList_ToastPinLimit_Text(Int32(3)), timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + + return + } + + f(.dismissWithoutContent) + + var updatedPinnedIds = self.pinnedIds + if isPinned { + updatedPinnedIds.remove(item.id) } else { - f(.dismissWithoutContent) + updatedPinnedIds.insert(item.id) } + let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: peerId, ids: Array(updatedPinnedIds)).startStandalone() + + let presentationData = self.presentationData - let _ = self.context.engine.messages.updateStoriesArePinned(peerId: self.peerId, ids: [item.id: item], isPinned: self.isArchive ? true : false).startStandalone() - self.parentController?.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.isArchive ? self.presentationData.strings.StoryList_ToastUnarchived_Text(1) : self.presentationData.strings.StoryList_ToastArchived_Text(1), cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + let toastTitle: String? + let toastText: String + if isPinned { + toastTitle = nil + toastText = presentationData.strings.StoryList_ToastUnpinned_Text(1) + } else { + toastTitle = presentationData.strings.StoryList_ToastPinned_Title(1) + toastText = presentationData.strings.StoryList_ToastPinned_Text(1) + } + self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: isPinned ? "anim_toastunpin" : "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) }))) - - if !self.isArchive { - let isPinned = self.pinnedIds.contains(item.id) - items.append(.action(ContextMenuActionItem(text: isPinned ? self.presentationData.strings.StoryList_ItemAction_Unpin : self.presentationData.strings.StoryList_ItemAction_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak itemLayer] _, f in - itemLayer?.isHidden = false - guard let self else { - f(.default) - return - } - - if !isPinned && self.pinnedIds.count >= 3 { - f(.default) - - let presentationData = self.presentationData - self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.StoryList_ToastPinLimit_Text(Int32(3)), timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - - return - } - - f(.dismissWithoutContent) - - var updatedPinnedIds = self.pinnedIds - if isPinned { - updatedPinnedIds.remove(item.id) - } else { - updatedPinnedIds.insert(item.id) - } - let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: self.peerId, ids: Array(updatedPinnedIds)).startStandalone() - - let presentationData = self.presentationData - - let toastTitle: String? - let toastText: String - if isPinned { - toastTitle = nil - toastText = presentationData.strings.StoryList_ToastUnpinned_Text(1) - } else { - toastTitle = presentationData.strings.StoryList_ToastPinned_Title(1) - toastText = presentationData.strings.StoryList_ToastPinned_Text(1) - } - self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: isPinned ? "anim_toastunpin" : "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - }))) - } - - /*items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryList_ItemAction_Edit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { - guard let self else { - return - } - let _ = self - - - }) - })))*/ } - if !item.isForwardingDisabled, case .everyone = item.privacy?.base { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryList_ItemAction_Forward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { - guard let self else { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryList_ItemAction_Edit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss(completion: { + guard let self else { + return + } + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + guard let self, let peer else { return } - let _ = (self.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId) - ) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in - guard let self else { + var foundItemLayer: SparseItemGridLayer? + var sourceImage: UIImage? + self.itemGrid.forEachVisibleItem { gridItem in + guard let itemLayer = gridItem.layer as? ItemLayer else { return } - guard let peer, let peerReference = PeerReference(peer._asPeer()) else { - return + if let listItem = itemLayer.item, listItem.story.id == item.id { + foundItemLayer = itemLayer + if let contents = itemLayer.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID { + sourceImage = UIImage(cgImage: contents as! CGImage) + } } - - let shareController = ShareController( - context: self.context, - subject: .media(.story(peer: peerReference, id: item.id, media: TelegramMediaStory(storyId: StoryId(peerId: self.peerId, id: item.id), isMention: false))), - presetText: nil, - preferredAction: .default, - showInChat: nil, - fromForeignApp: false, - segmentedValues: nil, - externalShare: false, - immediateExternalShare: false, - switchableAccounts: [], - immediatePeerId: nil, - updatedPresentationData: nil, - forceTheme: nil, - forcedActionTitle: nil, - shareAsLink: false, - collectibleItemInfo: nil - ) - self.parentController?.present(shareController, in: .window(.root)) - }) - }) - }))) - } - - if canManage { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryList_ItemAction_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { - guard let self else { - return } - self.presentDeleteConfirmation(ids: Set([item.id])) + guard let controller = MediaEditorScreen.makeEditStoryController( + context: self.context, + peer: peer, + storyItem: item, + videoPlaybackPosition: nil, + repost: false, + transitionIn: .gallery(MediaEditorScreen.TransitionIn.GalleryTransitionIn(sourceView: self.itemGrid.view, sourceRect: foundItemLayer?.frame ?? .zero, sourceImage: sourceImage)), + transitionOut: MediaEditorScreen.TransitionOut(destinationView: self.itemGrid.view, destinationRect: foundItemLayer?.frame ?? .zero, destinationCornerRadius: 0.0), + update: { [weak self] disposable in + guard let self else { + return + } + self.updateDisposable.set(disposable) + } + ) else { + return + } + self.parentController?.push(controller) }) - }))) - } - - if self.canManageStories { - if !items.isEmpty { - items.append(.separator) - } - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.actionSheet.primaryTextColor) - }, action: { [weak self] c, f in - guard let self, let parentController = self.parentController as? PeerInfoScreen else { - f(.default) + }) + }))) + } + + if !item.isForwardingDisabled, case .everyone = item.privacy?.base { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryList_ItemAction_Forward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss(completion: { + guard let self, let peerId = self.peerId else { return } - self.contextControllerToDismissOnSelection = c - parentController.toggleStorySelection(ids: [item.id], isSelected: true) - - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5, execute: { [weak self] in - guard let self, let contextControllerToDismissOnSelection = self.contextControllerToDismissOnSelection else { + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + guard let self else { return } - if let contextControllerToDismissOnSelection = contextControllerToDismissOnSelection as? ContextController { - contextControllerToDismissOnSelection.dismissWithCustomTransition(transition: .animated(duration: 0.4, curve: .spring), completion: nil) + guard let peer, let peerReference = PeerReference(peer._asPeer()) else { + return } + + let shareController = ShareController( + context: self.context, + subject: .media(.story(peer: peerReference, id: item.id, media: TelegramMediaStory(storyId: StoryId(peerId: peer.id, id: item.id), isMention: false))), + presetText: nil, + preferredAction: .default, + showInChat: nil, + fromForeignApp: false, + segmentedValues: nil, + externalShare: false, + immediateExternalShare: false, + switchableAccounts: [], + immediatePeerId: nil, + updatedPresentationData: nil, + forceTheme: nil, + forcedActionTitle: nil, + shareAsLink: false, + collectibleItemInfo: nil + ) + self.parentController?.present(shareController, in: .window(.root)) }) - }))) - } - - if items.isEmpty { - return + }) + }))) + } + + if canManage { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryList_ItemAction_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in + c?.dismiss(completion: { + guard let self else { + return + } + + self.presentDeleteConfirmation(ids: Set([item.id])) + }) + }))) + } + + if self.canManageStories { + if !items.isEmpty { + items.append(.separator) } - - let tempSourceNode = TempExtractedItemNode( - item: item, - itemLayer: itemLayer - ) - tempSourceNode.frame = rect - tempSourceNode.update(size: rect.size) - - let scaleSide = itemLayer.bounds.width - let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide) - let currentScale = minScale - - ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: tempSourceNode.contextSourceNode.contentNode, scale: currentScale) - ContainedViewLayoutTransition.immediate.updateTransformScale(layer: itemLayer, scale: 1.0) - - self.tempContextContentItemNode = tempSourceNode - self.addSubnode(tempSourceNode) - - let contextController = ContextController(presentationData: self.presentationData, source: .extracted(ExtractedContentSourceImpl(controller: parentController, sourceNode: tempSourceNode.contextSourceNode, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - parentController.presentInGlobalOverlay(contextController) - }) + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.actionSheet.primaryTextColor) + }, action: { [weak self] c, f in + guard let self, let parentController = self.parentController as? PeerInfoScreen else { + f(.default) + return + } + + self.contextControllerToDismissOnSelection = c + parentController.toggleStorySelection(ids: [item.id], isSelected: true) + + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5, execute: { [weak self] in + guard let self, let contextControllerToDismissOnSelection = self.contextControllerToDismissOnSelection else { + return + } + if let contextControllerToDismissOnSelection = contextControllerToDismissOnSelection as? ContextController { + contextControllerToDismissOnSelection.dismissWithCustomTransition(transition: .animated(duration: 0.4, curve: .spring), completion: nil) + } + }) + }))) + } + + if items.isEmpty { + return + } + + let tempSourceNode = TempExtractedItemNode( + item: item, + itemLayer: itemLayer + ) + tempSourceNode.frame = rect + tempSourceNode.update(size: rect.size) + + let scaleSide = itemLayer.bounds.width + let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide) + let currentScale = minScale + + ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: tempSourceNode.contextSourceNode.contentNode, scale: currentScale) + ContainedViewLayoutTransition.immediate.updateTransformScale(layer: itemLayer, scale: 1.0) + + self.tempContextContentItemNode = tempSourceNode + self.addSubnode(tempSourceNode) + + let contextController = ContextController(presentationData: self.presentationData, source: .extracted(ExtractedContentSourceImpl(controller: parentController, sourceNode: tempSourceNode.contextSourceNode, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + parentController.presentInGlobalOverlay(contextController) } public func updateContentType(contentType: ContentType) { @@ -2012,9 +2236,38 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.isRequestingView = true var firstTime = true let queue = Queue() + + let authorPeer: Signal + if self.searchQuery != nil { + authorPeer = self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId) + ) + } else { + authorPeer = .single(nil) + } + + var state = self.listSource.state + if self.peerId == nil && self.listDisposable == nil { + state = .single(PeerStoryListContext.State( + peerReference: nil, + items: [], + pinnedIds: Set(), + totalCount: 0, + loadMoreToken: 0, + isCached: false, + hasCache: false, + allEntityFiles: [:] + )) |> then(state |> delay(2.0, queue: .mainQueue())) + } + + self.listDisposable?.dispose() + self.listDisposable = nil - self.listDisposable.set((self.listSource.state - |> deliverOn(queue)).start(next: { [weak self] state in + self.listDisposable = (combineLatest( + state, + authorPeer + ) + |> deliverOn(queue)).startStrict(next: { [weak self] state, authorPeer in guard let self else { return } @@ -2044,6 +2297,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr index: mappedItems.count, peer: peerReference, story: item, + authorPeer: authorPeer, isPinned: state.pinnedIds.contains(item.id), localMonthTimestamp: Month(localTimestamp: item.timestamp + timezoneOffset).packedValue )) @@ -2084,7 +2338,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr strongSelf.updateHistory(items: items, pinnedIds: state.pinnedIds, synchronous: currentSynchronous, reloadAtTop: currentReloadAtTop) strongSelf.isRequestingView = false } - })) + }) } private func updateHistory(items: SparseItemGrid.Items, pinnedIds: Set, synchronous: Bool, reloadAtTop: Bool) { @@ -2405,6 +2659,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } private func presentDeleteConfirmation(ids: Set) { + guard let peerId = self.peerId else { + return + } + let presentationData = self.presentationData let controller = ActionSheetController(presentationData: presentationData) let dismissAction: () -> Void = { [weak controller] in @@ -2427,7 +2685,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr parentController.cancelItemSelection() } - let _ = self.context.engine.messages.deleteStories(peerId: self.peerId, ids: Array(ids)).startStandalone() + let _ = self.context.engine.messages.deleteStories(peerId: peerId, ids: Array(ids)).startStandalone() }) ]), ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) @@ -2439,7 +2697,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) var bottomInset = bottomInset - if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories { + if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories, let peerId = self.peerId { let selectionPanel: ComponentView var selectionPanelTransition = Transition(transition) if let current = self.selectionPanel { @@ -2473,7 +2731,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr let toastText = presentationData.strings.StoryList_ToastPinLimit_Text(3) self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_infotip", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: nil, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) } else { - let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: self.peerId, ids: Array(updatedPinnedIds)).startStandalone() + let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: peerId, ids: Array(updatedPinnedIds)).startStandalone() let presentationData = self.presentationData @@ -2491,7 +2749,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr for id in selectedIds { updatedPinnedIds.remove(id) } - let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: self.peerId, ids: Array(updatedPinnedIds)).startStandalone() + let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: peerId, ids: Array(updatedPinnedIds)).startStandalone() let presentationData = self.presentationData @@ -2522,7 +2780,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr parentController.cancelItemSelection() } - let _ = self.context.engine.messages.updateStoriesArePinned(peerId: self.peerId, ids: items, isPinned: self.isArchive ? true : false).startStandalone() + let _ = self.context.engine.messages.updateStoriesArePinned(peerId: peerId, ids: items, isPinned: self.isArchive ? true : false).startStandalone() let text: String if self.isArchive { diff --git a/submodules/TelegramUI/Components/PeerReportScreen/BUILD b/submodules/TelegramUI/Components/PeerReportScreen/BUILD index 95a0b407471..0dd5eee4436 100644 --- a/submodules/TelegramUI/Components/PeerReportScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerReportScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/PeerReportScreen/Sources/PeerReportScreen.swift b/submodules/TelegramUI/Components/PeerReportScreen/Sources/PeerReportScreen.swift index 1e9b7fbbb4c..d1a4f97e049 100644 --- a/submodules/TelegramUI/Components/PeerReportScreen/Sources/PeerReportScreen.swift +++ b/submodules/TelegramUI/Components/PeerReportScreen/Sources/PeerReportScreen.swift @@ -57,7 +57,7 @@ public func presentPeerReportOptions( items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) }, iconPosition: .left, action: { (c, _) in - c.popItems() + c?.popItems() }))) items.append(.separator) } diff --git a/submodules/TelegramUI/Components/PeerSelectionController/BUILD b/submodules/TelegramUI/Components/PeerSelectionController/BUILD index 8cce950b849..2432230c001 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/BUILD +++ b/submodules/TelegramUI/Components/PeerSelectionController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift index 29bb2552306..2c448b10547 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift @@ -19,7 +19,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon private var customTitle: String? public var peerSelected: ((EnginePeer, Int64?) -> Void)? - public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? + public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.SendParameters?) -> Void)? private let filter: ChatListNodePeersFilter private let forumPeerId: EnginePeer.Id? private let selectForumThreads: Bool @@ -246,8 +246,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.peerSelectionNode.navigationBar = self.navigationBar - self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState in - self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState) + self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState, messageEffect in + self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState, messageEffect) } self.peerSelectionNode.requestDeactivateSearch = { [weak self] in diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 2b8f8e1d17a..8b26941aac8 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -83,7 +83,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { var requestOpenDisabledPeer: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)? var requestOpenPeerFromSearch: ((EnginePeer, Int64?) -> Void)? var requestOpenMessageFromSearch: ((EnginePeer, Int64?, EngineMessage.Id) -> Void)? - var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? + var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.SendParameters?) -> Void)? private var presentationData: PresentationData { didSet { @@ -531,7 +531,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { forwardMessageIds = forwardMessageIds.filter { selectedMessageIds.contains($0) } strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds) }) }) } - strongSelf.textInputPanelNode?.sendMessage(.generic) + strongSelf.textInputPanelNode?.sendMessage(.generic, nil) f(.default) }))) @@ -693,20 +693,44 @@ final class PeerSelectionControllerNode: ASDisplayNode { hasEntityKeyboard = true } - let controller = ChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputNode.textView, canSendWhenOnline: false, completion: { - }, sendMessage: { [weak textInputPanelNode] mode in - switch mode { - case .generic: - textInputPanelNode?.sendMessage(.generic) - case .silently: - textInputPanelNode?.sendMessage(.silent) - case .whenOnline: - textInputPanelNode?.sendMessage(.whenOnline) + let controller = makeChatSendMessageActionSheetController( + context: strongSelf.context, + peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, + params: .sendMessage(SendMessageActionSheetControllerParams.SendMessage( + isScheduledMessages: false, + mediaPreview: nil, + mediaCaptionIsAbove: nil, + attachment: false, + canSendWhenOnline: false, + forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [] + )), + hasEntityKeyboard: hasEntityKeyboard, + gesture: gesture, + sourceSendButton: node, + textInputView: textInputNode.textView, + emojiViewProvider: textInputPanelNode.emojiViewProvider, + completion: { + }, + sendMessage: { [weak textInputPanelNode] mode, messageEffect in + switch mode { + case .generic: + textInputPanelNode?.sendMessage(.generic, messageEffect) + case .silently: + textInputPanelNode?.sendMessage(.silent, messageEffect) + case .whenOnline: + textInputPanelNode?.sendMessage(.whenOnline, messageEffect) + } + }, + schedule: { [weak textInputPanelNode] messageEffect in + textInputPanelNode?.sendMessage(.schedule, messageEffect) + }, + openPremiumPaywall: { [weak controller] c in + guard let controller else { + return + } + controller.push(c) } - }, schedule: { [weak textInputPanelNode] in - textInputPanelNode?.sendMessage(.schedule) - }) - controller.emojiViewProvider = textInputPanelNode.emojiViewProvider + ) strongSelf.presentInGlobalOverlay(controller, nil) }, openScheduledMessages: { }, openPeersNearby: { @@ -828,7 +852,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { return nil }) textInputPanelNode.interfaceInteraction = self.interfaceInteraction - textInputPanelNode.sendMessage = { [weak self] mode in + textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in guard let strongSelf = self else { return } @@ -838,7 +862,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { let (selectedPeers, selectedPeerMap) = strongSelf.selectedPeers if !selectedPeers.isEmpty { - strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState) + strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState, messageEffect) } } self.addSubnode(textInputPanelNode) diff --git a/submodules/TelegramUI/Components/PlainButtonComponent/BUILD b/submodules/TelegramUI/Components/PlainButtonComponent/BUILD index 146e69465a6..aee33bc7f22 100644 --- a/submodules/TelegramUI/Components/PlainButtonComponent/BUILD +++ b/submodules/TelegramUI/Components/PlainButtonComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/BUILD b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/BUILD new file mode 100644 index 00000000000..5725286c77f --- /dev/null +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/BUILD @@ -0,0 +1,29 @@ +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/PhotoResources", + "//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 62% rename from submodules/PremiumUI/Sources/GiftAvatarComponent.swift rename to submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift index 771982bd653..42472817e76 100644 --- a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift @@ -13,41 +13,71 @@ import TelegramCore import MergedAvatarsNode import MultilineTextComponent import TelegramPresentationData +import PhotoResources private let sceneVersion: Int = 1 -final class GiftAvatarComponent: Component { +public final class GiftAvatarComponent: Component { let context: AccountContext let theme: PresentationTheme let peers: [EnginePeer] + let photo: TelegramMediaWebFile? + let starsPeer: StarsContext.State.Transaction.Peer? let isVisible: Bool let hasIdleAnimations: Bool + let hasScaleAnimation: Bool + let avatarSize: CGFloat + 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], + photo: TelegramMediaWebFile? = nil, + starsPeer: StarsContext.State.Transaction.Peer? = nil, + isVisible: Bool, + hasIdleAnimations: Bool, + hasScaleAnimation: Bool = true, + avatarSize: CGFloat = 100.0, + color: UIColor? = nil, + offset: CGFloat? = nil + ) { self.context = context self.theme = theme self.peers = peers + self.photo = photo + self.starsPeer = starsPeer self.isVisible = isVisible self.hasIdleAnimations = hasIdleAnimations + self.hasScaleAnimation = hasScaleAnimation + self.avatarSize = avatarSize + 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.photo == rhs.photo && lhs.theme === rhs.theme && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations && lhs.hasScaleAnimation == rhs.hasScaleAnimation && lhs.avatarSize == rhs.avatarSize && 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() } @@ -58,6 +88,10 @@ final class GiftAvatarComponent: Component { private let sceneView: SCNView private let avatarNode: ImageNode private var mergedAvatarsNode: MergedAvatarsNode? + private var imageNode: TransformImageNode? + + private var iconBackgroundView: UIImageView? + private var iconView: UIImageView? private let badgeBackground = ComponentView() private let badge = ComponentView() @@ -66,7 +100,9 @@ final class GiftAvatarComponent: Component { private var timer: SwiftSignalKit.Timer? private var hasIdleAnimations = false - override init(frame: CGRect) { + private let fetchDisposable = MetaDisposable() + + 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 +116,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) @@ -96,6 +130,7 @@ final class GiftAvatarComponent: Component { deinit { self.timer?.invalidate() + self.fetchDisposable.dispose() } private let hapticFeedback = HapticFeedback() @@ -105,19 +140,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 +205,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,14 +308,114 @@ 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 - if component.peers.count > 1 { + if let photo = component.photo { + let imageNode: TransformImageNode + if let current = self.imageNode { + imageNode = current + } else { + imageNode = TransformImageNode() + imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] + self.addSubview(imageNode.view) + self.imageNode = imageNode + + imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo)) + self.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict()) + } + + let imageSize = CGSize(width: component.avatarSize, height: component.avatarSize) + imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: 113.0 - imageSize.height / 2.0), size: imageSize) + + imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: imageSize.width / 2.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + + self.avatarNode.isHidden = true + } else if let starsPeer = component.starsPeer { + let iconBackgroundView: UIImageView + let iconView: UIImageView + if let currentBackground = self.iconBackgroundView, let current = self.iconView { + iconBackgroundView = currentBackground + iconView = current + } else { + iconBackgroundView = UIImageView() + iconView = UIImageView() + + self.addSubview(iconBackgroundView) + self.addSubview(iconView) + + self.iconBackgroundView = iconBackgroundView + self.iconView = iconView + + let size = CGSize(width: component.avatarSize, height: component.avatarSize) + var iconInset: CGFloat = 9.0 + var iconOffset: CGFloat = 0.0 + + switch starsPeer { + case .appStore: + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0x2a9ef1).cgColor, + UIColor(rgb: 0x72d5fd).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = UIImage(bundleImageName: "Premium/Stars/Apple") + case .playMarket: + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0x54cb68).cgColor, + UIColor(rgb: 0xa0de7e).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = UIImage(bundleImageName: "Premium/Stars/Google") + case .fragment: + iconBackgroundView.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1b1f24)) + iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") + iconOffset = 5.0 + case .premiumBot: + iconInset = 15.0 + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0x6b93ff).cgColor, + UIColor(rgb: 0x6b93ff).cgColor, + UIColor(rgb: 0x8d77ff).cgColor, + UIColor(rgb: 0xb56eec).cgColor, + UIColor(rgb: 0xb56eec).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white) + case .peer, .unsupported: + iconInset = 15.0 + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0xb1b1b1).cgColor, + UIColor(rgb: 0xcdcdcd).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white) + } + + let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - size.width) / 2.0), y: 113.0 - size.height / 2.0), size: size) + iconBackgroundView.frame = imageFrame + iconView.frame = imageFrame.insetBy(dx: iconInset, dy: iconInset).offsetBy(dx: 0.0, dy: iconOffset) + } + } else if component.peers.count > 1 { let avatarSize = CGSize(width: 60.0, height: 60.0) let mergedAvatarsNode: MergedAvatarsNode @@ -275,7 +438,7 @@ final class GiftAvatarComponent: Component { self.mergedAvatarsNode = nil self.avatarNode.isHidden = false - let avatarSize = CGSize(width: 100.0, height: 100.0) + let avatarSize = CGSize(width: component.avatarSize, height: component.avatarSize) if let peer = component.peers.first { self.avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: peer, size: avatarSize, font: avatarPlaceholderFont(size: 43.0), fullSize: true)) } @@ -325,11 +488,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 77% rename from submodules/PremiumUI/Sources/PremiumStarComponent.swift rename to submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift index 549f8e605a9..5460e41b6cc 100644 --- a/submodules/PremiumUI/Sources/PremiumStarComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift @@ -7,6 +7,7 @@ import SceneKit import GZip import AppBundle import LegacyComponents +import TelegramPresentationData private let sceneVersion: Int = 7 @@ -30,22 +31,21 @@ 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, - ] - var locations: [CGFloat] = [0.0, 0.25, 0.5, 0.75, 1.0] + let colorsArray: [CGColor] = colors.map { $0.cgColor } + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + let t = CGFloat(i) / CGFloat(colors.count - 1) + locations.append(t) + } 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 +69,58 @@ func loadCompressedScene(name: String, version: Int) -> SCNScene? { return scene } -final class PremiumStarComponent: Component { +public final class PremiumStarComponent: Component { + let theme: PresentationTheme let isIntro: Bool let isVisible: Bool let hasIdleAnimations: Bool - - init(isIntro: Bool, isVisible: Bool, hasIdleAnimations: Bool) { + let colors: [UIColor]? + let particleColor: UIColor? + + public init( + theme: PresentationTheme, + isIntro: Bool, + isVisible: Bool, + hasIdleAnimations: Bool, + colors: [UIColor]? = nil, + particleColor: UIColor? = nil + ) { + self.theme = theme self.isIntro = isIntro self.isVisible = isVisible self.hasIdleAnimations = hasIdleAnimations + self.colors = colors + self.particleColor = particleColor } - 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.theme === rhs.theme && lhs.isIntro == rhs.isIntro && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations && lhs.colors == rhs.colors && lhs.particleColor == rhs.particleColor } - 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 +144,6 @@ final class PremiumStarComponent: Component { self.addSubview(self.sceneView) - self.setup() - let panGestureRecoginzer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))) self.addGestureRecognizer(panGestureRecoginzer) @@ -274,19 +290,100 @@ 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: "star2", version: sceneVersion) else { return } + self.didSetup = true self.sceneView.scene = scene self.sceneView.delegate = self - let _ = self.sceneView.snapshot() + if let component = self.component, let node = scene.rootNode.childNode(withName: "star", recursively: false), let colors = + component.colors { + node.geometry?.materials.first?.diffuse.contents = generateDiffuseTexture(colors: colors) + + let names: [String] = [ + "particles_left", + "particles_right", + "particles_left_bottom", + "particles_right_bottom", + "particles_center" + ] + + let starNames: [String] = [ + "coins_left", + "coins_right" + ] + + if let particleColor = component.particleColor { + for name in starNames { + if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first { + particleSystem.particleIntensity = 1.0 + particleSystem.particleIntensityVariation = 0.05 + particleSystem.particleColor = particleColor + particleSystem.particleColorVariation = SCNVector4Make(0.07, 0.0, 0.1, 0.0) + node.isHidden = false + + if let propertyControllers = particleSystem.propertyControllers, let sizeController = propertyControllers[.size], let colorController = propertyControllers[.color] { + let animation = CAKeyframeAnimation() + if let existing = colorController.animation as? CAKeyframeAnimation { + animation.keyTimes = existing.keyTimes + animation.values = existing.values?.compactMap { ($0 as? UIColor)?.alpha } ?? [] + } else { + animation.values = [ 0.0, 1.0, 1.0, 0.0 ] + } + let opacityController = SCNParticlePropertyController(animation: animation) + particleSystem.propertyControllers = [ + .size: sizeController, + .opacity: opacityController + ] + } + } + } + } + + for name in names { + if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first { + if let particleColor = component.particleColor { + particleSystem.particleIntensity = min(1.0, 2.0 * particleSystem.particleIntensity) + particleSystem.particleIntensityVariation = 0.05 + particleSystem.particleColor = particleColor + particleSystem.particleColorVariation = SCNVector4Make(0.1, 0.0, 0.12, 0.0) + } else { + particleSystem.particleColorVariation = SCNVector4Make(0.12, 0.03, 0.035, 0.0) + } + + if let propertyControllers = particleSystem.propertyControllers, let sizeController = propertyControllers[.size], let colorController = propertyControllers[.color] { + let animation = CAKeyframeAnimation() + if let existing = colorController.animation as? CAKeyframeAnimation { + animation.keyTimes = existing.keyTimes + animation.values = existing.values?.compactMap { ($0 as? UIColor)?.alpha } ?? [] + } else { + animation.values = [ 0.0, 1.0, 1.0, 0.0 ] + } + let opacityController = SCNParticlePropertyController(animation: animation) + particleSystem.propertyControllers = [ + .size: sizeController, + .opacity: opacityController + ] + } + } + } + } + + 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 @@ -303,7 +400,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 @@ -418,9 +515,9 @@ final class PremiumStarComponent: Component { return } - if #available(iOS 17.0, *), let material = node.geometry?.materials.first { - material.metalness.intensity = 0.2 - } +// if let material = node.geometry?.materials.first { +// material.metalness.intensity = 0.4 +// } let animation = CABasicAnimation(keyPath: "contentsTransform") animation.fillMode = .forwards @@ -560,6 +657,14 @@ final class PremiumStarComponent: Component { } func update(component: PremiumStarComponent, availableSize: CGSize, transition: Transition) -> CGSize { + self.component = component + + self.setup() + + if let _ = component.particleColor { + self.sceneView.backgroundColor = component.theme.list.blocksBackgroundColor + } + 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) @@ -571,11 +676,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/PremiumGiftAttachmentScreen/BUILD b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/BUILD index 154b99fe701..1ca248fb024 100644 --- a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/BUILD +++ b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift index 6232f869d9a..102e7b1fd69 100644 --- a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift @@ -34,6 +34,17 @@ private final class PremiumGiftContext: AttachmentMediaPickerContext { return .single(nil) } + var hasCaption: Bool { + return false + } + + var captionIsAboveMedia: Signal { + return .single(false) + } + + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { + } + public var loadingProgress: Signal { return .single(nil) } @@ -49,10 +60,10 @@ private final class PremiumGiftContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { } - func schedule() { + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { } func mainButtonAction() { diff --git a/submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent/BUILD b/submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent/BUILD index b98e6369aed..154c2508f83 100644 --- a/submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent/BUILD +++ b/submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/PremiumPeerShortcutComponent/BUILD b/submodules/TelegramUI/Components/PremiumPeerShortcutComponent/BUILD index b59ef6283be..8691b0beb07 100644 --- a/submodules/TelegramUI/Components/PremiumPeerShortcutComponent/BUILD +++ b/submodules/TelegramUI/Components/PremiumPeerShortcutComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/BUILD b/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/BUILD index 1fc9bca1131..f046ad9bd8d 100644 --- a/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/BUILD +++ b/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen/BUILD b/submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen/BUILD index 8606deae330..f8c004a8fe6 100644 --- a/submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen/BUILD +++ b/submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/ScrollComponent/BUILD b/submodules/TelegramUI/Components/ScrollComponent/BUILD index aa35236c12d..679ff5cf472 100644 --- a/submodules/TelegramUI/Components/ScrollComponent/BUILD +++ b/submodules/TelegramUI/Components/ScrollComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/ScrollComponent/Sources/ScrollComponent.swift b/submodules/TelegramUI/Components/ScrollComponent/Sources/ScrollComponent.swift index f9421338cba..9f4cb768d6e 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/SendInviteLinkScreen/BUILD b/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD index 0c0e555fadf..831a0f03633 100644 --- a/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD +++ b/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD index aa5718c1c17..3b7c644ffd8 100644 --- a/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD index 5ebf49bb3cd..4feaa83c8f5 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift index cde85a04763..66e706dddf3 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift @@ -215,6 +215,8 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco initialShortcut = shortcut case .businessLinkSetup: initialShortcut = "" + case .hashTagSearch: + initialShortcut = "" } let queue = Queue() @@ -251,9 +253,19 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco } case .businessLinkSetup: break + case .hashTagSearch: + break } } func businessLinkUpdate(message: String, entities: [MessageTextEntity], title: String?) { } + + func loadMore() { + } + + func hashtagSearchUpdate(query: String) { + } + + var hashtagSearchResultsUpdate: ((SearchMessagesResult, SearchMessagesState)) -> Void = { _ in } } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift index 2db55b4b3b1..44d712798c4 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift @@ -856,7 +856,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(BundleIconComponent( name: "Chat List/ComposeIcon", tintColor: environment.theme.list.itemAccentColor - )))), + ))), false), accessory: nil, action: { [weak self] _ in guard let self else { @@ -933,7 +933,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { image: checkIcon, tintColor: !isSelected ? .clear : environment.theme.list.itemAccentColor, contentMode: .center - )))), + ))), false), accessory: nil, action: { [weak self] _ in guard let self else { @@ -1203,7 +1203,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { image: checkIcon, tintColor: !self.hasAccessToAllChatsByDefault ? .clear : environment.theme.list.itemAccentColor, contentMode: .center - )))), + ))), false), accessory: nil, action: { [weak self] _ in guard let self else { @@ -1233,7 +1233,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { image: checkIcon, tintColor: self.hasAccessToAllChatsByDefault ? .clear : environment.theme.list.itemAccentColor, contentMode: .center - )))), + ))), false), accessory: nil, action: { [weak self] _ in guard let self else { @@ -1280,7 +1280,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(BundleIconComponent( name: "Chat List/AddIcon", tintColor: environment.theme.list.itemAccentColor - )))), + ))), false), accessory: nil, action: { [weak self] _ in guard let self else { diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkChatContents.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkChatContents.swift index 3b69806c611..13f07d581f2 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkChatContents.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkChatContents.swift @@ -85,4 +85,12 @@ final class BusinessLinkChatContents: ChatCustomContentsProtocol { )) } } + + func loadMore() { + } + + func hashtagSearchUpdate(query: String) { + } + + var hashtagSearchResultsUpdate: ((SearchMessagesResult, SearchMessagesState)) -> Void = { _ in } } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift index fc531baa095..86ea07efa8e 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift @@ -416,7 +416,7 @@ final class BusinessLinksSetupScreenComponent: Component { leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(BundleIconComponent( name: "Item List/AddLinkIcon", tintColor: environment.theme.list.itemAccentColor - )))), + ))), false), accessory: nil, action: { [weak self] _ in guard let self else { @@ -550,7 +550,7 @@ final class BusinessLinksSetupScreenComponent: Component { textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let self else { return } @@ -563,7 +563,7 @@ final class BusinessLinksSetupScreenComponent: Component { textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let self else { return } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 110d7b16714..f2ebe3a3053 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -547,18 +547,18 @@ final class QuickReplySetupScreenComponent: Component { } func openQuickReplyChat(shortcut: String?, shortcutId: Int32?) { - guard let component = self.component, let environment = self.environment else { + guard let component = self.component, let environment = self.environment, let controller = self.environment?.controller() as? QuickReplySetupScreen else { return } + + self.contentListNode?.clearHighlightAnimated(true) - if case let .select(completion) = component.mode { - if let shortcutId { + if let shortcut { + if let shortcutId, case let .select(completion) = component.mode { completion(shortcutId) + return } - return - } - - if let shortcut { + let shortcutType: ChatQuickReplyShortcutType if shortcut == "hello" { shortcutType = .greeting @@ -581,7 +581,12 @@ final class QuickReplySetupScreenComponent: Component { mode: .standard(.default) ) chatController.navigationPresentation = .modal - self.environment?.controller()?.push(chatController) + + if controller.navigationController != nil { + controller.push(chatController) + } else if let attachmentContainer = controller.parentController() { + attachmentContainer.push(chatController) + } } else { var completion: ((String?) -> Void)? let alertController = quickReplyNameAlertController( @@ -619,8 +624,6 @@ final class QuickReplySetupScreenComponent: Component { } self.environment?.controller()?.present(alertController, in: .window(.root)) } - - self.contentListNode?.clearHighlightAnimated(true) } func openEditShortcut(id: Int32, currentValue: String) { @@ -1172,13 +1175,8 @@ final class QuickReplySetupScreenComponent: Component { var entries: [ContentEntry] = [] if let shortcutMessageList = self.shortcutMessageList, let accountPeer = self.accountPeer { - switch component.mode { - case .manage: - if self.searchQuery.isEmpty { - entries.append(.add) - } - case .select: - break + if self.searchQuery.isEmpty { + entries.append(.add) } for item in shortcutMessageList.items { if !self.searchQuery.isEmpty { diff --git a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD index 26290c940d3..44598dcca09 100644 --- a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/BoostLevelIconComponent/BUILD b/submodules/TelegramUI/Components/Settings/BoostLevelIconComponent/BUILD index 9419d42c9bc..bfacde9230a 100644 --- a/submodules/TelegramUI/Components/Settings/BoostLevelIconComponent/BUILD +++ b/submodules/TelegramUI/Components/Settings/BoostLevelIconComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Settings/BotSettingsScreen/BUILD b/submodules/TelegramUI/Components/Settings/BotSettingsScreen/BUILD index f561ffc45a5..66548156b43 100644 --- a/submodules/TelegramUI/Components/Settings/BotSettingsScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/BotSettingsScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/BUILD index 426f4b106b8..bd66118ce4c 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift index c258c60fb4b..35689013c9f 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift @@ -531,7 +531,7 @@ final class BusinessDaySetupScreenComponent: Component { leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(BundleIconComponent( name: "Item List/AddTimeIcon", tintColor: environment.theme.list.itemAccentColor - )))), + ))), false), accessory: nil, action: { [weak self] _ in guard let self else { diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/BUILD index 02989c42f4c..900cc5f6b33 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", @@ -41,6 +41,7 @@ swift_library( "//submodules/TelegramUI/Components/PeerAllowedReactionsScreen", "//submodules/TelegramUI/Components/EmojiActionIconComponent", "//submodules/TelegramUI/Components/TextFieldComponent", + "//submodules/TelegramUI/Components/CameraScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift index b06da137cc0..439834ef2f4 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift @@ -22,6 +22,7 @@ import EntityKeyboard import PeerAllowedReactionsScreen import EmojiActionIconComponent import TextFieldComponent +import CameraScreen final class BusinessIntroSetupScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -161,6 +162,56 @@ final class BusinessIntroSetupScreenComponent: Component { return true } + func openStickerEditor() { + guard let component = self.component, let environment = self.environment, let controller = environment.controller() as? BusinessIntroSetupScreen else { + return + } + + let context = component.context + let navigationController = controller.navigationController as? NavigationController + + var dismissImpl: (() -> Void)? + let mainController = context.sharedContext.makeStickerMediaPickerScreen( + context: context, + getSourceRect: { return .zero }, + completion: { result, transitionView, transitionRect, transitionImage, fromCamera, completion, cancelled in + let editorController = context.sharedContext.makeStickerEditorScreen( + context: context, + source: result, + intro: true, + transitionArguments: transitionView.flatMap { ($0, transitionRect, transitionImage) }, + completion: { [weak self] file, emoji, commit in + dismissImpl?() + + guard let self else { + return + } + + self.stickerFile = file + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + + commit() + }, + cancelled: cancelled + ) + navigationController?.pushViewController(editorController) + }, + dismissed: {} + ) + dismissImpl = { [weak mainController] in + if let mainController, let navigationController = mainController.navigationController { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { c in + return !(c is CameraScreen) && c !== mainController + } + navigationController.setViewControllers(viewControllers, animated: false) + } + } + navigationController?.pushViewController(mainController) + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.ignoreScrolling { self.updateScrolling(transition: .immediate) @@ -229,6 +280,7 @@ final class BusinessIntroSetupScreenComponent: Component { hasSearch: true, hasTrending: false, forceHasPremium: true, + hasAdd: true, searchIsPlaceholderOnly: false, subject: .greetingStickers ) @@ -245,6 +297,13 @@ final class BusinessIntroSetupScreenComponent: Component { return } guard let itemFile = item.itemFile else { + if case .icon(.add) = item.content { + self.openStickerEditor() + self.displayStickerInput = false + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + } return } diff --git a/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD b/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD index 174ee14b59d..7393625fdba 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD +++ b/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/BUILD index 83cfb9c9c7c..62c86d81ec7 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD index c83d5538a0e..29b1c628f8c 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift index cd442605996..7f82689d756 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift @@ -425,7 +425,7 @@ final class BusinessRecipientListScreenComponent: Component { leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(BundleIconComponent( name: "Chat List/AddIcon", tintColor: environment.theme.list.itemAccentColor - )))), + ))), false), accessory: nil, action: { [weak self] _ in guard let self else { diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift index 7709c950483..4571ab2149b 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift @@ -774,7 +774,7 @@ final class ChatbotSetupScreenComponent: Component { image: checkIcon, tintColor: !self.hasAccessToAllChatsByDefault ? .clear : environment.theme.list.itemAccentColor, contentMode: .center - )))), + ))), false), accessory: nil, action: { [weak self] _ in guard let self else { @@ -804,7 +804,7 @@ final class ChatbotSetupScreenComponent: Component { image: checkIcon, tintColor: self.hasAccessToAllChatsByDefault ? .clear : environment.theme.list.itemAccentColor, contentMode: .center - )))), + ))), false), accessory: nil, action: { [weak self] _ in guard let self else { diff --git a/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/BUILD b/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/BUILD index 142a36bbaab..910a18c86fe 100644 --- a/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/GenerateThemeName/BUILD b/submodules/TelegramUI/Components/Settings/GenerateThemeName/BUILD index 32c10189fc8..72415507ad4 100644 --- a/submodules/TelegramUI/Components/Settings/GenerateThemeName/BUILD +++ b/submodules/TelegramUI/Components/Settings/GenerateThemeName/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD b/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD index 2fdef7364de..245b8b4a86e 100644 --- a/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorItem/BUILD b/submodules/TelegramUI/Components/Settings/PeerNameColorItem/BUILD index 5c280d2a228..bfd052567d7 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorItem/BUILD +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD index 59f56757076..00bd855a6a7 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/BUILD b/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/BUILD index a9240c4e47d..f35cc218fcd 100644 --- a/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/BUILD b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/BUILD index e35ab3233bd..f9bd71be181 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/BUILD +++ b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD index a9f96d6606b..d637d20b691 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD +++ b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD index d6fd0655ff4..39464fdab8e 100644 --- a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD +++ b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD index 49b5ed0d226..ab5384c930a 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/BUILD b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/BUILD index 7e7c7672241..c4e83683a76 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/BUILD +++ b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/BUILD b/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/BUILD index f0350af4f84..1dab1a211d4 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/BUILD +++ b/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/BUILD b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/BUILD index 38fdb23587f..cee8b3ce780 100644 --- a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD index 45b7b88b4e1..e73fe1628a7 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/BUILD b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/BUILD index e88a5216fda..57e674be5e3 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift index 5892427c044..a6fcaa6510e 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift @@ -362,6 +362,17 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext { return .single(nil) } + var hasCaption: Bool { + return false + } + + var captionIsAboveMedia: Signal { + return .single(false) + } + + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { + } + public var loadingProgress: Signal { return .single(nil) } @@ -377,10 +388,10 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { } - func schedule() { + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { } func mainButtonAction() { diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift index 71edb41aa56..534452bab42 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift @@ -101,13 +101,6 @@ public final class ThemeGridController: ViewController { } } }) - - if case .generic = mode { - self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Wallpaper_Search, activate: { [weak self] in - self?.activateSearch() - }) - self.navigationBar?.setContentNode(self.searchContentNode, animated: false) - } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift index 25fc99f957a..1f32b66fb4c 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift @@ -825,7 +825,7 @@ final class ThemeGridControllerNode: ASDisplayNode { if let makeGalleryIconLayout, let galleryItem = self.galleryItem as? ItemListPeerActionItem { (galleryLayout, galleryApply) = makeGalleryIconLayout(galleryItem, params, ItemListNeighbors(top: isChannel ? .none : .sameSection(alwaysPlain: false), bottom: .sameSection(alwaysPlain: !hasCustomWallpaper))) } else if let makeGalleryLayout, let galleryItem = self.galleryItem as? ItemListActionItem { - (galleryLayout, galleryApply) = makeGalleryLayout(galleryItem, params, ItemListNeighbors(top: isChannel ? .none : .sameSection(alwaysPlain: false), bottom: .sameSection(alwaysPlain: false))) + (galleryLayout, galleryApply) = makeGalleryLayout(galleryItem, params, ItemListNeighbors(top: isChannel ? .none : .sameSection(alwaysPlain: false), bottom: .sameSection(alwaysPlain: true))) } else { fatalError() } diff --git a/submodules/TelegramUI/Components/ShareExtensionContext/BUILD b/submodules/TelegramUI/Components/ShareExtensionContext/BUILD index 8a0c3ecf8dc..54f3e0238d8 100644 --- a/submodules/TelegramUI/Components/ShareExtensionContext/BUILD +++ b/submodules/TelegramUI/Components/ShareExtensionContext/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift index 45e01169919..9fb95b9863a 100644 --- a/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift @@ -390,6 +390,7 @@ public class ShareRootControllerImpl { voipMaxLayer: 0, voipVersions: [], appData: .single(nil), + externalRequestVerificationStream: .never(), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, @@ -497,7 +498,6 @@ public class ShareRootControllerImpl { let displayShare: () -> Void = { var cancelImpl: (() -> Void)? - let _ = cancelImpl let beginShare: () -> Void = { // MARK: Nicegram DB @@ -520,12 +520,7 @@ public class ShareRootControllerImpl { return Signal { [weak self] subscriber in switch content[0] { case let .contact(data): - #if !DEBUG - //qwefqwfqwefw - #endif - let _ = data - let _ = self - /*let controller = deviceContactInfoController(context: context, subject: .filter(peer: nil, contactId: nil, contactData: data, completion: { peer, contactData in + let controller = deviceContactInfoController(context: context, environment: environment, subject: .filter(peer: nil, contactId: nil, contactData: data, completion: { peer, contactData in let phone = contactData.basicData.phoneNumbers[0].value if let vCardData = contactData.serializedVCard() { subscriber.putNext([.media(.media(.standalone(media: TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: vCardData))))]) @@ -538,7 +533,7 @@ public class ShareRootControllerImpl { if let strongSelf = self, let window = strongSelf.mainWindow { controller.presentationArguments = ViewControllerPresentationArguments(presentationAnimation: .modalSheet) window.present(controller, on: .root) - }*/ + } break } return EmptyDisposable diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD b/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD index b5a0f4e9597..37abf27292f 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/SliderComponent/BUILD b/submodules/TelegramUI/Components/SliderComponent/BUILD index 6ed98b159f0..008c1d42923 100644 --- a/submodules/TelegramUI/Components/SliderComponent/BUILD +++ b/submodules/TelegramUI/Components/SliderComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/SliderContextItem/BUILD b/submodules/TelegramUI/Components/SliderContextItem/BUILD index add5fd4ffcd..5989914a5b7 100644 --- a/submodules/TelegramUI/Components/SliderContextItem/BUILD +++ b/submodules/TelegramUI/Components/SliderContextItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD b/submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD new file mode 100644 index 00000000000..37eebaff13e --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "StarsImageComponent", + module_name = "StarsImageComponent", + 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/TelegramPresentationData", + "//submodules/PhotoResources", + "//submodules/AvatarNode", + "//submodules/AccountContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift new file mode 100644 index 00000000000..7ab97d78869 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift @@ -0,0 +1,456 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import TelegramCore +import ComponentFlow +import TelegramPresentationData +import PhotoResources +import AvatarNode +import AccountContext + +final class StarsParticlesView: UIView { + private struct Particle { + var trackIndex: Int + var position: CGPoint + var scale: CGFloat + var alpha: CGFloat + var direction: CGPoint + var velocity: CGFloat + var color: UIColor + var currentTime: CGFloat + var lifeTime: CGFloat + + init( + trackIndex: Int, + position: CGPoint, + scale: CGFloat, + alpha: CGFloat, + direction: CGPoint, + velocity: CGFloat, + color: UIColor, + currentTime: CGFloat, + lifeTime: CGFloat + ) { + self.trackIndex = trackIndex + self.position = position + self.scale = scale + self.alpha = alpha + self.direction = direction + self.velocity = velocity + self.color = color + self.currentTime = currentTime + self.lifeTime = lifeTime + } + + mutating func update(deltaTime: CGFloat) { + var position = self.position + position.x += self.direction.x * self.velocity * deltaTime + position.y += self.direction.y * self.velocity * deltaTime + self.position = position + self.currentTime += deltaTime + } + } + + private final class ParticleSet { + private let size: CGSize + private let large: Bool + private(set) var particles: [Particle] = [] + + init(size: CGSize, large: Bool, preAdvance: Bool) { + self.size = size + self.large = large + + self.generateParticles(preAdvance: preAdvance) + } + + private func generateParticles(preAdvance: Bool) { + let maxDirections = self.large ? 8 : 80 + + if self.particles.count < maxDirections { + var allTrackIndices: [Int] = Array(repeating: 0, count: maxDirections) + for i in 0 ..< maxDirections { + allTrackIndices[i] = i + } + var takenIndexCount = 0 + for particle in self.particles { + allTrackIndices[particle.trackIndex] = -1 + takenIndexCount += 1 + } + var availableTrackIndices: [Int] = [] + availableTrackIndices.reserveCapacity(maxDirections - takenIndexCount) + for index in allTrackIndices { + if index != -1 { + availableTrackIndices.append(index) + } + } + + if !availableTrackIndices.isEmpty { + availableTrackIndices.shuffle() + + for takeIndex in availableTrackIndices { + let directionIndex = takeIndex + var angle = (CGFloat(directionIndex % maxDirections) / CGFloat(maxDirections)) * CGFloat.pi * 2.0 + var alpha = 1.0 + var lifeTimeMultiplier = 1.0 + + var isUpOrDownSemisphere = false + if angle > CGFloat.pi / 7.0 && angle < CGFloat.pi - CGFloat.pi / 7.0 { + isUpOrDownSemisphere = true + } else if !"".isEmpty, angle > CGFloat.pi + CGFloat.pi / 7.0 && angle < 2.0 * CGFloat.pi - CGFloat.pi / 7.0 { + isUpOrDownSemisphere = true + } + + if isUpOrDownSemisphere { + if CGFloat.random(in: 0.0 ... 1.0) < 0.2 { + lifeTimeMultiplier = 0.3 + } else { + angle += CGFloat.random(in: 0.0 ... 1.0) > 0.5 ? CGFloat.pi / 1.6 : -CGFloat.pi / 1.6 + angle += CGFloat.random(in: -0.2 ... 0.2) + lifeTimeMultiplier = 0.5 + } + if self.large { + alpha = 0.0 + } + } + if self.large { + angle += CGFloat.random(in: -0.5 ... 0.5) + } + + let direction = CGPoint(x: cos(angle), y: sin(angle)) + let velocity = self.large ? CGFloat.random(in: 15.0 ..< 20.0) : CGFloat.random(in: 20.0 ..< 35.0) + let scale = self.large ? CGFloat.random(in: 0.65 ... 0.9) : CGFloat.random(in: 0.65 ... 1.0) * 0.75 + let lifeTime = (self.large ? CGFloat.random(in: 2.0 ... 3.5) : CGFloat.random(in: 0.7 ... 3.0)) + + var position = CGPoint(x: self.size.width / 2.0, y: self.size.height / 2.0) + var initialOffset: CGFloat = 0.5 + if preAdvance { + initialOffset = CGFloat.random(in: 0.5 ... 1.0) + } else { + let p = CGFloat.random(in: 0.0 ... 1.0) + if p < 0.5 { + initialOffset = CGFloat.random(in: 0.65 ... 1.0) + } else { + initialOffset = 0.5 + } + } + position.x += direction.x * initialOffset * 105.0 + position.y += direction.y * initialOffset * 105.0 + + let largeColors: [UInt32] = [0xff9145, 0xfec007, 0xed9303] + let smallColors: [UInt32] = [0xfecc14, 0xf7ab04, 0xff9145, 0xfdda21] + + let particle = Particle( + trackIndex: directionIndex, + position: position, + scale: scale, + alpha: alpha, + direction: direction, + velocity: velocity, + color: UIColor(rgb: (self.large ? largeColors : smallColors).randomElement()!), + currentTime: 0.0, + lifeTime: lifeTime * lifeTimeMultiplier + ) + self.particles.append(particle) + } + } + } + } + + func update(deltaTime: CGFloat) { + for i in (0 ..< self.particles.count).reversed() { + self.particles[i].update(deltaTime: deltaTime) + if self.particles[i].currentTime > self.particles[i].lifeTime { + self.particles.remove(at: i) + } + } + + self.generateParticles(preAdvance: false) + } + } + + private var displayLink: SharedDisplayLinkDriver.Link? + + private var particleSet: ParticleSet? + private let particleImage: UIImage + private var particleLayers: [SimpleLayer] = [] + + private var size: CGSize? + private let large: Bool + + init(size: CGSize, large: Bool) { + if large { + self.particleImage = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/PremiumIcon"), color: .white)!.withRenderingMode(.alwaysTemplate) + } else { + self.particleImage = generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/Particle"), color: .white)!.withRenderingMode(.alwaysTemplate) + } + + self.large = large + + super.init(frame: .zero) + + self.particleSet = ParticleSet(size: size, large: large, preAdvance: true) + + self.displayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] delta in + self?.update(deltaTime: CGFloat(delta)) + }) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + fileprivate func update(size: CGSize) { + self.size = size + } + + private func update(deltaTime: CGFloat) { + guard let particleSet = self.particleSet else { + return + } + particleSet.update(deltaTime: deltaTime) + + for i in 0 ..< particleSet.particles.count { + let particle = particleSet.particles[i] + + let particleLayer: SimpleLayer + if i < self.particleLayers.count { + particleLayer = self.particleLayers[i] + particleLayer.isHidden = false + } else { + particleLayer = SimpleLayer() + particleLayer.contents = self.particleImage.cgImage + particleLayer.bounds = CGRect(origin: CGPoint(), size: particleImage.size) + self.particleLayers.append(particleLayer) + self.layer.addSublayer(particleLayer) + } + + particleLayer.layerTintColor = particle.color.cgColor + + particleLayer.position = particle.position + particleLayer.opacity = Float(particle.alpha) + + let particleScale = min(1.0, particle.currentTime / 0.3) * min(1.0, (particle.lifeTime - particle.currentTime) / 0.2) * particle.scale + particleLayer.transform = CATransform3DMakeScale(particleScale, particleScale, 1.0) + } + if particleSet.particles.count < self.particleLayers.count { + for i in particleSet.particles.count ..< self.particleLayers.count { + self.particleLayers[i].isHidden = true + } + } + } +} + +public final class StarsImageComponent: Component { + public enum Subject: Equatable { + case none + case photo(TelegramMediaWebFile) + case transactionPeer(StarsContext.State.Transaction.Peer) + } + + public let context: AccountContext + public let subject: Subject + public let theme: PresentationTheme + public let diameter: CGFloat + + public init( + context: AccountContext, + subject: Subject, + theme: PresentationTheme, + diameter: CGFloat + ) { + self.context = context + self.subject = subject + self.theme = theme + self.diameter = diameter + } + + public static func ==(lhs: StarsImageComponent, rhs: StarsImageComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.subject != rhs.subject { + return false + } + if lhs.diameter != rhs.diameter { + return false + } + return true + } + + public final class View: UIView { + private var component: StarsImageComponent? + + private var smallParticlesView: StarsParticlesView? + private var largeParticlesView: StarsParticlesView? + + private var imageNode: TransformImageNode? + private var avatarNode: ImageNode? + private var iconBackgroundView: UIImageView? + private var iconView: UIImageView? + + private let fetchDisposable = MetaDisposable() + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + deinit { + self.fetchDisposable.dispose() + } + + func update(component: StarsImageComponent, availableSize: CGSize, transition: Transition) -> CGSize { + self.component = component + + let smallParticlesView: StarsParticlesView + if let current = self.smallParticlesView { + smallParticlesView = current + } else { + smallParticlesView = StarsParticlesView(size: availableSize, large: false) + + self.addSubview(smallParticlesView) + self.smallParticlesView = smallParticlesView + } + smallParticlesView.update(size: availableSize) + smallParticlesView.frame = CGRect(origin: .zero, size: availableSize) + + let largeParticlesView: StarsParticlesView + if let current = self.largeParticlesView { + largeParticlesView = current + } else { + largeParticlesView = StarsParticlesView(size: availableSize, large: true) + + self.addSubview(largeParticlesView) + self.largeParticlesView = largeParticlesView + } + largeParticlesView.update(size: availableSize) + largeParticlesView.frame = CGRect(origin: .zero, size: availableSize) + + let imageSize = CGSize(width: component.diameter, height: component.diameter) + let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - imageSize.height) / 2.0)), size: imageSize) + + switch component.subject { + case .none: + break + case let .photo(photo): + let imageNode: TransformImageNode + if let current = self.imageNode { + imageNode = current + } else { + imageNode = TransformImageNode() + imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] + self.addSubview(imageNode.view) + self.imageNode = imageNode + + imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo)) + self.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict()) + } + + imageNode.frame = imageFrame + imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: imageSize.width / 2.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + case let .transactionPeer(peer): + if case let .peer(peer) = peer { + let avatarNode: ImageNode + if let current = self.avatarNode { + avatarNode = current + } else { + avatarNode = ImageNode() + avatarNode.displaysAsynchronously = false + self.addSubview(avatarNode.view) + self.avatarNode = avatarNode + + avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: peer, size: imageSize, font: avatarPlaceholderFont(size: 43.0), fullSize: true)) + } + avatarNode.frame = imageFrame + } else { + let iconBackgroundView: UIImageView + let iconView: UIImageView + if let currentBackground = self.iconBackgroundView, let current = self.iconView { + iconBackgroundView = currentBackground + iconView = current + } else { + iconBackgroundView = UIImageView() + iconView = UIImageView() + + self.addSubview(iconBackgroundView) + self.addSubview(iconView) + + self.iconBackgroundView = iconBackgroundView + self.iconView = iconView + } + + var iconInset: CGFloat = 9.0 + var iconOffset: CGFloat = 0.0 + switch peer { + case .appStore: + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: imageSize.width, + colors: [ + UIColor(rgb: 0x2a9ef1).cgColor, + UIColor(rgb: 0x72d5fd).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = UIImage(bundleImageName: "Premium/Stars/Apple") + case .playMarket: + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: imageSize.width, + colors: [ + UIColor(rgb: 0x54cb68).cgColor, + UIColor(rgb: 0xa0de7e).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = UIImage(bundleImageName: "Premium/Stars/Google") + case .fragment: + iconBackgroundView.image = generateFilledCircleImage( + diameter: imageSize.width, + color: UIColor(rgb: 0x1b1f24) + ) + iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") + iconOffset = 5.0 + case .premiumBot: + iconInset = 15.0 + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: imageSize.width, + colors: [ + UIColor(rgb: 0x6b93ff).cgColor, + UIColor(rgb: 0x6b93ff).cgColor, + UIColor(rgb: 0x8d77ff).cgColor, + UIColor(rgb: 0xb56eec).cgColor, + UIColor(rgb: 0xb56eec).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white) + case .peer, .unsupported: + iconInset = 15.0 + iconBackgroundView.image = generateGradientFilledCircleImage( + diameter: imageSize.width, + colors: [ + UIColor(rgb: 0xb1b1b1).cgColor, + UIColor(rgb: 0xcdcdcd).cgColor + ], + direction: .mirroredDiagonal + ) + iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white) + } + iconBackgroundView.frame = imageFrame + iconView.frame = imageFrame.insetBy(dx: iconInset, dy: iconInset).offsetBy(dx: 0.0, dy: iconOffset) + } + } + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + 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/Stars/StarsPurchaseScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD new file mode 100644 index 00000000000..dd5b8cbbb61 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD @@ -0,0 +1,44 @@ +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/TextLoadingEffect", + "//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/ItemLoadingComponent.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/ItemLoadingComponent.swift new file mode 100644 index 00000000000..24905d0bf9f --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/ItemLoadingComponent.swift @@ -0,0 +1,92 @@ +import Foundation +import UIKit +import Display +import AppBundle +import HierarchyTrackingLayer +import ComponentFlow +import TextLoadingEffect + +final class ItemLoadingComponent: Component { + private let color: UIColor + + public init(color: UIColor) { + self.color = color + } + + public static func ==(lhs: ItemLoadingComponent, rhs: ItemLoadingComponent) -> Bool { + if !lhs.color.isEqual(rhs.color) { + return false + } + return true + } + + public final class View: UIView { + private let loadingView = TextLoadingEffectView() + private let borderView = UIImageView() + + private let borderMaskView = UIView() + private let borderMaskGradientView = UIImageView() + private let borderMaskFillView = UIImageView() + + private var component: ItemLoadingComponent? + + override public init(frame: CGRect) { + super.init(frame: frame) + + self.addSubview(self.loadingView) + self.addSubview(self.borderView) + + self.borderView.image = generateFilledRoundedRectImage(size: CGSize(width: 24.0, height: 24.0), cornerRadius: 10.0, color: nil, strokeColor: .white, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10).withRenderingMode(.alwaysTemplate) + + self.borderMaskView.backgroundColor = .clear + self.borderMaskFillView.backgroundColor = .white + + self.borderMaskView.addSubview(self.borderMaskFillView) + self.borderMaskFillView.addSubview(self.borderMaskGradientView) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func playAppearanceAnimation() { + self.borderView.mask = self.borderMaskView + + let gradientWidth = self.borderView.bounds.width * 0.4 + self.borderMaskGradientView.image = generateGradientImage(size: CGSize(width: gradientWidth, height: 24.0), colors: [UIColor.white, UIColor.white.withAlphaComponent(0.0)], locations: [0.0, 1.0], direction: .horizontal) + + self.borderMaskGradientView.frame = CGRect(origin: CGPoint(x: self.borderView.bounds.width, y: 0.0), size: CGSize(width: gradientWidth, height: self.borderView.bounds.height)) + self.borderMaskFillView.frame = CGRect(origin: .zero, size: self.borderView.bounds.size) + + self.borderMaskFillView.layer.animatePosition(from: CGPoint(x: -self.borderView.bounds.width, y: 0.0), to: .zero, duration: 1.0, removeOnCompletion: false, additive: true, completion: { _ in + self.borderView.mask = nil + }) + } + + func update(component: ItemLoadingComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let isFirstTime = self.component == nil + + self.component = component + + self.borderView.tintColor = component.color + + self.loadingView.update(color: component.color, rect: CGRect(origin: .zero, size: availableSize)) + transition.setFrame(view: self.borderView, frame: CGRect(origin: .zero, size: availableSize)) + self.borderMaskView.frame = self.borderView.bounds + + if isFirstTime { + self.playAppearanceAnimation() + } + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public 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/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift new file mode 100644 index 00000000000..56175544ac3 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -0,0 +1,1137 @@ +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) + + public class ExternalState { + public var descriptionHeight: CGFloat = 0.0 + + public init() { + + } + } + + let context: AccountContext + let externalState: ExternalState + let containerSize: CGSize + let balance: Int64? + let options: [StarsTopUpOption] + let peerId: EnginePeer.Id? + let requiredStars: Int64? + let selectedProductId: String? + let forceDark: Bool + let products: [StarsProduct]? + let expanded: Bool + let stateUpdated: (Transition) -> Void + let buy: (StarsProduct) -> Void + + init( + context: AccountContext, + externalState: ExternalState, + containerSize: CGSize, + balance: Int64?, + options: [StarsTopUpOption], + peerId: EnginePeer.Id?, + requiredStars: Int64?, + selectedProductId: String?, + forceDark: Bool, + products: [StarsProduct]?, + expanded: Bool, + stateUpdated: @escaping (Transition) -> Void, + buy: @escaping (StarsProduct) -> Void + ) { + self.context = context + self.externalState = externalState + self.containerSize = containerSize + self.balance = balance + self.options = options + self.peerId = peerId + self.requiredStars = requiredStars + self.selectedProductId = selectedProductId + 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.containerSize != rhs.containerSize { + 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.selectedProductId != rhs.selectedProductId { + 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? + + 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 strings = environment.strings + 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) + + let textString: String + if let _ = context.component.requiredStars { + textString = strings.Stars_Purchase_StarsNeededInfo(state.peer?.compactDisplayTitle ?? "").string + } else { + textString = strings.Stars_Purchase_GetStarsInfo + } + + 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 + + context.component.externalState.descriptionHeight = text.size.height + + 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 + + size.height += 8.0 + + var i = 0 + var items: [AnyComponentWithIdentity] = [] + + if let products = state.products, let balance = context.component.balance { + var minimumCount: Int64? + if let requiredStars = context.component.requiredStars { + minimumCount = requiredStars - balance + } + for product in products { + if let minimumCount, minimumCount > product.option.count { + continue + } + + if let _ = minimumCount, items.isEmpty { + + } else if !context.component.expanded && !initialValues.contains(product.option.count) { + continue + } + + let title = strings.Stars_Purchase_Stars(Int32(product.option.count)) + 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 backgroundComponent: AnyComponent? + if product.storeProduct.id == context.component.selectedProductId { + backgroundComponent = AnyComponent( + ItemLoadingComponent(color: environment.theme.list.itemAccentColor) + ) + } else { + backgroundComponent = nil + } + + 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, + background: backgroundComponent, + 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 + ))), true), + 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) + }, + highlighting: .disabled, + updateIsHighlighted: { view, isHighlighted in + let transition: Transition = .easeInOut(duration: 0.25) + if let superview = view.superview { + transition.setScale(view: superview, scale: isHighlighted ? 0.9 : 1.0) + } + } + )))] + )) + )) + i += 1 + } + } + + if !context.component.expanded { + let titleComponent = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.Stars_Purchase_ShowMore, + 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 + + + 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 component = context.component + let termsText = termsText.update( + component: BalancedTextComponent( + text: .markdown(text: strings.Stars_Purchase_Info, 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 + component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Purchase_Terms_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) + } + ), + 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 + + if context.component.expanded { + size.height = max(size.height, component.containerSize.height + 150.0 + text.size.height) + } + + 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: Int64? + let forceDark: Bool + let updateInProgress: (Bool) -> Void + let present: (ViewController) -> Void + let completion: (Int64) -> Void + + init( + context: AccountContext, + starsContext: StarsContext, + options: [StarsTopUpOption], + peerId: EnginePeer.Id?, + requiredStars: Int64?, + forceDark: Bool, + updateInProgress: @escaping (Bool) -> Void, + present: @escaping (ViewController) -> Void, + completion: @escaping (Int64) -> 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: (Int64) -> Void + + var topContentOffset: CGFloat? + var bottomContentOffset: CGFloat? + + var hasIdleAnimations = true + + var progressProduct: StarsProduct? + + 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() + + init( + context: AccountContext, + starsContext: StarsContext, + initialOptions: [StarsTopUpOption], + updateInProgress: @escaping (Bool) -> Void, + present: @escaping (ViewController) -> Void, + completion: @escaping (Int64) -> 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.progressProduct == nil else { + return + } + + self.progressProduct = product + self.updateInProgress(true) + self.updated(transition: .easeInOut(duration: 0.2)) + + 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.updateInProgress(false) + + self.updated(transition: .easeInOut(duration: 0.2)) + self.completion(product.option.count) + } + }, error: { [weak self] error in + if let strongSelf = self { + strongSelf.progressProduct = nil + strongSelf.updateInProgress(false) + strongSelf.updated(transition: .easeInOut(duration: 0.2)) + + 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 .tryLater: + errorText = presentationData.strings.Premium_Purchase_ErrorTryLater + 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.progressProduct = nil + strongSelf.updateInProgress(false) + strongSelf.updated(transition: .easeInOut(duration: 0.2)) + } + } + }) + } + + 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 balanceTitle = Child(MultilineTextComponent.self) + let balanceValue = Child(MultilineTextComponent.self) + let balanceIcon = Child(BundleIconComponent.self) + + let scrollAction = ActionSlot() + + let contentExternalState = StarsPurchaseScreenContentComponent.ExternalState() + + return { context in + let environment = context.environment[EnvironmentType.self].value + let state = context.state + + let strings = environment.strings + + 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( + theme: environment.theme, + isIntro: true, + isVisible: starIsVisible, + hasIdleAnimations: state.hasIdleAnimations, + colors: [ + UIColor(rgb: 0xe57d02), + UIColor(rgb: 0xf09903), + UIColor(rgb: 0xf9b004), + UIColor(rgb: 0xfdd219) + ], + particleColor: UIColor(rgb: 0xf9b004) + ), + 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 + ) + + let titleText: String + if let requiredStars = context.component.requiredStars { + titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars)) + } else { + titleText = strings.Stars_Purchase_GetStars + } + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString(string: titleText, font: Font.bold(28.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)), + horizontalAlignment: .center, + truncationType: .end, + maximumNumberOfLines: 1 + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let balanceTitle = balanceTitle.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Stars_Purchase_Balance, + font: Font.regular(14.0), + textColor: environment.theme.actionSheet.primaryTextColor + )), + maximumNumberOfLines: 1 + ), + availableSize: context.availableSize, + transition: .immediate + ) + let balanceValue = balanceValue.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: presentationStringsFormattedNumber(Int32(state.starsState?.balance ?? 0), environment.dateTimeFormat.groupingSeparator), + font: Font.semibold(14.0), + textColor: environment.theme.actionSheet.primaryTextColor + )), + maximumNumberOfLines: 1 + ), + availableSize: context.availableSize, + transition: .immediate + ) + let balanceIcon = balanceIcon.update( + component: BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil), + availableSize: context.availableSize, + transition: .immediate + ) + + let scrollContent = scrollContent.update( + component: ScrollComponent( + content: AnyComponent(StarsPurchaseScreenContentComponent( + context: context.component.context, + externalState: contentExternalState, + containerSize: context.availableSize, + balance: state.starsState?.balance, + options: context.component.options, + peerId: context.component.peerId, + requiredStars: context.component.requiredStars, + selectedProductId: state.progressProduct?.storeProduct.id, + forceDark: context.component.forceDark, + products: state.products, + expanded: state.isExpanded, + stateUpdated: { [weak state] transition in + scrollAction.invoke(CGPoint(x: 0.0, y: 150.0 + contentExternalState.descriptionHeight)) + 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 + let anchorOffset = 150.0 + contentExternalState.descriptionHeight + if targetContentOffset.pointee.y < 100.0 { + targetContentOffset.pointee = CGPoint(x: 0.0, y: 0.0) + } else if targetContentOffset.pointee.y < anchorOffset { + targetContentOffset.pointee = CGPoint(x: 0.0, y: anchorOffset) + } + }, + 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) + ) + + let navigationHeight = environment.navigationHeight - environment.statusBarHeight + let topBalanceOriginY = environment.statusBarHeight + (navigationHeight - balanceTitle.size.height - balanceValue.size.height) / 2.0 + context.add(balanceTitle + .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceTitle.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height / 2.0)) + ) + context.add(balanceValue + .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceValue.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0)) + ) + context.add(balanceIcon + .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceValue.size.width - balanceIcon.size.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 - UIScreenPixel)) + ) + + 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: Int64?, + modal: Bool = true, + forceDark: Bool = false, + completion: @escaping (Int64) -> Void = { _ in } + ) { + self.context = context + self.starsContext = starsContext + self.options = options + + var updateInProgressImpl: ((Bool) -> Void)? + var presentImpl: ((ViewController) -> Void)? + var completionImpl: ((Int64) -> 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: { stars in + completionImpl?(stars) + } + ), 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] stars in + if let self { + self.animateSuccess() + + completion(stars) + } + } + } + + 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) + + let mainImage = UIImage(bundleImageName: "Premium/Stars/StarLarge") + if let cgImage = mainImage?.cgImage, let partCGImage = partImage.cgImage { + context.draw(cgImage, in: CGRect(origin: CGPoint(x: originX, y: 0.0), size: imageSize), byTiling: false) + originX += spacing + UIScreenPixel + + for _ in 0 ..< count - 1 { + context.draw(partCGImage, in: CGRect(origin: CGPoint(x: originX, y: -UIScreenPixel), size: imageSize).insetBy(dx: -1.0 + UIScreenPixel, dy: -1.0 + UIScreenPixel), 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 00000000000..518c2783e2c --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD @@ -0,0 +1,47 @@ +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", + "//submodules/PhotoResources", + "//submodules/TelegramUI/Components/Stars/StarsImageComponent", + ], + 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 00000000000..66546529efe --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift @@ -0,0 +1,169 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import AccountContext +import MultilineTextComponent +import TelegramPresentationData +import PresentationDataUtils +import SolidRoundedButtonComponent + +final class StarsBalanceComponent: Component { + let theme: PresentationTheme + let strings: PresentationStrings + let dateTimeFormat: PresentationDateTimeFormat + let count: Int64 + let purchaseAvailable: Bool + let buy: () -> Void + + init( + theme: PresentationTheme, + strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, + count: Int64, + purchaseAvailable: Bool, + buy: @escaping () -> Void + ) { + self.theme = theme + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.count = count + self.purchaseAvailable = purchaseAvailable + 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.dateTimeFormat != rhs.dateTimeFormat { + return false + } + if lhs.purchaseAvailable != rhs.purchaseAvailable { + 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/BalanceStar") + + 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 + var contentHeight: CGFloat = sideInset + + let balanceString = presentationStringsFormattedNumber(Int32(component.count), component.dateTimeFormat.groupingSeparator) + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: balanceString, font: Font.with(size: 48.0, design: .round, weight: .semibold), textColor: component.theme.list.itemPrimaryTextColor)) + ) + ), + 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: contentHeight - 3.0), size: titleSize) + titleView.frame = titleFrame + + self.icon.frame = CGRect(origin: CGPoint(x: origin, y: contentHeight + 2.0), size: icon.size) + } + } + contentHeight += titleSize.height + + let subtitleSize = self.subtitle.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: component.strings.Stars_Intro_YourBalance, 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: contentHeight - 4.0), size: subtitleSize) + subtitleView.frame = subtitleFrame + } + contentHeight += subtitleSize.height + + if component.purchaseAvailable { + contentHeight += 12.0 + + let buttonSize = self.button.update( + transition: .immediate, + component: AnyComponent( + SolidRoundedButtonComponent( + title: component.strings.Stars_Intro_Buy, + 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: contentHeight), size: buttonSize) + buttonView.frame = buttonFrame + } + contentHeight += buttonSize.height + } + contentHeight += sideInset + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + 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/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift new file mode 100644 index 00000000000..a3e2450ba23 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift @@ -0,0 +1,1228 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import TelegramCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import PresentationDataUtils +import ComponentFlow +import ViewControllerComponent +import SheetComponent +import MultilineTextComponent +import BundleIconComponent +import SolidRoundedButtonComponent +import Markdown +import BalancedTextComponent +import AvatarNode +import TextFormat +import TelegramStringFormatting +import UndoUI +import StarsImageComponent + +private final class StarsTransactionSheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let subject: StarsTransactionScreen.Subject + let action: () -> Void + let cancel: (Bool) -> Void + let openPeer: (EnginePeer) -> Void + let copyTransactionId: () -> Void + + init( + context: AccountContext, + subject: StarsTransactionScreen.Subject, + action: @escaping () -> Void, + cancel: @escaping (Bool) -> Void, + openPeer: @escaping (EnginePeer) -> Void, + copyTransactionId: @escaping () -> Void + ) { + self.context = context + self.subject = subject + self.action = action + self.cancel = cancel + self.openPeer = openPeer + self.copyTransactionId = copyTransactionId + } + + static func ==(lhs: StarsTransactionSheetContent, rhs: StarsTransactionSheetContent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.subject != rhs.subject { + return false + } + return true + } + + final class State: ComponentState { + private let context: AccountContext + private var disposable: Disposable? + var initialized = false + + var peerMap: [EnginePeer.Id: EnginePeer] = [:] + + var cachedCloseImage: (UIImage, PresentationTheme)? + + var inProgress = false + + init(context: AccountContext, subject: StarsTransactionScreen.Subject) { + self.context = context + + super.init() + + var peerIds: [EnginePeer.Id] = [] + switch subject { + case let .transaction(transaction): + if case let .peer(peer) = transaction.peer { + peerIds.append(peer.id) + } + case let .receipt(receipt): + peerIds.append(receipt.botPaymentId) + } + + self.disposable = (context.engine.data.get( + EngineDataMap( + peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in + return TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + } + ) + ) |> deliverOnMainQueue).startStrict(next: { [weak self] peers in + if let strongSelf = self { + var peersMap: [EnginePeer.Id: EnginePeer] = [:] + for peerId in peerIds { + if let maybePeer = peers[peerId], let peer = maybePeer { + peersMap[peerId] = peer + } + } + strongSelf.peerMap = peersMap + strongSelf.initialized = true + + strongSelf.updated(transition: .immediate) + } + }) + } + + deinit { + self.disposable?.dispose() + } + } + + func makeState() -> State { + return State(context: self.context, subject: self.subject) + } + + static var body: Body { + let closeButton = Child(Button.self) + let title = Child(MultilineTextComponent.self) + let star = Child(StarsImageComponent.self) + let amount = Child(BalancedTextComponent.self) + let amountStar = Child(BundleIconComponent.self) + let description = Child(MultilineTextComponent.self) + let table = Child(TableComponent.self) + let additional = Child(BalancedTextComponent.self) + let button = Child(SolidRoundedButtonComponent.self) + + let refundBackgound = Child(RoundedRectangle.self) + let refundText = Child(MultilineTextComponent.self) + + return { context in + let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + let controller = environment.controller + + let component = context.component + let theme = environment.theme + let strings = environment.strings + let dateTimeFormat = environment.dateTimeFormat + + let state = context.state + let subject = component.subject + + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let textSideInset: CGFloat = 32.0 + environment.safeInsets.left + + let closeImage: UIImage + if let (image, theme) = state.cachedCloseImage, theme === environment.theme { + closeImage = image + } else { + closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)! + state.cachedCloseImage = (closeImage, theme) + } + + let closeButton = closeButton.update( + component: Button( + content: AnyComponent(Image(image: closeImage)), + action: { [weak component] in + component?.cancel(true) + } + ), + availableSize: CGSize(width: 30.0, height: 30.0), + transition: .immediate + ) + + let titleText: String + let amountText: String + let descriptionText: String + let additionalText: String + let buttonText: String + + let count: Int64 + let transactionId: String? + let date: Int32 + let via: String? + let toPeer: EnginePeer? + let transactionPeer: StarsContext.State.Transaction.Peer? + let photo: TelegramMediaWebFile? + let isRefund: Bool + + var delayedCloseOnOpenPeer = true + switch subject { + case let .transaction(transaction): + switch transaction.peer { + case let .peer(peer): + titleText = transaction.title ?? peer.compactDisplayTitle + via = nil + case .appStore: + titleText = strings.Stars_Transaction_AppleTopUp_Title + via = strings.Stars_Transaction_AppleTopUp_Subtitle + case .playMarket: + titleText = strings.Stars_Transaction_GoogleTopUp_Title + via = strings.Stars_Transaction_GoogleTopUp_Subtitle + case .premiumBot: + titleText = strings.Stars_Transaction_PremiumBotTopUp_Title + via = strings.Stars_Transaction_PremiumBotTopUp_Subtitle + case .fragment: + titleText = strings.Stars_Transaction_FragmentTopUp_Title + via = strings.Stars_Transaction_FragmentTopUp_Subtitle + case .unsupported: + titleText = strings.Stars_Transaction_Unsupported_Title + via = nil + } + descriptionText = transaction.description ?? "" + + count = transaction.count + transactionId = transaction.id + date = transaction.date + if case let .peer(peer) = transaction.peer { + toPeer = peer + } else { + toPeer = nil + } + transactionPeer = transaction.peer + photo = transaction.photo + isRefund = transaction.flags.contains(.isRefund) + case let .receipt(receipt): + titleText = receipt.invoiceMedia.title + descriptionText = receipt.invoiceMedia.description + count = (receipt.invoice.prices.first?.amount ?? receipt.invoiceMedia.totalAmount) * -1 + via = nil + transactionId = receipt.transactionId + date = receipt.date + if let peer = state.peerMap[receipt.botPaymentId] { + toPeer = peer + } else { + toPeer = nil + } + transactionPeer = nil + photo = receipt.invoiceMedia.photo + isRefund = false + delayedCloseOnOpenPeer = false + } + + let formattedAmount = presentationStringsFormattedNumber(abs(Int32(count)), dateTimeFormat.groupingSeparator) + if count < 0 { + amountText = "- \(formattedAmount)" + } else { + amountText = "+ \(formattedAmount)" + } + additionalText = strings.Stars_Transaction_Terms + buttonText = strings.Common_OK + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: titleText, + font: Font.bold(25.0), + textColor: theme.actionSheet.primaryTextColor, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + + let imageSubject: StarsImageComponent.Subject + if let photo { + imageSubject = .photo(photo) + } else if let transactionPeer { + imageSubject = .transactionPeer(transactionPeer) + } else if let toPeer { + imageSubject = .transactionPeer(.peer(toPeer)) + } else { + imageSubject = .none + } + let star = star.update( + component: StarsImageComponent( + context: component.context, + subject: imageSubject, + theme: theme, + diameter: 90.0 + ), + availableSize: CGSize(width: context.availableSize.width, height: 200.0), + transition: .immediate + ) + + let amountAttributedText = NSMutableAttributedString(string: amountText, font: Font.semibold(17.0), textColor: amountText.hasPrefix("-") ? theme.list.itemDestructiveColor : theme.list.itemDisclosureActions.constructive.fillColor) + let amount = amount.update( + component: BalancedTextComponent( + text: .plain(amountAttributedText), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + + let amountStar = amountStar.update( + component: BundleIconComponent( + name: "Premium/Stars/StarMedium", + tintColor: nil + ), + availableSize: context.availableSize, + transition: .immediate + ) + + let tableFont = Font.regular(15.0) + let tableTextColor = theme.list.itemPrimaryTextColor + let tableLinkColor = theme.list.itemAccentColor + var tableItems: [TableComponent.Item] = [] + + if let toPeer { + tableItems.append(.init( + id: "to", + title: count < 0 ? strings.Stars_Transaction_To : strings.Stars_Transaction_From, + component: AnyComponent( + Button( + content: AnyComponent( + PeerCellComponent( + context: component.context, + textColor: tableLinkColor, + peer: toPeer + ) + ), + action: { + if delayedCloseOnOpenPeer { + component.openPeer(toPeer) + Queue.mainQueue().after(1.0, { + component.cancel(false) + }) + } else { + if let controller = controller() as? StarsTransactionScreen, let navigationController = controller.navigationController, let chatController = navigationController.viewControllers.first(where: { $0 is ChatController }) as? ChatController { + chatController.playShakeAnimation() + } + component.cancel(true) + } + } + ) + ) + )) + } else if let via { + tableItems.append(.init( + id: "via", + title: strings.Stars_Transaction_Via, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: via, font: tableFont, textColor: tableTextColor))) + ) + )) + } + + if let transactionId { + tableItems.append(.init( + id: "transaction", + title: strings.Stars_Transaction_Id, + component: AnyComponent( + Button( + content: AnyComponent( + TransactionCellComponent( + textColor: tableTextColor, + accentColor: tableLinkColor, + transactionId: transactionId + ) + ), + action: { + component.copyTransactionId() + } + ) + ), + insets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 5.0) + )) + } + + tableItems.append(.init( + id: "date", + title: strings.Stars_Transaction_Date, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: date, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor))) + ) + )) + + let table = table.update( + component: TableComponent( + theme: environment.theme, + items: tableItems + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude), + transition: .immediate + ) + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + let textColor = theme.actionSheet.secondaryTextColor + 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 additional = additional.update( + component: BalancedTextComponent( + text: .markdown(text: additionalText, attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.1, + highlightColor: linkColor.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 + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Transaction_Terms_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) + } + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + + let button = button.update( + component: SolidRoundedButtonComponent( + title: buttonText, + theme: SolidRoundedButtonComponent.Theme(theme: theme), + font: .bold, + fontSize: 17.0, + height: 50.0, + cornerRadius: 10.0, + gloss: false, + iconName: nil, + animationName: nil, + iconPosition: .left, + isLoading: state.inProgress, + action: { + component.cancel(true) + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + transition: context.transition + ) + + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: 31.0 + 125.0)) + ) + + context.add(star + .position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 19.0)) + ) + + var originY: CGFloat = 0.0 + originY += star.size.height - 23.0 + + if !descriptionText.isEmpty { + let description = description.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: descriptionText, + font: Font.regular(15.0), + textColor: theme.actionSheet.primaryTextColor, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 3 + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + context.add(description + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + description.size.height / 2.0)) + ) + originY += description.size.height + 10.0 + } + + let amountSpacing: CGFloat = 3.0 + var totalAmountWidth: CGFloat = amount.size.width + amountSpacing + amountStar.size.width + var amountOriginX: CGFloat = floor(context.availableSize.width - totalAmountWidth) / 2.0 + if isRefund { + let refundText = refundText.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.Stars_Transaction_Refund, + font: Font.medium(14.0), + textColor: theme.list.itemDisclosureActions.constructive.fillColor + )) + ), + availableSize: context.availableSize, + transition: .immediate + ) + let refundBackground = refundBackgound.update( + component: RoundedRectangle( + color: theme.list.itemDisclosureActions.constructive.fillColor.withAlphaComponent(0.1), + cornerRadius: 6.0 + ), + availableSize: CGSize(width: refundText.size.width + 10.0, height: refundText.size.height + 4.0), + transition: .immediate + ) + totalAmountWidth += amountSpacing * 2.0 + refundBackground.size.width + amountOriginX = floor(context.availableSize.width - totalAmountWidth) / 2.0 + + context.add(refundBackground + .position(CGPoint(x: amountOriginX + amount.size.width + amountSpacing + amountStar.size.width + amountSpacing * 2.0 + refundBackground.size.width / 2.0, y: originY + refundBackground.size.height / 2.0)) + ) + context.add(refundText + .position(CGPoint(x: amountOriginX + amount.size.width + amountSpacing + amountStar.size.width + amountSpacing * 2.0 + refundBackground.size.width / 2.0, y: originY + refundBackground.size.height / 2.0)) + ) + } + + context.add(amount + .position(CGPoint(x: amountOriginX + amount.size.width / 2.0, y: originY + amount.size.height / 2.0)) + ) + context.add(amountStar + .position(CGPoint(x: amountOriginX + amount.size.width + amountSpacing + amountStar.size.width / 2.0, y: originY + amountStar.size.height / 2.0)) + ) + + originY += amount.size.height + 20.0 + + context.add(table + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0)) + ) + originY += table.size.height + 23.0 + + context.add(additional + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + additional.size.height / 2.0)) + ) + originY += additional.size.height + 23.0 + + let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: originY), size: button.size) + context.add(button + .position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY)) + ) + + context.add(closeButton + .position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0)) + ) + + let contentSize = CGSize(width: context.availableSize.width, height: buttonFrame.maxY + 5.0 + environment.safeInsets.bottom) + + return contentSize + } + } +} + +private final class StarsTransactionSheetComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let subject: StarsTransactionScreen.Subject + let action: () -> Void + let openPeer: (EnginePeer) -> Void + let copyTransactionId: () -> Void + + init( + context: AccountContext, + subject: StarsTransactionScreen.Subject, + action: @escaping () -> Void, + openPeer: @escaping (EnginePeer) -> Void, + copyTransactionId: @escaping () -> Void + ) { + self.context = context + self.subject = subject + self.action = action + self.openPeer = openPeer + self.copyTransactionId = copyTransactionId + } + + static func ==(lhs: StarsTransactionSheetComponent, rhs: StarsTransactionSheetComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.subject != rhs.subject { + return false + } + return true + } + + static var body: Body { + let sheet = Child(SheetComponent.self) + let animateOut = StoredActionSlot(Action.self) + + let sheetExternalState = SheetComponent.ExternalState() + + return { context in + let environment = context.environment[EnvironmentType.self] + let controller = environment.controller + + let sheet = sheet.update( + component: SheetComponent( + content: AnyComponent(StarsTransactionSheetContent( + context: context.component.context, + subject: context.component.subject, + action: context.component.action, + cancel: { animate in + if animate { + if let controller = controller() as? StarsTransactionScreen { + controller.dismissAllTooltips() + animateOut.invoke(Action { [weak controller] _ in + controller?.dismiss(completion: nil) + }) + } + } else if let controller = controller() { + controller.dismiss(animated: false, completion: nil) + } + }, + openPeer: context.component.openPeer, + copyTransactionId: context.component.copyTransactionId + )), + backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), + followContentSizeChanges: true, + clipsContent: true, + externalState: sheetExternalState, + animateOut: animateOut, + onPan: { + if let controller = controller() as? StarsTransactionScreen { + controller.dismissAllTooltips() + } + } + ), + 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 { + if let controller = controller() as? StarsTransactionScreen { + controller.dismissAllTooltips() + animateOut.invoke(Action { _ in + controller.dismiss(completion: nil) + }) + } + } else { + if let controller = controller() as? StarsTransactionScreen { + controller.dismissAllTooltips() + 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)) + ) + + if let controller = controller(), !controller.automaticallyControlPresentationContextLayout { + let layout = ContainerViewLayout( + size: context.availableSize, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: max(environment.safeInsets.bottom, sheetExternalState.contentHeight), right: 0.0), + safeInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right), + additionalInsets: .zero, + statusBarHeight: environment.statusBarHeight, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ) + controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition) + } + + return context.availableSize + } + } +} + +public class StarsTransactionScreen: ViewControllerComponentContainer { + public enum Subject: Equatable { + case transaction(StarsContext.State.Transaction) + case receipt(BotPaymentReceipt) + } + + private let context: AccountContext + public var disposed: () -> Void = {} + + private let hapticFeedback = HapticFeedback() + + public init( + context: AccountContext, + subject: StarsTransactionScreen.Subject, + forceDark: Bool = false, + action: @escaping () -> Void + ) { + self.context = context + + var openPeerImpl: ((EnginePeer) -> Void)? + var copyTransactionIdImpl: (() -> Void)? + super.init( + context: context, + component: StarsTransactionSheetComponent( + context: context, + subject: subject, + action: action, + openPeer: { peerId in + openPeerImpl?(peerId) + }, + copyTransactionId: { + copyTransactionIdImpl?() + } + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: forceDark ? .dark : .default + ) + + self.navigationPresentation = .flatModal + self.automaticallyControlPresentationContextLayout = false + + openPeerImpl = { [weak self] peer in + guard let self, let navigationController = self.navigationController as? NavigationController else { + return + } + self.dismissAllTooltips() + + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id) + ) + |> deliverOnMainQueue).start(next: { peer in + guard let peer else { + return + } + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), subject: nil, botStart: nil, updateTextInputState: nil, keepStack: .always, useExisting: true, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: nil, animated: true)) + }) + } + + copyTransactionIdImpl = { [weak self] in + guard let self else { + return + } + self.dismissAllTooltips() + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Stars_Transaction_CopiedId), elevatedLayout: false, position: .bottom, action: { _ in return true }), in: .current) + + HapticFeedback().tap() + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.disposed() + } + + public override func viewDidLoad() { + super.viewDidLoad() + + self.view.disablesInteractiveModalDismiss = true + } + + public override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.dismissAllTooltips() + } + + public func dismissAnimated() { + self.dismissAllTooltips() + + if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { + view.dismissAnimated() + } + } + + 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 + }) + } +} + +private final class TableComponent: CombinedComponent { + class Item: Equatable { + public let id: AnyHashable + public let title: String + public let component: AnyComponent + public let insets: UIEdgeInsets? + + public init(id: IdType, title: String, component: AnyComponent, insets: UIEdgeInsets? = nil) { + self.id = AnyHashable(id) + self.title = title + self.component = component + self.insets = insets + } + + public static func == (lhs: Item, rhs: Item) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.component != rhs.component { + return false + } + if lhs.insets != rhs.insets { + return false + } + return true + } + } + + private let theme: PresentationTheme + private let items: [Item] + + public init(theme: PresentationTheme, items: [Item]) { + self.theme = theme + self.items = items + } + + public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.items != rhs.items { + return false + } + return true + } + + final class State: ComponentState { + var cachedBorderImage: (UIImage, PresentationTheme)? + } + + func makeState() -> State { + return State() + } + + public static var body: Body { + let leftColumnBackground = Child(Rectangle.self) + let verticalBorder = Child(Rectangle.self) + let titleChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) + let valueChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) + let borderChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) + let outerBorder = Child(Image.self) + + return { context in + let verticalPadding: CGFloat = 11.0 + let horizontalPadding: CGFloat = 12.0 + let borderWidth: CGFloat = 1.0 + + let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor + let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6) + + var leftColumnWidth: CGFloat = 0.0 + + var updatedTitleChildren: [_UpdatedChildComponent] = [] + var updatedValueChildren: [(_UpdatedChildComponent, UIEdgeInsets)] = [] + var updatedBorderChildren: [_UpdatedChildComponent] = [] + + for item in context.component.items { + let titleChild = titleChildren[item.id].update( + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: item.title, font: Font.regular(15.0), textColor: context.component.theme.list.itemPrimaryTextColor)) + )), + availableSize: context.availableSize, + transition: context.transition + ) + updatedTitleChildren.append(titleChild) + + if titleChild.size.width > leftColumnWidth { + leftColumnWidth = titleChild.size.width + } + } + + leftColumnWidth = max(100.0, leftColumnWidth + horizontalPadding * 2.0) + let rightColumnWidth = context.availableSize.width - leftColumnWidth + + var i = 0 + var rowHeights: [Int: CGFloat] = [:] + var totalHeight: CGFloat = 0.0 + + for item in context.component.items { + let titleChild = updatedTitleChildren[i] + + let insets: UIEdgeInsets + if let customInsets = item.insets { + insets = customInsets + } else { + insets = UIEdgeInsets(top: 0.0, left: horizontalPadding, bottom: 0.0, right: horizontalPadding) + } + let valueChild = valueChildren[item.id].update( + component: item.component, + availableSize: CGSize(width: rightColumnWidth - insets.left - insets.right, height: context.availableSize.height), + transition: context.transition + ) + updatedValueChildren.append((valueChild, insets)) + + let rowHeight = max(40.0, max(titleChild.size.height, valueChild.size.height) + verticalPadding * 2.0) + rowHeights[i] = rowHeight + totalHeight += rowHeight + + if i < context.component.items.count - 1 { + let borderChild = borderChildren[item.id].update( + component: AnyComponent(Rectangle(color: borderColor)), + availableSize: CGSize(width: context.availableSize.width, height: borderWidth), + transition: context.transition + ) + updatedBorderChildren.append(borderChild) + } + + i += 1 + } + + let leftColumnBackground = leftColumnBackground.update( + component: Rectangle(color: context.component.theme.list.itemInputField.backgroundColor), + availableSize: CGSize(width: leftColumnWidth, height: totalHeight), + transition: context.transition + ) + context.add( + leftColumnBackground + .position(CGPoint(x: leftColumnWidth / 2.0, y: totalHeight / 2.0)) + ) + + let borderImage: UIImage + if let (currentImage, theme) = context.state.cachedBorderImage, theme === context.component.theme { + borderImage = currentImage + } else { + let borderRadius: CGFloat = 5.0 + borderImage = generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in + let bounds = CGRect(origin: .zero, size: size) + context.setFillColor(backgroundColor.cgColor) + context.fill(bounds) + + let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil) + context.setBlendMode(.clear) + context.addPath(path) + context.fillPath() + + context.setBlendMode(.normal) + context.setStrokeColor(borderColor.cgColor) + context.setLineWidth(borderWidth) + context.addPath(path) + context.strokePath() + })!.stretchableImage(withLeftCapWidth: 5, topCapHeight: 5) + context.state.cachedBorderImage = (borderImage, context.component.theme) + } + + let outerBorder = outerBorder.update( + component: Image(image: borderImage), + availableSize: CGSize(width: context.availableSize.width, height: totalHeight), + transition: context.transition + ) + context.add(outerBorder + .position(CGPoint(x: context.availableSize.width / 2.0, y: totalHeight / 2.0)) + ) + + let verticalBorder = verticalBorder.update( + component: Rectangle(color: borderColor), + availableSize: CGSize(width: borderWidth, height: totalHeight), + transition: context.transition + ) + context.add( + verticalBorder + .position(CGPoint(x: leftColumnWidth - borderWidth / 2.0, y: totalHeight / 2.0)) + ) + + i = 0 + var originY: CGFloat = 0.0 + for (titleChild, (valueChild, valueInsets)) in zip(updatedTitleChildren, updatedValueChildren) { + let rowHeight = rowHeights[i] ?? 0.0 + + let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size) + let valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + valueInsets.left, y: originY + verticalPadding), size: valueChild.size) + + context.add(titleChild + .position(titleFrame.center) + ) + + context.add(valueChild + .position(valueFrame.center) + ) + + if i < updatedBorderChildren.count { + let borderChild = updatedBorderChildren[i] + context.add(borderChild + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + rowHeight - borderWidth / 2.0)) + ) + } + + originY += rowHeight + i += 1 + } + + return CGSize(width: context.availableSize.width, height: totalHeight) + } + } +} + +private final class PeerCellComponent: Component { + let context: AccountContext + let textColor: UIColor + let peer: EnginePeer? + + init(context: AccountContext, textColor: UIColor, peer: EnginePeer?) { + self.context = context + self.textColor = textColor + self.peer = peer + } + + static func ==(lhs: PeerCellComponent, rhs: PeerCellComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.textColor !== rhs.textColor { + return false + } + if lhs.peer != rhs.peer { + return false + } + return true + } + + final class View: UIView { + private let avatarNode: AvatarNode + private let text = ComponentView() + + private var component: PeerCellComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 13.0)) + + super.init(frame: frame) + + self.addSubnode(self.avatarNode) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: PeerCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.state = state + + self.avatarNode.setPeer( + context: component.context, + theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme, + peer: component.peer, + synchronousLoad: true + ) + + let avatarSize = CGSize(width: 22.0, height: 22.0) + let spacing: CGFloat = 6.0 + + let textSize = self.text.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: component.peer?.compactDisplayTitle ?? "", font: Font.regular(15.0), textColor: component.textColor, paragraphAlignment: .left)) + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - avatarSize.width - spacing, height: availableSize.height) + ) + + let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height) + + let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize) + self.avatarNode.frame = avatarFrame + + if let view = self.text.view { + if view.superview == nil { + self.addSubview(view) + } + let textFrame = CGRect(origin: CGPoint(x: avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + transition.setFrame(view: view, frame: textFrame) + } + + 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 TransactionCellComponent: Component { + let textColor: UIColor + let accentColor: UIColor + let transactionId: String + + init(textColor: UIColor, accentColor: UIColor, transactionId: String) { + self.textColor = textColor + self.accentColor = accentColor + self.transactionId = transactionId + } + + static func ==(lhs: TransactionCellComponent, rhs: TransactionCellComponent) -> Bool { + if lhs.textColor !== rhs.textColor { + return false + } + if lhs.accentColor != rhs.accentColor { + return false + } + if lhs.transactionId != rhs.transactionId { + return false + } + return true + } + + final class View: UIView { + private let text = ComponentView() + private let button = ComponentView() + + private var component: TransactionCellComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: TransactionCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.state = state + + let spacing: CGFloat = 6.0 + + let buttonSize = self.button.update( + transition: .immediate, + component: AnyComponent( + BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: component.accentColor) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: availableSize.height) + ) + + func brokenLine(_ string: String) -> String { + if string.count > 30 { + return string + } + let middleIndex = string.index(string.startIndex, offsetBy: string.count / 2) + var newString = string + newString.insert("\n", at: middleIndex) + return newString + } + + let text: String + if availableSize.width > 230.0 { + text = component.transactionId + } else { + text = brokenLine(component.transactionId) + } + + let textSize = self.text.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString( + string: text, + font: Font.monospace(15.0), + textColor: component.textColor, + paragraphAlignment: .left + )), + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - buttonSize.width - spacing, height: availableSize.height) + ) + + let size = CGSize(width: availableSize.width, height: textSize.height) + + let buttonFrame = CGRect(origin: CGPoint(x: availableSize.width - buttonSize.width - 2.0, y: floorToScreenPixels((size.height - buttonSize.height) / 2.0)), size: buttonSize) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + transition.setFrame(view: buttonView, frame: buttonFrame) + } + + let textFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - textSize.height) / 2.0) + 1.0), size: textSize) + if let textView = self.text.view { + if textView.superview == nil { + self.addSubview(textView) + } + transition.setFrame(view: textView, frame: textFrame) + } + + 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 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/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift new file mode 100644 index 00000000000..2efc927a33d --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -0,0 +1,709 @@ +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 +import PhotoResources + +private extension StarsContext.State.Transaction { + var extendedId: String { + if self.count > 0 { + return "\(id)_in" + } else { + return "\(id)_out" + } + } +} + +final class StarsTransactionsListPanelComponent: Component { + typealias EnvironmentType = StarsTransactionsPanelEnvironment + + let context: AccountContext + let transactionsContext: StarsTransactionsContext + let action: (StarsContext.State.Transaction) -> Void + + init( + context: AccountContext, + transactionsContext: StarsTransactionsContext, + action: @escaping (StarsContext.State.Transaction) -> Void + ) { + self.context = context + self.transactionsContext = transactionsContext + self.action = action + } + + static func ==(lhs: StarsTransactionsListPanelComponent, rhs: StarsTransactionsListPanelComponent) -> Bool { + if lhs.context !== rhs.context { + 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? + + private var items: [StarsContext.State.Transaction] = [] + private var itemsDisposable: Disposable? + private var currentLoadMoreId: String? + + 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 = 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) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.itemsDisposable?.dispose() + } + + 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 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 >= self.items.count { + continue + } + let item = self.items[index] + let id = item.extendedId + 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.scrollView.addSubview(separatorView) + } + + separatorView.backgroundColor = environment.theme.list.itemBlocksSeparatorColor + + let fontBaseDisplaySize = 17.0 + + let itemTitle: String + let itemSubtitle: String? + var itemDate: String + switch item.peer { + case let .peer(peer): + if let title = item.title { + itemTitle = title + itemSubtitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) + } else { + itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) + itemSubtitle = nil + } + case .appStore: + itemTitle = environment.strings.Stars_Intro_Transaction_AppleTopUp_Title + itemSubtitle = environment.strings.Stars_Intro_Transaction_AppleTopUp_Subtitle + case .playMarket: + itemTitle = environment.strings.Stars_Intro_Transaction_GoogleTopUp_Title + itemSubtitle = environment.strings.Stars_Intro_Transaction_GoogleTopUp_Subtitle + case .fragment: + itemTitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Title + itemSubtitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Subtitle + case .premiumBot: + itemTitle = environment.strings.Stars_Intro_Transaction_PremiumBotTopUp_Title + itemSubtitle = environment.strings.Stars_Intro_Transaction_PremiumBotTopUp_Subtitle + case .unsupported: + itemTitle = environment.strings.Stars_Intro_Transaction_Unsupported_Title + itemSubtitle = nil + } + + let itemLabel: NSAttributedString + let labelString: String + + let formattedLabel = presentationStringsFormattedNumber(abs(Int32(item.count)), environment.dateTimeFormat.groupingSeparator) + if item.count < 0 { + labelString = "- \(formattedLabel)" + } else { + labelString = "+ \(formattedLabel)" + } + itemLabel = NSAttributedString(string: labelString, font: Font.medium(fontBaseDisplaySize), textColor: labelString.hasPrefix("-") ? environment.theme.list.itemDestructiveColor : environment.theme.list.itemDisclosureActions.constructive.fillColor) + + itemDate = stringForMediumCompactDate(timestamp: item.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat) + if item.flags.contains(.isRefund) { + itemDate += " – \(environment.strings.Stars_Intro_Transaction_Refund)" + } + + var titleComponents: [AnyComponentWithIdentity] = [] + titleComponents.append( + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: itemTitle, + font: Font.semibold(fontBaseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))) + ) + if let itemSubtitle { + titleComponents.append( + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: itemSubtitle, + font: Font.regular(fontBaseDisplaySize * 16.0 / 17.0), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))) + ) + } + titleComponents.append( + AnyComponentWithIdentity(id: AnyHashable(2), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: itemDate, + font: Font.regular(floor(fontBaseDisplaySize * 14.0 / 17.0)), + textColor: environment.theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + ))) + ) + let _ = itemView.update( + transition: itemTransition, + component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)), + contentInsets: UIEdgeInsets(top: 9.0, left: environment.containerInsets.left, bottom: 8.0, right: environment.containerInsets.right), + leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(AvatarComponent(context: component.context, theme: environment.theme, peer: item.peer, photo: item.photo))), false), + 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 + } + if !item.flags.contains(.isLocal) { + component.action(item) + } + } + )), + environment: {}, + containerSize: CGSize(width: itemLayout.containerWidth, height: itemLayout.itemHeight) + ) + let itemFrame = itemLayout.itemFrame(for: index) + if let itemComponentView = itemView.view { + if itemComponentView.superview == nil { + if !transition.animation.isImmediate { + transition.animateAlpha(view: itemComponentView, from: 0.0, to: 1.0) + } + self.scrollView.addSubview(itemComponentView) + } + itemTransition.setFrame(view: itemComponentView, frame: itemFrame) + } + let sideInset: CGFloat = 60.0 + environment.containerInsets.left + 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) + } + + let bottomOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height) + let loadMore = bottomOffset < 100.0 + if environment.isCurrent, loadMore { + let lastId = self.items.last?.extendedId + if lastId != self.currentLoadMoreId || lastId == nil { + self.currentLoadMoreId = lastId + component.transactionsContext.loadMore() + } + } + } + + private var isUpdating = false + func update(component: StarsTransactionsListPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + self.component = component + + if self.itemsDisposable == nil { + self.itemsDisposable = (component.transactionsContext.state + |> deliverOnMainQueue).start(next: { [weak self, weak state] status in + guard let self else { + return + } + let wasEmpty = self.items.isEmpty + let hadLocalTransactions = self.items.contains(where: { $0.flags.contains(.isLocal) }) + + self.items = status.transactions + if !status.isLoading { + self.currentLoadMoreId = nil + } + if !self.isUpdating { + state?.updated(transition: wasEmpty || hadLocalTransactions ? .immediate : .easeInOut(duration: 0.2)) + } + }) + } + + 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(fontBaseDisplaySize * 16.0 / 17.0), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + AnyComponentWithIdentity(id: AnyHashable(2), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "abc", + 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: 9.0, left: 0.0, bottom: 8.0, right: 0.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: self.items.count + ) + 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 + let photo: TelegramMediaWebFile? + + init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer, photo: TelegramMediaWebFile?) { + self.context = context + self.theme = theme + self.peer = peer + self.photo = photo + } + + 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 + } + if lhs.photo != rhs.photo { + return false + } + return true + } + + final class View: UIView { + private let avatarNode: AvatarNode + private let backgroundView = UIImageView() + private let iconView = UIImageView() + private var imageNode: TransformImageNode? + + private let fetchDisposable = MetaDisposable() + + 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 = .scaleAspectFit + + self.addSubnode(self.avatarNode) + self.addSubview(self.backgroundView) + self.addSubview(self.iconView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.fetchDisposable.dispose() + } + + 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) + var iconInset: CGFloat = 3.0 + var iconOffset: CGFloat = 0.0 + + switch component.peer { + case let .peer(peer): + if let photo = component.photo { + let imageNode: TransformImageNode + if let current = self.imageNode { + imageNode = current + } else { + imageNode = TransformImageNode() + imageNode.contentAnimations = [.subsequentUpdates] + self.addSubview(imageNode.view) + self.imageNode = imageNode + + imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo)) + self.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict()) + } + + imageNode.frame = CGRect(origin: .zero, size: size) + imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + + self.backgroundView.isHidden = true + self.iconView.isHidden = true + self.avatarNode.isHidden = true + } else { + 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 = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0x2a9ef1).cgColor, + UIColor(rgb: 0x72d5fd).cgColor + ], + direction: .mirroredDiagonal + ) + self.backgroundView.isHidden = false + self.iconView.isHidden = false + self.avatarNode.isHidden = true + self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Apple") + case .playMarket: + self.backgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0x54cb68).cgColor, + UIColor(rgb: 0xa0de7e).cgColor + ], + direction: .mirroredDiagonal + ) + self.backgroundView.isHidden = false + self.iconView.isHidden = false + self.avatarNode.isHidden = true + self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Google") + case .fragment: + self.backgroundView.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1b1f24)) + self.backgroundView.isHidden = false + self.iconView.isHidden = false + self.avatarNode.isHidden = true + self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") + iconOffset = 2.0 + case .premiumBot: + iconInset = 7.0 + self.backgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0x6b93ff).cgColor, + UIColor(rgb: 0x6b93ff).cgColor, + UIColor(rgb: 0x8d77ff).cgColor, + UIColor(rgb: 0xb56eec).cgColor, + UIColor(rgb: 0xb56eec).cgColor + ], + direction: .mirroredDiagonal + ) + self.backgroundView.isHidden = false + self.iconView.isHidden = false + self.avatarNode.isHidden = true + self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white) + case .unsupported: + iconInset = 7.0 + self.backgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0xb1b1b1).cgColor, + UIColor(rgb: 0xcdcdcd).cgColor + ], + direction: .mirroredDiagonal + ) + self.backgroundView.isHidden = false + self.iconView.isHidden = false + self.avatarNode.isHidden = true + self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white) + } + + self.avatarNode.frame = CGRect(origin: .zero, size: size) + self.iconView.frame = CGRect(origin: .zero, size: size).insetBy(dx: iconInset, dy: iconInset).offsetBy(dx: 0.0, dy: iconOffset) + 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/StarLarge", + 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 - UIScreenPixel)) + ) + 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 00000000000..2311dd02f2a --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift @@ -0,0 +1,805 @@ +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 + let isCurrent: Bool + + init( + theme: PresentationTheme, + strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, + containerInsets: UIEdgeInsets, + isScrollable: Bool, + isCurrent: Bool + ) { + self.theme = theme + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.containerInsets = containerInsets + self.isScrollable = isScrollable + self.isCurrent = isCurrent + } + + 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 + } + if lhs.isCurrent != rhs.isCurrent { + 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 sideInset: CGFloat = 16.0 + let condensedPanelWidth: CGFloat = availableSize.width - sideInset * 2.0 + let headerSize = 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: CGSize(width: condensedPanelWidth, height: topPanelFrame.size.height) + ) + if let headerView = self.header.view { + if headerView.superview == nil { + self.addSubview(headerView) + } + transition.setFrame(view: headerView, frame: CGRect(origin: topPanelFrame.origin.offsetBy(dx: sideInset, dy: 0.0), size: headerSize)) + } + + 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 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, + isCurrent: self.currentId == panelItem.id + ) + + 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 00000000000..2659ac2f523 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -0,0 +1,771 @@ +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 BundleIconComponent +import TextFormat +import UndoUI + +final class StarsTransactionsScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let starsContext: StarsContext + let openTransaction: (StarsContext.State.Transaction) -> Void + let buy: () -> Void + + init( + context: AccountContext, + starsContext: StarsContext, + openTransaction: @escaping (StarsContext.State.Transaction) -> Void, + buy: @escaping () -> Void + ) { + self.context = context + self.starsContext = starsContext + self.openTransaction = openTransaction + 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 topBalanceTitleView = ComponentView() + private let topBalanceValueView = ComponentView() + private let topBalanceIconView = 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? + + private var previousBalance: Int64? + + private var allTransactionsContext: StarsTransactionsContext? + private var incomingTransactionsContext: StarsTransactionsContext? + private var outgoingTransactionsContext: StarsTransactionsContext? + + 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) + } + + let topBalanceAlpha = 1.0 - expansionDistanceFactor + if let view = self.topBalanceTitleView.view { + view.alpha = topBalanceAlpha + } + if let view = self.topBalanceValueView.view { + view.alpha = topBalanceAlpha + } + if let view = self.topBalanceIconView.view { + view.alpha = topBalanceAlpha + } + } + + let _ = self.panelContainer.updateEnvironment( + transition: transition, + environment: { + StarsTransactionsPanelContainerEnvironment(isScrollable: isLockedAtPanels) + } + ) + } + + 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( + theme: environment.theme, + isIntro: true, + isVisible: true, + hasIdleAnimations: true, + colors: [ + UIColor(rgb: 0xe57d02), + UIColor(rgb: 0xf09903), + UIColor(rgb: 0xf9b004), + UIColor(rgb: 0xfdd219) + ], + particleColor: UIColor(rgb: 0xf9b004) + )), + environment: {}, + containerSize: CGSize(width: min(414.0, availableSize.width), height: 220.0) + ) + let starFrame = CGRect(origin: .zero, size: starSize) + if let starView = self.starView.view { + if starView.superview == nil { + self.insertSubview(starView, aboveSubview: self.scrollView) + } + starTransition.setBounds(view: starView, bounds: starFrame) + } + + let titleSize = self.titleView.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: environment.strings.Stars_Intro_Title, 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 topBalanceTitleSize = self.topBalanceTitleView.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Stars_Intro_Balance, + font: Font.regular(14.0), + textColor: environment.theme.actionSheet.primaryTextColor + )), + maximumNumberOfLines: 1 + )), + environment: {}, + containerSize: CGSize(width: 120.0, height: 100.0) + ) + + let topBalanceValueSize = self.topBalanceValueView.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: presentationStringsFormattedNumber(Int32(self.starsState?.balance ?? 0), environment.dateTimeFormat.groupingSeparator), + font: Font.semibold(14.0), + textColor: environment.theme.actionSheet.primaryTextColor + )), + maximumNumberOfLines: 1 + )), + environment: {}, + containerSize: CGSize(width: 120.0, height: 100.0) + ) + let topBalanceIconSize = self.topBalanceIconView.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil)), + environment: {}, + containerSize: availableSize + ) + + let navigationHeight = environment.navigationHeight - environment.statusBarHeight + let topBalanceOriginY = environment.statusBarHeight + (navigationHeight - topBalanceTitleSize.height - topBalanceValueSize.height) / 2.0 + let topBalanceTitleFrame = CGRect(origin: CGPoint(x: availableSize.width - topBalanceTitleSize.width - 16.0 - environment.safeInsets.right, y: topBalanceOriginY), size: topBalanceTitleSize) + if let topBalanceTitleView = self.topBalanceTitleView.view { + if topBalanceTitleView.superview == nil { + topBalanceTitleView.alpha = 0.0 + self.addSubview(topBalanceTitleView) + } + starTransition.setFrame(view: topBalanceTitleView, frame: topBalanceTitleFrame) + } + + let topBalanceValueFrame = CGRect(origin: CGPoint(x: availableSize.width - topBalanceValueSize.width - 16.0 - environment.safeInsets.right, y: topBalanceTitleFrame.maxY), size: topBalanceValueSize) + if let topBalanceValueView = self.topBalanceValueView.view { + if topBalanceValueView.superview == nil { + topBalanceValueView.alpha = 0.0 + self.addSubview(topBalanceValueView) + } + starTransition.setFrame(view: topBalanceValueView, frame: topBalanceValueFrame) + } + + let topBalanceIconFrame = CGRect(origin: CGPoint(x: topBalanceValueFrame.minX - topBalanceIconSize.width - 2.0, y: floorToScreenPixels(topBalanceValueFrame.midY - topBalanceIconSize.height / 2.0) - UIScreenPixel), size: topBalanceIconSize) + if let topBalanceIconView = self.topBalanceIconView.view { + if topBalanceIconView.superview == nil { + topBalanceIconView.alpha = 0.0 + self.addSubview(topBalanceIconView) + } + starTransition.setFrame(view: topBalanceIconView, frame: topBalanceIconFrame) + } + + contentHeight += 181.0 + + let descriptionSize = self.descriptionView.update( + transition: .immediate, + component: AnyComponent( + BalancedTextComponent( + text: .plain(NSAttributedString(string: environment.strings.Stars_Intro_Description, 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 + 20.0 - floor(descriptionSize.height / 2.0)), 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 premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $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, + dateTimeFormat: environment.dateTimeFormat, + count: self.starsState?.balance ?? 0, + purchaseAvailable: !premiumConfiguration.areStarsDisabled, + 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 + + let initialTransactions = self.starsState?.transactions ?? [] + var panelItems: [StarsTransactionsPanelContainerComponent.Item] = [] + if !initialTransactions.isEmpty { + let allTransactionsContext: StarsTransactionsContext + if let current = self.allTransactionsContext { + allTransactionsContext = current + } else { + allTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(starsContext: component.starsContext, subject: .all) + } + + let incomingTransactionsContext: StarsTransactionsContext + if let current = self.incomingTransactionsContext { + incomingTransactionsContext = current + } else { + incomingTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(starsContext: component.starsContext, subject: .incoming) + } + + let outgoingTransactionsContext: StarsTransactionsContext + if let current = self.outgoingTransactionsContext { + outgoingTransactionsContext = current + } else { + outgoingTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(starsContext: component.starsContext, subject: .outgoing) + } + + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "all", + title: environment.strings.Stars_Intro_AllTransactions, + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + transactionsContext: allTransactionsContext, + action: { transaction in + component.openTransaction(transaction) + } + )) + )) + + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "incoming", + title: environment.strings.Stars_Intro_Incoming, + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + transactionsContext: incomingTransactionsContext, + action: { transaction in + component.openTransaction(transaction) + } + )) + )) + + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "outgoing", + title: environment.strings.Stars_Intro_Outgoing, + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + transactionsContext: outgoingTransactionsContext, + action: { transaction in + component.openTransaction(transaction) + } + )) + )) + } + + 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 starsContext: StarsContext + + private let options = Promise<[StarsTopUpOption]>() + + public init(context: AccountContext, starsContext: StarsContext, forceDark: Bool = false) { + self.context = context + self.starsContext = starsContext + + var buyImpl: (() -> Void)? + var openTransactionImpl: ((StarsContext.State.Transaction) -> Void)? + super.init(context: context, component: StarsTransactionsScreenComponent( + context: context, + starsContext: starsContext, + openTransaction: { transaction in + openTransactionImpl?(transaction) + }, + buy: { + buyImpl?() + } + ), navigationBarAppearance: .transparent) + + self.options.set(.single([]) |> then(context.engine.payments.starsTopUpOptions())) + + openTransactionImpl = { [weak self] transaction in + guard let self else { + return + } + let controller = context.sharedContext.makeStarsTransactionScreen(context: context, transaction: transaction) + self.push(controller) + } + + 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, completion: { [weak self] stars in + guard let self else { + return + } + self.starsContext.add(balance: stars) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let resultController = UndoOverlayController( + presentationData: presentationData, + content: .image( + image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!, + title: presentationData.strings.Stars_Intro_PurchasedTitle, + text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(stars))).string, + round: false, + undoText: nil + ), + elevatedLayout: false, + action: { _ in return true}) + self.present(resultController, in: .window(.root)) + }) + self.push(controller) + }) + } + + self.starsContext.load(force: false) + } + + 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 00000000000..d19426478da --- /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/Stars/StarsImageComponent", + ], + 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 00000000000..3d45440c82e --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -0,0 +1,635 @@ +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 BundleIconComponent +import ButtonComponent +import ItemListUI +import UndoUI +import AccountContext +import PresentationDataUtils +import StarsImageComponent + +private final class SheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let starsContext: StarsContext + let invoice: TelegramMediaInvoice + let source: BotPaymentInvoiceSource + let inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + let dismiss: () -> Void + + init( + context: AccountContext, + starsContext: StarsContext, + invoice: TelegramMediaInvoice, + source: BotPaymentInvoiceSource, + inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>, + dismiss: @escaping () -> Void + ) { + self.context = context + self.starsContext = starsContext + self.invoice = invoice + self.source = source + self.inputData = inputData + self.dismiss = dismiss + } + + 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 cachedStarImage: (UIImage, PresentationTheme)? + + private let context: AccountContext + private let starsContext: StarsContext + private let source: BotPaymentInvoiceSource + private let invoice: TelegramMediaInvoice + + private(set) var peer: EnginePeer? + private var peerDisposable: Disposable? + private(set) var balance: Int64? + private(set) var form: BotPaymentForm? + + private var stateDisposable: Disposable? + + private var optionsDisposable: Disposable? + private(set) var options: [StarsTopUpOption] = [] { + didSet { + self.optionsPromise.set(self.options) + } + } + private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil) + + var inProgress = false + + init( + context: AccountContext, + starsContext: StarsContext, + source: BotPaymentInvoiceSource, + invoice: TelegramMediaInvoice, + inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + ) { + self.context = context + self.starsContext = starsContext + self.source = source + self.invoice = invoice + + 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) + + if self.optionsDisposable == nil, let balance = self.balance, balance < self.invoice.totalAmount { + self.optionsDisposable = (context.engine.payments.starsTopUpOptions() + |> deliverOnMainQueue).start(next: { [weak self] options in + guard let self else { + return + } + self.options = options + }) + } + }) + + self.stateDisposable = (starsContext.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self else { + return + } + self.balance = state?.balance + self.updated(transition: .immediate) + }) + } + + deinit { + self.peerDisposable?.dispose() + self.stateDisposable?.dispose() + self.optionsDisposable?.dispose() + } + + func buy(requestTopUp: @escaping (@escaping () -> Void) -> Void, completion: @escaping () -> Void) { + guard let form, let balance else { + return + } + + let action = { [weak self] in + guard let self else { + return + } + self.inProgress = true + self.updated() + + let _ = (self.context.engine.payments.sendStarsPaymentForm(formId: form.id, source: self.source) + |> deliverOnMainQueue).start(next: { _ in + completion() + }) + } + + if balance < self.invoice.totalAmount { + if self.options.isEmpty { + self.inProgress = true + self.updated() + } + let _ = (self.optionsPromise.get() + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in + if let self { + self.inProgress = false + self.updated() + + requestTopUp({ [weak self] in + guard let self else { + return + } + self.inProgress = true + self.updated() + + let _ = (self.starsContext.state + |> filter { state in + if let state { + return !state.flags.contains(.isPendingBalance) + } + return false + } + |> take(1) + |> deliverOnMainQueue).start(next: { _ in + action() + }) + }) + } + }) + } else { + action() + } + } + } + + func makeState() -> State { + return State(context: self.context, starsContext: self.starsContext, source: self.source, invoice: self.invoice, inputData: self.inputData) + } + + static var body: Body { + let background = Child(RoundedRectangle.self) + let star = Child(StarsImageComponent.self) + let closeButton = Child(Button.self) + let title = Child(Text.self) + let text = Child(BalancedTextComponent.self) + let button = Child(ButtonComponent.self) + let balanceTitle = Child(MultilineTextComponent.self) + let balanceValue = Child(MultilineTextComponent.self) + let balanceIcon = Child(BundleIconComponent.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 subject: StarsImageComponent.Subject + if let photo = component.invoice.photo { + subject = .photo(photo) + } else { + subject = .transactionPeer(.peer(peer)) + } + let star = star.update( + component: StarsImageComponent( + context: component.context, + subject: subject, + theme: theme, + diameter: 90.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: star.size.height / 2.0 - 27.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 += 126.0 + + let title = title.update( + component: Text(text: strings.Stars_Transfer_Title, 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: strings.Stars_Transfer_Info( + component.invoice.title, + state.peer?.compactDisplayTitle ?? "", + strings.Stars_Transfer_Info_Stars(Int32(amount)) + ).string, + 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 += 28.0 + + let balanceTitle = balanceTitle.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Stars_Transfer_Balance, + font: Font.regular(14.0), + textColor: textColor + )), + maximumNumberOfLines: 1 + ), + availableSize: context.availableSize, + transition: .immediate + ) + let balanceValue = balanceValue.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: presentationStringsFormattedNumber(Int32(state.balance ?? 0), environment.dateTimeFormat.groupingSeparator), + font: Font.semibold(16.0), + textColor: textColor + )), + maximumNumberOfLines: 1 + ), + availableSize: context.availableSize, + transition: .immediate + ) + let balanceIcon = balanceIcon.update( + component: BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil), + availableSize: context.availableSize, + transition: .immediate + ) + + let topBalanceOriginY = 11.0 + context.add(balanceTitle + .position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceTitle.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height / 2.0)) + ) + context.add(balanceIcon + .position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceIcon.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 + 1.0 + UIScreenPixel)) + ) + context.add(balanceValue + .position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceIcon.size.width + 3.0 + balanceValue.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 + 2.0 - UIScreenPixel)) + ) + + if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme { + state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white)!, theme) + } + + let buttonAttributedString = NSMutableAttributedString(string: "\(strings.Stars_Transfer_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 starsContext = component.starsContext + 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(requestTopUp: { [weak controller] completion in + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: accountContext.currentAppConfiguration.with { $0 }) + if !premiumConfiguration.isPremiumDisabled { + let purchaseController = accountContext.sharedContext.makeStarsPurchaseScreen( + context: accountContext, + starsContext: starsContext, + options: state?.options ?? [], + peerId: state?.peer?.id, + requiredStars: invoice.totalAmount, + completion: { [weak starsContext] stars in + starsContext?.add(balance: stars) + Queue.mainQueue().after(0.1) { + completion() + } + } + ) + controller?.push(purchaseController) + } else { + let alertController = textAlertController(context: accountContext, title: nil, text: presentationData.strings.Stars_Transfer_Unavailable, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + controller?.present(alertController, in: .window(.root)) + } + }, completion: { [weak controller] in + let presentationData = accountContext.sharedContext.currentPresentationData.with { $0 } + if let navigationController = controller?.navigationController { + Queue.mainQueue().after(0.5) { + if let lastController = navigationController.viewControllers.last as? ViewController { + let resultController = UndoOverlayController( + presentationData: presentationData, + content: .image( + image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!, + title: presentationData.strings.Stars_Transfer_PurchasedTitle, + text: presentationData.strings.Stars_Transfer_PurchasedText(invoice.title, botTitle, presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string, + round: false, + undoText: nil + ), + elevatedLayout: lastController is ChatController, + action: { _ in return true} + ) + lastController.present(resultController, in: .window(.root)) + } + } + } + + controller?.complete(paid: true) + controller?.dismissAnimated() + + starsContext.load(force: true) + }) + } + ), + 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 starsContext: StarsContext + private let invoice: TelegramMediaInvoice + private let source: BotPaymentInvoiceSource + private let inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + + init( + context: AccountContext, + starsContext: StarsContext, + invoice: TelegramMediaInvoice, + source: BotPaymentInvoiceSource, + inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> + ) { + self.context = context + self.starsContext = starsContext + 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, + starsContext: context.component.starsContext, + invoice: context.component.invoice, + source: context.component.source, + inputData: context.component.inputData, + dismiss: { + animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + }) + } + )), + backgroundColor: .blur(.light), + followContentSizeChanges: true, + clipsContent: 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 + private let completion: (Bool) -> Void + + public init( + context: AccountContext, + starsContext: StarsContext, + invoice: TelegramMediaInvoice, + source: BotPaymentInvoiceSource, + inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>, + completion: @escaping (Bool) -> Void + ) { + self.context = context + self.completion = completion + + super.init( + context: context, + component: StarsTransferSheetComponent( + context: context, + starsContext: starsContext, + invoice: invoice, + source: source, + inputData: inputData + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: .default + ) + + self.navigationPresentation = .flatModal + + starsContext.load(force: false) + } + + deinit { + self.complete(paid: false) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var didComplete = false + fileprivate func complete(paid: Bool) { + guard !self.didComplete else { + return + } + self.didComplete = true + self.completion(paid) + } + + 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/Components/StickerPickerScreen/BUILD b/submodules/TelegramUI/Components/StickerPickerScreen/BUILD index 1de601b3368..c6fbd0fa829 100644 --- a/submodules/TelegramUI/Components/StickerPickerScreen/BUILD +++ b/submodules/TelegramUI/Components/StickerPickerScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/BUILD b/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/BUILD index 0b594f8af34..7d60c7aa6bb 100644 --- a/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/BUILD +++ b/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD index 48894fed16d..a1301bb7f35 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD +++ b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index 8f1f89d7146..85596c09108 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -2009,7 +2009,7 @@ final class StorageUsageScreenComponent: Component { text: presentationData.strings.StorageManagement_PeerShowDetails, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let self else { return } @@ -2027,7 +2027,7 @@ final class StorageUsageScreenComponent: Component { } }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let self, let component = self.component, let controller = self.controller?() else { return } @@ -2050,7 +2050,7 @@ final class StorageUsageScreenComponent: Component { text: presentationData.strings.StorageManagement_ContextSelect, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { }) guard let self, let aggregatedData = self.aggregatedData else { @@ -2545,7 +2545,7 @@ final class StorageUsageScreenComponent: Component { text: openTitle, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Expand"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let self else { return } @@ -2555,7 +2555,7 @@ final class StorageUsageScreenComponent: Component { )) items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let self, let component = self.component, let controller = self.controller?(), let navigationController = controller.navigationController as? NavigationController else { return } @@ -2581,7 +2581,7 @@ final class StorageUsageScreenComponent: Component { items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuSelect, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { }) guard let self, let aggregatedData = self.aggregatedData else { @@ -2642,7 +2642,7 @@ final class StorageUsageScreenComponent: Component { text: openTitle, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Expand"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let self else { return } @@ -2658,7 +2658,7 @@ final class StorageUsageScreenComponent: Component { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let self, let component = self.component, let controller = self.controller?(), let navigationController = controller.navigationController as? NavigationController else { return } @@ -2685,7 +2685,7 @@ final class StorageUsageScreenComponent: Component { text: aggregatedData.selectionState.selectedMessages.contains(messageId) ? presentationData.strings.StorageManagement_ContextDeselect : presentationData.strings.StorageManagement_ContextSelect, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { }) guard let self, let aggregatedData = self.aggregatedData else { diff --git a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/BUILD b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/BUILD index 1f197e748ed..6b75a90b66f 100644 --- a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent/BUILD b/submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent/BUILD index ae6df8bf440..600aa21d483 100644 --- a/submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/BUILD b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/BUILD index 2841b2a498e..69e106a15f0 100644 --- a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 6bb5c6bbe3c..c75d5360a25 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 9d200b7a992..2be200e6434 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -974,6 +974,13 @@ private final class StoryContainerScreenComponent: Component { } } else { if let result = subview.hitTest(self.convert(self.convert(point, to: subview), to: subview), with: event) { + if let environment = self.environment, case .regular = environment.metrics.widthClass { + if result.isDescendant(of: self.backgroundEffectView) { + if let stateValue = self.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id] { + return itemSetView.view.view + } + } + } return result } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 01cc825f96a..2fab648f95d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -569,7 +569,7 @@ final class StoryItemContentComponent: Component { return } if apply { - videoNode.seek(timestamp) + videoNode.seek(min(timestamp, self.effectiveDuration - 0.3)) } self.isSeeking = true self.updateVideoPlaybackProgress(timestamp) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 6f931b1bca6..5ebfa323368 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -4231,7 +4231,7 @@ public final class StoryItemSetContainerComponent: Component { } switch action { case let .url(url, concealed): - let _ = openUserGeneratedUrl(context: component.context, peerId: component.slice.peer.id, url: url, concealed: concealed, skipUrlAuth: false, skipConcealedAlert: false, present: { [weak self] c in + let _ = openUserGeneratedUrl(context: component.context, peerId: component.slice.peer.id, url: url, concealed: concealed, skipUrlAuth: false, skipConcealedAlert: false, forceDark: true, present: { [weak self] c in guard let self, let component = self.component, let controller = component.controller() else { return } @@ -4241,6 +4241,12 @@ public final class StoryItemSetContainerComponent: Component { return } self.sendMessageContext.openResolved(view: self, result: resolved, forceExternal: false, concealed: concealed) + }, alertDisplayUpdated: { [weak self] alertController in + guard let self else { + return + } + self.sendMessageContext.statusController = alertController + self.updateIsProgressPaused() }) case let .textMention(value): self.sendMessageContext.openPeerMention(view: self, name: value) @@ -4302,7 +4308,7 @@ public final class StoryItemSetContainerComponent: Component { self.sendMessageContext.currentSpeechHolder = speechHolder } case .translate: - self.sendMessageContext.performTranslateTextAction(view: self, text: text.string) + self.sendMessageContext.performTranslateTextAction(view: self, text: text.string, entities: []) case .quote: break } @@ -4338,6 +4344,7 @@ public final class StoryItemSetContainerComponent: Component { }, sendFile: nil, sendSticker: nil, + sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in @@ -4375,6 +4382,12 @@ public final class StoryItemSetContainerComponent: Component { let captionFrame = CGRect(origin: CGPoint(x: 0.0, y: contentFrame.height - captionSize.height), size: captionSize) if let captionItemView = captionItem.view.view { if captionItemView.superview == nil { + if case .regular = component.metrics.widthClass { + self.topContentGradientView.layer.cornerRadius = 12.0 + self.topContentGradientView.clipsToBounds = true + captionItemView.layer.cornerRadius = 12.0 + captionItemView.clipsToBounds = true + } self.controlsContainerView.insertSubview(captionItemView, aboveSubview: self.contentDimView) } captionItemTransition.setFrame(view: captionItemView, frame: captionFrame) @@ -4437,7 +4450,7 @@ public final class StoryItemSetContainerComponent: Component { context: component.context, animationCache: component.context.animationCache, presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme), - items: reactionItems.map(ReactionContextItem.reaction), + items: reactionItems.map { ReactionContextItem.reaction(item: $0, icon: .none) }, selectedItems: component.slice.item.storyItem.myReaction.flatMap { Set([$0]) } ?? Set(), title: self.displayLikeReactions ? nil : (isGroup ? component.strings.Story_SendReactionAsGroupMessage : component.strings.Story_SendReactionAsMessage), reactionsLocked: false, @@ -5346,13 +5359,9 @@ public final class StoryItemSetContainerComponent: Component { private let updateDisposable = MetaDisposable() func openStoryEditing(repost: Bool = false) { - guard let component = self.component, let peerReference = PeerReference(component.slice.peer._asPeer()) else { + guard let component = self.component else { return } - let context = component.context - let peerId = component.slice.peer.id - let item = component.slice.item.storyItem - let id = item.id self.isEditingStory = true self.updateIsProgressPaused() @@ -5363,277 +5372,39 @@ public final class StoryItemSetContainerComponent: Component { videoPlaybackPosition = view.videoPlaybackPosition } - let subject: Signal - subject = getStorySource(engine: component.context.engine, peerId: component.context.account.peerId, id: Int64(item.id)) - |> mapToSignal { source in - if !repost, let source { - return .single(.draft(source, Int64(item.id))) - } else { - let media = item.media._asMedia() - return fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: item.id, media: media)) - |> mapToSignal { (value, isImage) -> Signal in - guard case let .data(data) = value, data.complete else { - return .complete() - } - if let image = UIImage(contentsOfFile: data.path) { - return .single(nil) - |> then( - .single(.image(image, PixelDimensions(image.size), nil, .bottomRight)) - |> delay(0.1, queue: Queue.mainQueue()) - ) - } else { - var duration: Double? - if let file = media as? TelegramMediaFile { - duration = file.duration - } - let symlinkPath = data.path + ".mp4" - if fileSize(symlinkPath) == nil { - let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath) - } - return .single(nil) - |> then( - .single(.video(symlinkPath, nil, false, nil, nil, PixelDimensions(width: 720, height: 1280), duration ?? 0.0, [], .bottomRight)) - ) - } - } - } - } - - let initialCaption: NSAttributedString? - let initialPrivacy: EngineStoryPrivacy? - let initialMediaAreas: [MediaArea] - if repost { - initialCaption = nil - initialPrivacy = nil - initialMediaAreas = [] - } else { - initialCaption = chatInputStateStringWithAppliedEntities(item.text, entities: item.entities) - initialPrivacy = item.privacy - initialMediaAreas = item.mediaAreas - } - - let externalState = MediaEditorTransitionOutExternalState( - storyTarget: nil, - isForcedTarget: false, - isPeerArchived: false, - transitionOut: nil - ) - - var updateProgressImpl: ((Float) -> Void)? - let controller = MediaEditorScreen( - context: context, - mode: .storyEditor, - subject: subject, - isEditing: !repost, - forwardSource: repost ? (component.slice.peer, item) : nil, - initialCaption: initialCaption, - initialPrivacy: initialPrivacy, - initialMediaAreas: initialMediaAreas, - initialVideoPosition: videoPlaybackPosition, + guard let controller = MediaEditorScreen.makeEditStoryController( + context: component.context, + peer: component.slice.peer, + storyItem: component.slice.item.storyItem, + videoPlaybackPosition: videoPlaybackPosition, + repost: repost, transitionIn: .noAnimation, - transitionOut: { finished, isNew in - if repost && finished { - if let transitionOut = externalState.transitionOut?(externalState.storyTarget, externalState.isPeerArchived), let destinationView = transitionOut.destinationView { - return MediaEditorScreen.TransitionOut( - destinationView: destinationView, - destinationRect: transitionOut.destinationRect, - destinationCornerRadius: transitionOut.destinationCornerRadius - ) - } else { - return nil - } - } else { - return nil + transitionOut: nil, + completed: { [weak self] in + guard let self else { + return } + self.component?.controller()?.dismiss(animated: false) }, - completion: { [weak self] result, commit in + willDismiss: { [weak self] in guard let self else { return } - - let entities = generateChatInputTextEntities(result.caption) - - if repost { - let target: Stories.PendingTarget - let targetPeerId: EnginePeer.Id - if let sendAsPeerId = result.options.sendAsPeerId { - target = .peer(sendAsPeerId) - targetPeerId = sendAsPeerId - } else { - target = .myStories - targetPeerId = context.account.peerId - } - externalState.storyTarget = target - - self.component?.controller()?.dismiss(animated: false) - - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: targetPeerId)) - |> deliverOnMainQueue).startStandalone(next: { peer in - guard let peer else { - return - } - - if case let .user(user) = peer { - externalState.isPeerArchived = user.storiesHidden ?? false - - } else if case let .channel(channel) = peer { - externalState.isPeerArchived = channel.storiesHidden ?? false - } - - let forwardInfo = Stories.PendingForwardInfo(peerId: component.slice.peer.id, storyId: item.id, isModified: result.media != nil) - - if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface { - var existingMedia: EngineMedia? - if let _ = result.media { - } else { - existingMedia = item.media - } - rootController.proceedWithStoryUpload(target: target, result: result as! MediaEditorScreenResult, existingMedia: existingMedia, forwardInfo: forwardInfo, externalState: externalState, commit: commit) - } - }) - } else { - var updatedText: String? - var updatedEntities: [MessageTextEntity]? - if result.caption.string != item.text || entities != item.entities { - updatedText = result.caption.string - updatedEntities = entities - } - - if let mediaResult = result.media { - switch mediaResult { - case let .image(image, dimensions): - updateProgressImpl?(0.0) - - let tempFile = TempBox.shared.tempFile(fileName: "file") - defer { - TempBox.shared.dispose(tempFile) - } - if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) { - self.updateDisposable.set((context.engine.messages.editStory(peerId: peerId, id: id, media: .image(dimensions: dimensions, data: imageData, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil) - |> deliverOnMainQueue).startStrict(next: { [weak self] result in - guard let self else { - return - } - switch result { - case let .progress(progress): - updateProgressImpl?(progress) - case .completed: - Queue.mainQueue().after(0.1) { - self.isEditingStory = false - self.rewindCurrentItem() - self.updateIsProgressPaused() - self.state?.updated(transition: .easeInOut(duration: 0.2)) - - HapticFeedback().success() - - commit({}) - } - } - })) - } - case let .video(content, firstFrameImage, values, duration, dimensions): - updateProgressImpl?(0.0) - - if let valuesData = try? JSONEncoder().encode(values) { - let data = MemoryBuffer(data: valuesData) - let digest = MemoryBuffer(data: data.md5Digest()) - let adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true) - - let resource: TelegramMediaResource - switch content { - case let .imageFile(path): - resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments) - case let .videoFile(path): - resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments) - case let .asset(localIdentifier): - resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments)) - } - - let tempFile = TempBox.shared.tempFile(fileName: "file") - defer { - TempBox.shared.dispose(tempFile) - } - let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6, tempFilePath: tempFile.path) } - let firstFrameFile = firstFrameImageData.flatMap { data -> TempBoxFile? in - let file = TempBox.shared.tempFile(fileName: "image.jpg") - if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) { - return file - } else { - return nil - } - } - - self.updateDisposable.set((context.engine.messages.editStory(peerId: peerId, id: id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil) - |> deliverOnMainQueue).startStrict(next: { [weak self] result in - guard let self else { - return - } - switch result { - case let .progress(progress): - updateProgressImpl?(progress) - case .completed: - Queue.mainQueue().after(0.1) { - self.isEditingStory = false - self.rewindCurrentItem() - self.updateIsProgressPaused() - self.state?.updated(transition: .easeInOut(duration: 0.2)) - - HapticFeedback().success() - - commit({}) - } - } - })) - } - default: - break - } - } else if updatedText != nil { - let _ = (context.engine.messages.editStory(peerId: peerId, id: id, media: nil, mediaAreas: nil, text: updatedText, entities: updatedEntities, privacy: nil) - |> deliverOnMainQueue).startStandalone(next: { [weak self] result in - switch result { - case .completed: - Queue.mainQueue().after(0.1) { - if let self { - self.isEditingStory = false - self.rewindCurrentItem() - self.updateIsProgressPaused() - self.state?.updated(transition: .easeInOut(duration: 0.2)) - - HapticFeedback().success() - } - commit({}) - } - default: - break - } - }) - } else { - self.isEditingStory = false - self.rewindCurrentItem() - self.updateIsProgressPaused() - self.state?.updated(transition: .easeInOut(duration: 0.2)) - - HapticFeedback().success() - - commit({}) - } + self.isEditingStory = false + self.rewindCurrentItem() + self.updateIsProgressPaused() + self.state?.updated(transition: .easeInOut(duration: 0.2)) + }, + update: { [weak self] disposable in + guard let self else { + return } + self.updateDisposable.set(disposable) } - ) - controller.willDismiss = { [weak self] in - self?.isEditingStory = false - self?.rewindCurrentItem() - self?.updateIsProgressPaused() - self?.state?.updated(transition: .easeInOut(duration: 0.2)) + ) else { + return } - controller.navigationPresentation = .flatModal self.component?.controller()?.push(controller) - updateProgressImpl = { [weak controller, weak self] progress in - controller?.updateEditProgress(progress, cancel: { [weak self] in - self?.updateDisposable.set(nil) - }) - } } private func presentSaveUpgradeScreen() { @@ -6156,7 +5927,7 @@ public final class StoryItemSetContainerComponent: Component { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) }, iconPosition: .left, action: { c, _ in - c.popItems() + c?.popItems() }))) items.append(.custom(SliderContextItem(minValue: 0.2, maxValue: 2.5, value: baseRate, valueChanged: { [weak self] newValue, done in @@ -6247,11 +6018,11 @@ public final class StoryItemSetContainerComponent: Component { return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in guard let self else { - c.dismiss(completion: nil) + c?.dismiss(completion: nil) return } - c.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) + c?.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) }))) items.append(.separator) } @@ -6463,11 +6234,11 @@ public final class StoryItemSetContainerComponent: Component { return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in guard let self else { - c.dismiss(completion: nil) + c?.dismiss(completion: nil) return } - c.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) + c?.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) }))) items.append(.separator) } @@ -6778,11 +6549,11 @@ public final class StoryItemSetContainerComponent: Component { return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in guard let self else { - c.dismiss(completion: nil) + c?.dismiss(completion: nil) return } - c.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) + c?.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) }))) items.append(.separator) } @@ -7046,7 +6817,7 @@ public final class StoryItemSetContainerComponent: Component { guard let self, let component = self.component else { return } - self.sendMessageContext.performTranslateTextAction(view: self, text: component.slice.item.storyItem.text) + self.sendMessageContext.performTranslateTextAction(view: self, text: component.slice.item.storyItem.text, entities: component.slice.item.storyItem.entities) }))) } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 0f5ea43c4b6..8abf1537a3f 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -1159,7 +1159,7 @@ final class StoryItemSetContainerSendMessage { controller.present(shareController, in: .window(.root)) } - func performTranslateTextAction(view: StoryItemSetContainerComponent.View, text: String) { + func performTranslateTextAction(view: StoryItemSetContainerComponent.View, text: String, entities: [MessageTextEntity]) { guard let component = view.component else { return } @@ -1190,7 +1190,7 @@ final class StoryItemSetContainerSendMessage { let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: component.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start() - let translateController = TranslateScreen(context: component.context, forceTheme: defaultDarkPresentationTheme, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) + let translateController = TranslateScreen(context: component.context, forceTheme: defaultDarkPresentationTheme, text: text, entities: entities, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) translateController.pushController = { [weak view] c in guard let view, let component = view.component else { return @@ -1556,14 +1556,14 @@ final class StoryItemSetContainerSendMessage { completion(controller, mediaPickerContext) }, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in attachmentController?.mediaPickerContext = mediaPickerContext - }, completion: { [weak self, weak view] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + }, completion: { [weak self, weak view] signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in guard let self, let view else { return } if !inputText.string.isEmpty { self.clearInputText(view: view) } - self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) } ) case .file: @@ -1658,7 +1658,7 @@ final class StoryItemSetContainerSendMessage { } self.controllerNavigationDisposable.set((contactsController.result |> deliverOnMainQueue).start(next: { [weak self, weak view] peers in - guard let self, let view, let (peers, _, silent, scheduleTime, text) = peers else { + guard let self, let view, let (peers, _, silent, scheduleTime, text, _) = peers else { return } @@ -1762,7 +1762,7 @@ final class StoryItemSetContainerSendMessage { self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) } else { - let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: component.context, subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in + let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: component.context), environment: ShareControllerAppEnvironment(sharedContext: component.context.sharedContext), subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in guard let self, let view else { return } @@ -1875,7 +1875,7 @@ final class StoryItemSetContainerSendMessage { bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, - completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void + completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void ) { guard let component = view.component else { return @@ -1936,8 +1936,8 @@ final class StoryItemSetContainerSendMessage { } return self.getCaptionPanelView(view: view, peer: peer, mediaPicker: controller) } - controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in - completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion) + controller.legacyCompletion = { signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion in + completion(signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion) } present(controller, mediaPickerContext) } @@ -2240,14 +2240,14 @@ final class StoryItemSetContainerSendMessage { present(controller, mediaPickerContext) }, updateMediaPickerContext: { _ in }, - completion: { [weak self, weak view] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + completion: { [weak self, weak view] signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in guard let self, let view else { return } if !inputText.string.isEmpty { self.clearInputText(view: view) } - self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) } ) } @@ -2320,7 +2320,7 @@ final class StoryItemSetContainerSendMessage { return nil } //TODO:self.presentationInterfaceState.customEmojiAvailable - return component.context.sharedContext.makeGalleryCaptionPanelView(context: component.context, chatLocation: .peer(id: peer.id), isScheduledMessages: false, customEmojiAvailable: true, present: { [weak view] c in + return component.context.sharedContext.makeGalleryCaptionPanelView(context: component.context, chatLocation: .peer(id: peer.id), isScheduledMessages: false, isFile: false, customEmojiAvailable: true, present: { [weak view] c in guard let view else { return } @@ -2569,7 +2569,7 @@ final class StoryItemSetContainerSendMessage { } } - private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { + private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, parameters: ChatSendMessageActionSheetController.SendParameters? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { guard let component = view.component else { return } @@ -2620,6 +2620,22 @@ final class StoryItemSetContainerSendMessage { } } } + if let parameters { + if let effect = parameters.effect { + message = message.withUpdatedAttributes { attributes in + var attributes = attributes + attributes.append(EffectMessageAttribute(id: effect.id)) + return attributes + } + } + if parameters.textIsAboveMedia { + message = message.withUpdatedAttributes { attributes in + var attributes = attributes + attributes.append(InvertMediaMessageAttribute()) + return attributes + } + } + } mappedMessages.append(message) } @@ -2711,12 +2727,17 @@ final class StoryItemSetContainerSendMessage { if let navigationController = controller.navigationController as? NavigationController { component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peerId), attachBotStart: attachBotStart)) } + case let .withBotApp(botAppStart): + if let navigationController = controller.navigationController as? NavigationController { + component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peerId), botAppStart: botAppStart)) + } default: break } }, sendFile: nil, sendSticker: nil, + sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak view] c, a in @@ -2897,8 +2918,13 @@ final class StoryItemSetContainerSendMessage { return } if !hashtag.isEmpty { - let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, all: true) - navigationController.pushViewController(searchController) + /*if !"".isEmpty { + let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, query: hashtag) + navigationController.pushViewController(searchController) + } else {*/ + let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, all: true) + navigationController.pushViewController(searchController) + //} } })) } diff --git a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/BUILD b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/BUILD index 89ed0682d27..37292a832b3 100644 --- a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/BUILD b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/BUILD index 6658f5726db..7bd33d3f21d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD index 316cc15fa87..7ad3399938c 100644 --- a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/BUILD b/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/BUILD index 49b18603a1b..877f1cf0b3c 100644 --- a/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/BUILD index 0fc67aed4c6..7771fc5b34e 100644 --- a/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/SwitchComponent/BUILD b/submodules/TelegramUI/Components/SwitchComponent/BUILD index 6bbae84901c..b70491e97fc 100644 --- a/submodules/TelegramUI/Components/SwitchComponent/BUILD +++ b/submodules/TelegramUI/Components/SwitchComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/TabSelectorComponent/BUILD b/submodules/TelegramUI/Components/TabSelectorComponent/BUILD index 87087a3713f..a56ab9fe513 100644 --- a/submodules/TelegramUI/Components/TabSelectorComponent/BUILD +++ b/submodules/TelegramUI/Components/TabSelectorComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/TelegramAccountAuxiliaryMethods/BUILD b/submodules/TelegramUI/Components/TelegramAccountAuxiliaryMethods/BUILD index 0f5bf919fa2..382926a71ed 100644 --- a/submodules/TelegramUI/Components/TelegramAccountAuxiliaryMethods/BUILD +++ b/submodules/TelegramUI/Components/TelegramAccountAuxiliaryMethods/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/BUILD b/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/BUILD index 5faf3562fe9..31a670cad19 100644 --- a/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/BUILD +++ b/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Components/TextFieldComponent/BUILD b/submodules/TelegramUI/Components/TextFieldComponent/BUILD index 045b43ad01f..4894bf1e0c2 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/BUILD +++ b/submodules/TelegramUI/Components/TextFieldComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 2005164bf6a..b9831240c97 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -84,7 +84,30 @@ public final class TextFieldComponent: Component { } public enum FormatMenuAvailability: Equatable { - case available + public enum Action: CaseIterable { + case bold + case italic + case monospace + case link + case strikethrough + case underline + case spoiler + case quote + case code + + public static var all: [Action] = [ + .bold, + .italic, + .monospace, + .link, + .strikethrough, + .underline, + .spoiler, + .quote, + .code + ] + } + case available([Action]) case locked case none } @@ -101,6 +124,7 @@ public final class TextFieldComponent: Component { public let externalState: ExternalState public let fontSize: CGFloat public let textColor: UIColor + public let accentColor: UIColor public let insets: UIEdgeInsets public let hideKeyboard: Bool public let customInputView: UIView? @@ -124,6 +148,7 @@ public final class TextFieldComponent: Component { externalState: ExternalState, fontSize: CGFloat, textColor: UIColor, + accentColor: UIColor, insets: UIEdgeInsets, hideKeyboard: Bool, customInputView: UIView?, @@ -146,6 +171,7 @@ public final class TextFieldComponent: Component { self.externalState = externalState self.fontSize = fontSize self.textColor = textColor + self.accentColor = accentColor self.insets = insets self.hideKeyboard = hideKeyboard self.customInputView = customInputView @@ -182,6 +208,9 @@ public final class TextFieldComponent: Component { if lhs.textColor != rhs.textColor { return false } + if lhs.accentColor != rhs.accentColor { + return false + } if lhs.insets != rhs.insets { return false } @@ -285,13 +314,17 @@ public final class TextFieldComponent: Component { let inputState = f(self.inputState) let currentAttributedText = self.textView.attributedText - let updatedAttributedText = textAttributedStringForStateText(inputState.inputText, fontSize: component.fontSize, textColor: component.textColor, accentTextColor: component.textColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) + let updatedAttributedText = textAttributedStringForStateText(context: component.context, stateText: inputState.inputText, fontSize: component.fontSize, textColor: component.textColor, accentTextColor: component.accentColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) if currentAttributedText != updatedAttributedText { self.textView.attributedText = updatedAttributedText } self.textView.selectedRange = NSMakeRange(inputState.selectionRange.lowerBound, inputState.selectionRange.count) - refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) + refreshChatTextInputAttributes(context: component.context, textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.accentColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) self.updateEntities() @@ -423,7 +456,9 @@ public final class TextFieldComponent: Component { guard let component = self.component else { return } - refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) + refreshChatTextInputAttributes(context: component.context, textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.accentColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) refreshChatTextInputTypingAttributes(self.textView, textColor: component.textColor, baseFontSize: component.fontSize) self.textView.updateTextContainerInset() @@ -517,84 +552,105 @@ public final class TextFieldComponent: Component { updatedActions.insert(formatAction, at: 1) return UIMenu(children: updatedActions) } + + guard case let .available(availableActions) = component.formatMenuAvailability else { + return UIMenu(children: suggestedActions) + } - var actions: [UIAction] = [ - UIAction(title: strings.TextFormat_Bold, image: nil) { [weak self] action in + var actions: [UIAction] = [] + if availableActions.contains(.bold) { + actions.append(UIAction(title: strings.TextFormat_Bold, image: nil) { [weak self] action in if let self { self.toggleAttribute(key: ChatTextInputAttributes.bold) } - }, - UIAction(title: strings.TextFormat_Italic, image: nil) { [weak self] action in + }) + } + if availableActions.contains(.italic) { + actions.append(UIAction(title: strings.TextFormat_Italic, image: nil) { [weak self] action in if let self { self.toggleAttribute(key: ChatTextInputAttributes.italic) } - }, - UIAction(title: strings.TextFormat_Monospace, image: nil) { [weak self] action in + }) + } + if availableActions.contains(.monospace) { + actions.append(UIAction(title: strings.TextFormat_Monospace, image: nil) { [weak self] action in if let self { self.toggleAttribute(key: ChatTextInputAttributes.monospace) } - }, - UIAction(title: strings.TextFormat_Link, image: nil) { [weak self] action in + }) + } + if availableActions.contains(.link) { + actions.append(UIAction(title: strings.TextFormat_Link, image: nil) { [weak self] action in if let self { self.openLinkEditing() } - }, - UIAction(title: strings.TextFormat_Strikethrough, image: nil) { [weak self] action in + }) + } + if availableActions.contains(.strikethrough) { + actions.append(UIAction(title: strings.TextFormat_Strikethrough, image: nil) { [weak self] action in if let self { self.toggleAttribute(key: ChatTextInputAttributes.strikethrough) } - }, - UIAction(title: strings.TextFormat_Underline, image: nil) { [weak self] action in + }) + } + if availableActions.contains(.underline) { + actions.append(UIAction(title: strings.TextFormat_Underline, image: nil) { [weak self] action in if let self { self.toggleAttribute(key: ChatTextInputAttributes.underline) } - } - ] - actions.append(UIAction(title: strings.TextFormat_Spoiler, image: nil) { [weak self] action in - if let self { - var animated = false - let attributedText = self.inputState.inputText - attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, _, _ in - if let _ = attributes[ChatTextInputAttributes.spoiler] { - animated = true - } - }) - - self.toggleAttribute(key: ChatTextInputAttributes.spoiler) - - self.updateSpoilersRevealed(animated: animated) - } - }) - actions.insert(UIAction(title: strings.TextFormat_Quote, image: nil) { [weak self] action in - if let self { - var animated = false - let attributedText = self.inputState.inputText - attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, _, _ in - if let _ = attributes[ChatTextInputAttributes.block] { - animated = true - } - }) - - self.toggleAttribute(key: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote)) - - self.updateSpoilersRevealed(animated: animated) - } - }, at: 0) - actions.append(UIAction(title: strings.TextFormat_Code, image: nil) { [weak self] action in - if let self { - var animated = false - let attributedText = self.inputState.inputText - attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, _, _ in - if let _ = attributes[ChatTextInputAttributes.block] { - animated = true - } - }) - - self.toggleAttribute(key: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: nil))) - - self.updateSpoilersRevealed(animated: animated) - } - }) + }) + } + if availableActions.contains(.spoiler) { + actions.append(UIAction(title: strings.TextFormat_Spoiler, image: nil) { [weak self] action in + if let self { + var animated = false + let attributedText = self.inputState.inputText + attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, _, _ in + if let _ = attributes[ChatTextInputAttributes.spoiler] { + animated = true + } + }) + + self.toggleAttribute(key: ChatTextInputAttributes.spoiler) + + self.updateSpoilersRevealed(animated: animated) + } + }) + } + if availableActions.contains(.quote) { + actions.insert(UIAction(title: strings.TextFormat_Quote, image: nil) { [weak self] action in + if let self { + var animated = false + let attributedText = self.inputState.inputText + attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, _, _ in + if let _ = attributes[ChatTextInputAttributes.block] { + animated = true + } + }) + + self.toggleAttribute(key: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false)) + + self.updateSpoilersRevealed(animated: animated) + } + }, at: 0) + } + if availableActions.contains(.code) { + actions.append(UIAction(title: strings.TextFormat_Code, image: nil) { [weak self] action in + if let self { + var animated = false + let attributedText = self.inputState.inputText + attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, _, _ in + if let _ = attributes[ChatTextInputAttributes.block] { + animated = true + } + }) + + self.toggleAttribute(key: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: nil), isCollapsed: false)) + + self.updateSpoilersRevealed(animated: animated) + } + }) + } var updatedActions = suggestedActions let formatMenu = UIMenu(title: strings.TextFormat_Format, image: nil, children: actions) @@ -609,31 +665,56 @@ public final class TextFieldComponent: Component { } if let characterLimit = component.characterLimit { + let replacementString = text as NSString let string = self.inputState.inputText.string as NSString - let updatedString = string.replacingCharacters(in: range, with: text) - if (updatedString as NSString).length > characterLimit { + let deltaLength = replacementString.length - range.length + let resultingLength = string.length + deltaLength + if resultingLength > characterLimit { + let availableLength = characterLimit - string.length + if availableLength > 0 { + var insertString = replacementString.substring(to: availableLength) + + switch component.emptyLineHandling { + case .allowed: + break + case .oneConsecutive: + while insertString.range(of: "\n\n") != nil { + if let range = insertString.range(of: "\n\n") { + insertString.replaceSubrange(range, with: "\n") + } + } + case .notAllowed: + insertString = insertString.replacingOccurrences(of: "\n", with: "") + } + + self.insertText(NSAttributedString(string: insertString)) + } return false } } - switch component.emptyLineHandling { - case .allowed: - break - case .oneConsecutive: - let string = self.inputState.inputText.string as NSString - let updatedString = string.replacingCharacters(in: range, with: text) - if updatedString.range(of: "\n\n") != nil { - return false - } - case .notAllowed: - if (range.length == 0 && text == "\n"), let returnKeyAction = component.returnKeyAction { - returnKeyAction() - return false - } - - let string = self.inputState.inputText.string as NSString - let updatedString = string.replacingCharacters(in: range, with: text) - if updatedString.range(of: "\n") != nil { - return false + if text.count != 0 { + switch component.emptyLineHandling { + case .allowed: + break + case .oneConsecutive: + let string = self.inputState.inputText.string as NSString + let updatedString = string.replacingCharacters(in: range, with: text) + if updatedString.range(of: "\n\n") != nil { + return false + } + case .notAllowed: + if (range.length == 0 && text == "\n"), let returnKeyAction = component.returnKeyAction { + returnKeyAction() + return false + } + + if text.range(of: "\n") != nil { + let updatedText = text.replacingOccurrences(of: "\n", with: "") + if !updatedText.isEmpty { + self.insertText(NSAttributedString(string: updatedText)) + } + return false + } } } @@ -882,7 +963,9 @@ public final class TextFieldComponent: Component { self.textView.isScrollEnabled = false - refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider) + refreshChatTextInputAttributes(context: component.context, textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.accentColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) refreshChatTextInputTypingAttributes(self.textView, textColor: component.textColor, baseFontSize: component.fontSize) if self.textView.subviews.count > 1, animated { @@ -1151,6 +1234,8 @@ public final class TextFieldComponent: Component { let pointSize = floor(24.0 * 1.3) return EmojiTextAttachmentView(context: component.context, userLocation: .other, emoji: emoji, file: emoji.file, cache: component.context.animationCache, renderer: component.context.animationRenderer, placeholderColor: UIColor.white.withAlphaComponent(0.12), pointSize: CGSize(width: pointSize, height: pointSize)) } + + self.chatInputTextNodeDidUpdateText() } let wasEditing = component.externalState.isEditing @@ -1361,7 +1446,7 @@ extension TextFieldComponent.InputState { for (key, value) in attributes { if let value = value as? ChatTextInputTextQuoteAttribute { result.removeAttribute(key, range: range) - result.addAttribute(key, value: ChatTextInputTextQuoteAttribute(kind: value.kind), range: range) + result.addAttribute(key, value: ChatTextInputTextQuoteAttribute(kind: value.kind, isCollapsed: value.isCollapsed), range: range) } } } @@ -1374,7 +1459,7 @@ extension TextFieldComponent.InputState { if addAttribute { if attribute == ChatTextInputAttributes.block { - result.addAttribute(attribute, value: value ?? ChatTextInputTextQuoteAttribute(kind: .quote), range: nsRange) + result.addAttribute(attribute, value: value ?? ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false), range: nsRange) var selectionIndex = nsRange.upperBound if nsRange.upperBound != result.length && (result.string as NSString).character(at: nsRange.upperBound) != 0x0a { result.insert(NSAttributedString(string: "\n"), at: nsRange.upperBound) diff --git a/submodules/TelegramUI/Components/TextLoadingEffect/BUILD b/submodules/TelegramUI/Components/TextLoadingEffect/BUILD index 1dd36290ec9..bb148d5d717 100644 --- a/submodules/TelegramUI/Components/TextLoadingEffect/BUILD +++ b/submodules/TelegramUI/Components/TextLoadingEffect/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift b/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift index e89d9338952..953953dafa7 100644 --- a/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift +++ b/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift @@ -20,8 +20,8 @@ public final class TextLoadingEffectView: UIView { private let backgroundView: UIImageView private let borderBackgroundView: UIImageView - private let duration: Double - private let gradientWidth: CGFloat + private var duration: Double + private var gradientWidth: CGFloat private var size: CGSize? @@ -112,12 +112,37 @@ public final class TextLoadingEffectView: UIView { self.borderBackgroundView.layer.add(animation, forKey: "shimmer") } - public func update(color: UIColor, textNode: TextNode, range: NSRange) { + public func update(color: UIColor, rect: CGRect) { + let maskFrame = CGRect(origin: CGPoint(), size: rect.size).insetBy(dx: -4.0, dy: -4.0) + + self.gradientWidth = 260.0 + self.duration = 1.2 + + self.maskContentsView.backgroundColor = .clear + + self.backgroundView.alpha = 0.25 + self.backgroundView.tintColor = color + + self.maskContentsView.frame = maskFrame + + let rectsSet: [CGRect] = [rect] + + self.maskHighlightNode.updateRects(rectsSet) + self.maskHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize()) + + if self.size != maskFrame.size { + self.size = maskFrame.size + + self.backgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height)) + + self.updateAnimations(size: maskFrame.size) + } + } + + public func update(color: UIColor, textNode: TextNodeProtocol, range: NSRange) { var rectsSet: [CGRect] = [] - if let cachedLayout = textNode.cachedLayout { - if let rects = cachedLayout.rangeRects(in: range)?.rects, !rects.isEmpty { - rectsSet = rects - } + if let rects = textNode.textRangeRects(in: range)?.rects, !rects.isEmpty { + rectsSet = rects } let maskFrame = CGRect(origin: CGPoint(), size: textNode.bounds.size).insetBy(dx: -4.0, dy: -4.0) diff --git a/submodules/TelegramUI/Components/TextNodeWithEntities/BUILD b/submodules/TelegramUI/Components/TextNodeWithEntities/BUILD index 2381686bbc9..e208fbc03d2 100644 --- a/submodules/TelegramUI/Components/TextNodeWithEntities/BUILD +++ b/submodules/TelegramUI/Components/TextNodeWithEntities/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/TimeSelectionActionSheet/BUILD b/submodules/TelegramUI/Components/TimeSelectionActionSheet/BUILD index b4680d882db..5b2f9e2ad95 100644 --- a/submodules/TelegramUI/Components/TimeSelectionActionSheet/BUILD +++ b/submodules/TelegramUI/Components/TimeSelectionActionSheet/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/ToastComponent/BUILD b/submodules/TelegramUI/Components/ToastComponent/BUILD index 29d26d819c0..6e2dbf0b6a6 100644 --- a/submodules/TelegramUI/Components/ToastComponent/BUILD +++ b/submodules/TelegramUI/Components/ToastComponent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/TokenListTextField/BUILD b/submodules/TelegramUI/Components/TokenListTextField/BUILD index 31308b9b22b..77344249ec5 100644 --- a/submodules/TelegramUI/Components/TokenListTextField/BUILD +++ b/submodules/TelegramUI/Components/TokenListTextField/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage/BUILD b/submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage/BUILD index b388ccc50dc..49cabbcec47 100644 --- a/submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage/BUILD +++ b/submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath/BUILD b/submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath/BUILD index ded71fb7724..f491b227a52 100644 --- a/submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath/BUILD +++ b/submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display", diff --git a/submodules/TelegramUI/Components/VideoAnimationCache/BUILD b/submodules/TelegramUI/Components/VideoAnimationCache/BUILD index 7eb3cd35c39..4ba6523e155 100644 --- a/submodules/TelegramUI/Components/VideoAnimationCache/BUILD +++ b/submodules/TelegramUI/Components/VideoAnimationCache/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD b/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD index 9044bfadc1f..0a630c430b1 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit", @@ -39,6 +39,8 @@ swift_library( "//submodules/TelegramUI/Components/MediaEditor", "//submodules/LegacyMediaPickerUI", "//submodules/TelegramAudio", + "//submodules/ChatSendMessageActionUI", + "//submodules/TelegramUI/Components/ChatControllerInteraction", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index a783c7b6d68..7a56391afb1 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -30,6 +30,8 @@ import LocalMediaResources import ImageCompression import LegacyMediaPickerUI import TelegramAudio +import ChatSendMessageActionUI +import ChatControllerInteraction struct CameraState: Equatable { enum Recording: Equatable { @@ -556,6 +558,7 @@ public class VideoMessageCameraScreen: ViewController { fileprivate let containerView: UIView fileprivate let componentHost: ComponentView fileprivate let previewContainerView: UIView + fileprivate let previewContainerContentView: UIView private var previewSnapshotView: UIView? private var previewBlurView: BlurView @@ -628,8 +631,11 @@ public class VideoMessageCameraScreen: ViewController { self.componentHost = ComponentView() self.previewContainerView = UIView() - self.previewContainerView.clipsToBounds = true - + + self.previewContainerContentView = UIView() + self.previewContainerContentView.clipsToBounds = true + self.previewContainerView.addSubview(self.previewContainerContentView) + let isDualCameraEnabled = Camera.isDualCameraSupported(forRoundVideo: true) // MARK: Nicegram (useRearCamTelescopy), change let to var var isFrontPosition = "".isEmpty @@ -676,13 +682,13 @@ public class VideoMessageCameraScreen: ViewController { self.containerView.addSubview(self.previewContainerView) - self.previewContainerView.addSubview(self.mainPreviewView) + self.previewContainerContentView.addSubview(self.mainPreviewView) if isDualCameraEnabled { - self.previewContainerView.addSubview(self.additionalPreviewView) + self.previewContainerContentView.addSubview(self.additionalPreviewView) } - self.previewContainerView.addSubview(self.progressView) - self.previewContainerView.addSubview(self.previewBlurView) - self.previewContainerView.addSubview(self.loadingView) + self.previewContainerContentView.addSubview(self.progressView) + self.previewContainerContentView.addSubview(self.previewBlurView) + self.previewContainerContentView.addSubview(self.loadingView) self.completion.connect { [weak self] result in if let self { @@ -847,7 +853,7 @@ public class VideoMessageCameraScreen: ViewController { private func animatePositionChange() { if let snapshotView = self.mainPreviewView.snapshotView(afterScreenUpdates: false) { - self.previewContainerView.insertSubview(snapshotView, belowSubview: self.progressView) + self.previewContainerContentView.insertSubview(snapshotView, belowSubview: self.progressView) self.previewSnapshotView = snapshotView let action = { [weak self] in @@ -882,7 +888,7 @@ public class VideoMessageCameraScreen: ViewController { func resumeCameraCapture() { if !self.mainPreviewView.isEnabled { if let snapshotView = self.resultPreviewView?.snapshotView(afterScreenUpdates: false) { - self.previewContainerView.insertSubview(snapshotView, belowSubview: self.previewBlurView) + self.previewContainerContentView.insertSubview(snapshotView, belowSubview: self.previewBlurView) self.previewSnapshotView = snapshotView } self.mainPreviewView.isEnabled = true @@ -1158,8 +1164,9 @@ public class VideoMessageCameraScreen: ViewController { } if !self.animatingIn { transition.setFrame(view: self.previewContainerView, frame: previewFrame) + transition.setFrame(view: self.previewContainerContentView, frame: CGRect(origin: CGPoint(), size: previewFrame.size)) } - transition.setCornerRadius(layer: self.previewContainerView.layer, cornerRadius: previewSide / 2.0) + transition.setCornerRadius(layer: self.previewContainerContentView.layer, cornerRadius: previewSide / 2.0) let previewBounds = CGRect(origin: .zero, size: previewFrame.size) @@ -1254,7 +1261,7 @@ public class VideoMessageCameraScreen: ViewController { }, transition: .easeInOut(duration: 0.2)) } } - self.previewContainerView.addSubview(resultPreviewView) + self.previewContainerContentView.addSubview(resultPreviewView) self.resultPreviewView = resultPreviewView resultPreviewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -1471,7 +1478,7 @@ public class VideoMessageCameraScreen: ViewController { fileprivate var didSend = false fileprivate var lastActionTimestamp: Double? fileprivate var isSendingImmediately = false - public func sendVideoRecording(silentPosting: Bool? = nil, scheduleTime: Int32? = nil) { + public func sendVideoRecording(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageEffect? = nil) { guard !self.didSend else { return } @@ -1617,11 +1624,13 @@ public class VideoMessageCameraScreen: ViewController { let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: video.dimensions, flags: [.instantRoundVideo], preloadSize: nil)]) - var attributes: [MessageAttribute] = [] if self.cameraState.isViewOnceEnabled { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) } + if let messageEffect { + attributes.append(EffectMessageAttribute(id: messageEffect.id)) + } self.completion(.message( text: "", @@ -1740,6 +1749,10 @@ public class VideoMessageCameraScreen: ViewController { (self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: Transition(transition)) } } + + public func makeSendMessageContextPreview() -> ChatSendMessageContextScreenMediaPreview? { + return VideoMessageSendMessageContextPreview(node: self.node) + } } private func composition(with results: [VideoMessageCameraScreen.CaptureResult]) -> AVComposition { @@ -1807,3 +1820,84 @@ private class BlurView: UIVisualEffectView { self.setup() } } + +private final class VideoMessageSendMessageContextPreview: UIView, ChatSendMessageContextScreenMediaPreview { + var isReady: Signal { + return .single(true) + } + + var view: UIView { + return self + } + + var globalClippingRect: CGRect? { + return nil + } + + var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType { + return .videoMessage + } + + private weak var previewContainerContentParentView: UIView? + private let previewContainerContentView: UIView + + init(node: VideoMessageCameraScreen.Node) { + self.previewContainerContentParentView = node.previewContainerView + self.previewContainerContentView = node.previewContainerContentView + + super.init(frame: CGRect()) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func animateIn(transition: Transition) { + self.addSubview(self.previewContainerContentView) + + guard let previewContainerContentParentView = self.previewContainerContentParentView else { + return + } + + let fromFrame = previewContainerContentParentView.convert(CGRect(origin: CGPoint(), size: self.previewContainerContentView.bounds.size), to: self) + let toFrame = self.previewContainerContentView.frame + + transition.animatePosition(view: self.previewContainerContentView, from: CGPoint(x: fromFrame.midX - toFrame.midX, y: fromFrame.midY - toFrame.midY), to: CGPoint(), additive: true) + } + + func animateOut(transition: Transition) { + guard let previewContainerContentParentView = self.previewContainerContentParentView else { + return + } + + let toFrame = previewContainerContentParentView.convert(CGRect(origin: CGPoint(), size: self.previewContainerContentView.bounds.size), to: self) + + let previewContainerContentView = self.previewContainerContentView + transition.setPosition(view: self.previewContainerContentView, position: toFrame.center, completion: { [weak previewContainerContentParentView, weak previewContainerContentView] _ in + guard let previewContainerContentParentView, let previewContainerContentView else { + return + } + + previewContainerContentView.frame = CGRect(origin: CGPoint(), size: previewContainerContentView.bounds.size) + previewContainerContentParentView.addSubview(previewContainerContentView) + }) + } + + func animateOutOnSend(transition: Transition) { + guard let previewContainerContentParentView = self.previewContainerContentParentView else { + return + } + + if let snapshotView = self.previewContainerContentView.snapshotView(afterScreenUpdates: false) { + self.addSubview(snapshotView) + transition.setAlpha(view: snapshotView, alpha: 0.0) + } + + self.previewContainerContentView.frame = CGRect(origin: CGPoint(), size: self.previewContainerContentView.bounds.size) + previewContainerContentParentView.addSubview(self.previewContainerContentView) + } + + func update(containerSize: CGSize, transition: Transition) -> CGSize { + return self.previewContainerContentView.bounds.size + } +} diff --git a/submodules/TelegramUI/Components/VolumeSliderContextItem/BUILD b/submodules/TelegramUI/Components/VolumeSliderContextItem/BUILD index 07902042e2a..4804bb72430 100644 --- a/submodules/TelegramUI/Components/VolumeSliderContextItem/BUILD +++ b/submodules/TelegramUI/Components/VolumeSliderContextItem/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD b/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD index 2bbdb275afd..76ce5b55009 100644 --- a/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD +++ b/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Postbox", diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddUser.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddUser.imageset/Contents.json index d7f8a263fa0..c184976a0d0 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddUser.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddUser.imageset/Contents.json @@ -1,12 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "ic_lt_adduser.pdf" + "filename" : "ic_lt_adduser.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/18on_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/18on_24.pdf new file mode 100644 index 00000000000..356e9348078 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/18on_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/Contents.json new file mode 100644 index 00000000000..eda08930c8c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "18on_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/18off_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/18off_24.pdf new file mode 100644 index 00000000000..974496474d3 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/18off_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/Contents.json new file mode 100644 index 00000000000..b48dcd4e150 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "18off_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/FactCheck.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/FactCheck.imageset/Contents.json new file mode 100644 index 00000000000..80e11468972 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/FactCheck.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "verification_24 (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/FactCheck.imageset/verification_24 (2).pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/FactCheck.imageset/verification_24 (2).pdf new file mode 100644 index 00000000000..4a24cedf126 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/FactCheck.imageset/verification_24 (2).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/Contents.json new file mode 100644 index 00000000000..353e2a5ade2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tlogo_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/tlogo_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/tlogo_24.pdf new file mode 100644 index 00000000000..3293521e27f Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/tlogo_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/ClearRecent.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/ClearRecent.imageset/Contents.json new file mode 100644 index 00000000000..0c68b117b18 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/ClearRecent.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "clearrecents_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/ClearRecent.imageset/clearrecents_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/ClearRecent.imageset/clearrecents_30.pdf new file mode 100644 index 00000000000..dcaeecc3045 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/ClearRecent.imageset/clearrecents_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/Contents.json new file mode 100644 index 00000000000..6e965652df6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/EmptyHashtag.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/EmptyHashtag.imageset/Contents.json new file mode 100644 index 00000000000..19cb866d351 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/EmptyHashtag.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagempty_80.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/EmptyHashtag.imageset/tagempty_80.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/EmptyHashtag.imageset/tagempty_80.pdf new file mode 100644 index 00000000000..ff7a6c11ef2 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/EmptyHashtag.imageset/tagempty_80.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/RecentHashtag.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/RecentHashtag.imageset/Contents.json new file mode 100644 index 00000000000..dc214f3c0e1 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/RecentHashtag.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagsearch_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/RecentHashtag.imageset/tagsearch_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/RecentHashtag.imageset/tagsearch_30.pdf new file mode 100644 index 00000000000..4854fca9ab6 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/RecentHashtag.imageset/tagsearch_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/Contents.json new file mode 100644 index 00000000000..7ce72aef995 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "eyeoff_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/eyeoff_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/eyeoff_30.pdf new file mode 100644 index 00000000000..bad728a846f Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/eyeoff_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Contents.json b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Contents.json index 38f0c81fc22..6e965652df6 100644 --- a/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Contents.json @@ -1,9 +1,9 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "provides-namespace" : true } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/Contents.json new file mode 100644 index 00000000000..d46c98ac2d3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagsearch_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/tagsearch_24.pdf b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/tagsearch_24.pdf new file mode 100644 index 00000000000..893341aa10e Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/tagsearch_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Apple.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Apple.imageset/Contents.json new file mode 100644 index 00000000000..097594087a2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Apple.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "apple.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Apple.imageset/apple.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Apple.imageset/apple.pdf new file mode 100644 index 00000000000..c057740f515 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Apple.imageset/apple.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/BalanceStar.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/BalanceStar.imageset/Contents.json new file mode 100644 index 00000000000..ac82493b967 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/BalanceStar.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "balancestar_48 (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/BalanceStar.imageset/balancestar_48 (2).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/BalanceStar.imageset/balancestar_48 (2).pdf new file mode 100644 index 00000000000..323a72ad266 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/BalanceStar.imageset/balancestar_48 (2).pdf differ 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 00000000000..6e965652df6 --- /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/Fragment.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Fragment.imageset/Contents.json new file mode 100644 index 00000000000..b82158fd858 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Fragment.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "fragment.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Fragment.imageset/fragment.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Fragment.imageset/fragment.pdf new file mode 100644 index 00000000000..e29adc31a04 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Fragment.imageset/fragment.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Google.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Google.imageset/Contents.json new file mode 100644 index 00000000000..114b9d9975b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Google.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "android.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Google.imageset/android.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Google.imageset/android.pdf new file mode 100644 index 00000000000..16f23def6eb Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Google.imageset/android.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/Contents.json new file mode 100644 index 00000000000..2fc87745c76 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "particle.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/particle.png b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/particle.png new file mode 100644 index 00000000000..bd4f1d2524f Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/particle.png differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json new file mode 100644 index 00000000000..46aa1a67a6f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Star20 (3).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Star20 (3).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Star20 (3).pdf new file mode 100644 index 00000000000..11a9fc5e3bb Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Star20 (3).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMedium.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMedium.imageset/Contents.json new file mode 100644 index 00000000000..da87fd09d2b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMedium.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "star_18 (3).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMedium.imageset/star_18 (3).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMedium.imageset/star_18 (3).pdf new file mode 100644 index 00000000000..04a33dabefb Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMedium.imageset/star_18 (3).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarSmall.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarSmall.imageset/Contents.json new file mode 100644 index 00000000000..e283fcf4161 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarSmall.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "star_16 (3).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarSmall.imageset/star_16 (3).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarSmall.imageset/star_16 (3).pdf new file mode 100644 index 00000000000..60349a9d908 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarSmall.imageset/star_16 (3).pdf differ diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 9e10492a543..a96aa6768b9 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -127,6 +127,7 @@ public final class AccountContextImpl: AccountContext { public let wallpaperUploadManager: WallpaperUploadManager? private let themeUpdateManager: ThemeUpdateManager? public let inAppPurchaseManager: InAppPurchaseManager? + public let starsContext: StarsContext? public let peerChannelMemberCategoriesContextsManager = PeerChannelMemberCategoriesContextsManager() @@ -241,6 +242,19 @@ public final class AccountContextImpl: AccountContext { return availableReactionsValue.get() } + private var availableMessageEffectsValue: Promise? + public var availableMessageEffects: Signal { + let availableMessageEffectsValue: Promise + if let current = self.availableMessageEffectsValue { + availableMessageEffectsValue = current + } else { + availableMessageEffectsValue = Promise() + self.availableMessageEffectsValue = availableMessageEffectsValue + availableMessageEffectsValue.set(self.engine.stickers.availableMessageEffects()) + } + return availableMessageEffectsValue.get() + } + private var userLimitsConfigurationDisposable: Disposable? public private(set) var userLimits: EngineConfiguration.UserLimits @@ -281,11 +295,13 @@ public final class AccountContextImpl: AccountContext { self.themeUpdateManager = ThemeUpdateManagerImpl(sharedContext: sharedContext, account: account) self.inAppPurchaseManager = InAppPurchaseManager(engine: self.engine) + self.starsContext = self.engine.payments.peerStarsContext(peerId: account.peerId) } else { self.prefetchManager = nil self.wallpaperUploadManager = nil self.themeUpdateManager = nil self.inAppPurchaseManager = nil + self.starsContext = nil } if let locationManager = self.sharedContextImpl.locationManager, sharedContext.applicationBindings.isMainApp && !temp { diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index a9d55013cbd..8e06d7d6341 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -1,6 +1,7 @@ // MARK: Nicegram imports import AppLovinAdProvider import FeatNicegramHub +import FeatOnboarding import FeatTasks import NGAiChat import NGAnalytics @@ -11,13 +12,13 @@ import NGEntryPoint import NGEnv import NGLogging import NGLottie -import NGOnboarding import NGRemoteConfig import NGRepoTg import NGRepoUser import NGStats import NGStealthMode import NGStrings +import NicegramWallet import SubscriptionAnalytics import UIKit @@ -322,6 +323,15 @@ private class UserInterfaceStyleObserverWindow: UIWindow { } private let firebaseSecretStream = Promise<[String: String]>([:]) + private var firebaseRequestVerificationSecrets: [String: String] = [:] { + didSet { + if self.firebaseRequestVerificationSecrets != oldValue { + self.firebaseRequestVerificationSecretStream.set(.single(self.firebaseRequestVerificationSecrets)) + } + } + } + private let firebaseRequestVerificationSecretStream = Promise<[String: String]>([:]) + private var urlSessions: [URLSession] = [] private func urlSession(identifier: String) -> URLSession { if let existingSession = self.urlSessions.first(where: { $0.configuration.identifier == identifier }) { @@ -358,7 +368,8 @@ private class UserInterfaceStyleObserverWindow: UIWindow { private var alertActions: (primary: (() -> Void)?, other: (() -> Void)?)? - private let deviceToken = Promise(nil) + private let voipDeviceToken = Promise(nil) + private let regularDeviceToken = Promise(nil) func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { precondition(!testIsLaunched) @@ -366,7 +377,10 @@ private class UserInterfaceStyleObserverWindow: UIWindow { AppCache.appLaunchCount += 1 let _ = voipTokenPromise.get().start(next: { token in - self.deviceToken.set(.single(token)) + self.voipDeviceToken.set(.single(token)) + }) + let _ = notificationTokenPromise.get().start(next: { token in + self.regularDeviceToken.set(.single(token)) }) if #available(iOS 12.0, *) { @@ -396,8 +410,8 @@ private class UserInterfaceStyleObserverWindow: UIWindow { NGEntryPoint.onAppLaunch( env: Env( - apiBaseUrl: URL(string: NGENV.esim_api_url)!, - apiKey: NGENV.esim_api_key, + apiBaseUrl: URL(string: NGENV.ng_api_url)!, + apiKey: NGENV.ng_api_key, isAppStoreBuild: buildConfig.isAppStoreBuild, premiumProductId: NGENV.premium_bundle, privacyUrl: URL(string: NGENV.privacy_url)!, @@ -426,9 +440,81 @@ private class UserInterfaceStyleObserverWindow: UIWindow { }, remoteConfig: { RemoteConfigServiceImpl.shared - } + }, + walletData: .init( + env: { + .init( + appUrlScheme: buildConfig.appSpecificUrlScheme, + enableLogging: false, + keychainGroupIdentifier: NGENV.wallet.keychainGroupIdentifier, + nicegramApiBaseUrl: URL(string: NGENV.ng_api_url)! + .appendingPathComponent("v7/"), + walletConnectProjectId: NGENV.wallet.walletConnectProjectId, + web3AuthBackupQuestion: NGENV.wallet.web3AuthBackupQuestion, + web3AuthClientId: NGENV.wallet.web3AuthClientId, + web3AuthVerifier: NGENV.wallet.web3AuthVerifier + ) + }, + contactImageProvider: { + AnonymousContactImageProvider { contact in + guard let contextValue = self.contextValue else { + return nil + } + return await ContactImageProviderImpl.image( + context: contextValue.context, + contact: contact + ) + } + }, + contactMessageSender: { + AnonymousContactMessageSender { text, contactId in + guard let contextValue = self.contextValue else { + return + } + ContactMessageSenderImpl.send( + context: contextValue.context, + text: text, + contactId: contactId + ) + } + }, + contactsRetriever: { + AnonymousContactsRetriever { + guard let contextValue = self.contextValue else { + return [] + } + return await ContactsRetrieverImpl.getContacts( + context: contextValue.context + ) + } + }, + walletVerificationInterceptor: { + AnonymousWalletVerificationInterceptor( + shouldVerifyOnApplicationResignActive: { + guard let contextValue = self.contextValue else { + return false + } + return await WalletVerificationInterceptorImpl.shouldVerifyOnApplicationResignActive( + context: contextValue.context + ) + } + ) + } + ) ) + // MARK: Nicegram Unblock + let _ = (self.context.get() + |> take(1) + |> deliverOnMainQueue).start(next: { context in + if let context = context { + Queue().async { + self.fetchNGUserSettings(context.context.account.peerId.id._internalGetInt64Value()) + } + } + }) + // + let launchStartTime = CFAbsoluteTimeGetCurrent() let statusBarHost = ApplicationStatusBarHost() @@ -582,15 +668,22 @@ private class UserInterfaceStyleObserverWindow: UIWindow { let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManagerImpl.voipMaxLayer, voipVersions: PresentationCallManagerImpl.voipVersions(includeExperimental: true, includeReference: false).map { version, supportsVideo -> CallSessionManagerImplementationVersion in CallSessionManagerImplementationVersion(version: version, supportsVideo: supportsVideo) - }, appData: self.deviceToken.get() + }, appData: self.regularDeviceToken.get() |> map { token in - let data = buildConfig.bundleData(withAppToken: token, signatureDict: signatureDict) + let tokenEnvironment: String + #if DEBUG + tokenEnvironment = "sandbox" + #else + tokenEnvironment = "production" + #endif + + let data = buildConfig.bundleData(withAppToken: token, tokenType: "apns", tokenEnvironment: tokenEnvironment, signatureDict: signatureDict) if let data = data, let _ = String(data: data, encoding: .utf8) { } else { Logger.shared.log("data", "can't deserialize") } return data - }, autolockDeadine: autolockDeadine, encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: buildConfig.isICloudEnabled) + }, externalRequestVerificationStream: self.firebaseRequestVerificationSecretStream.get(), autolockDeadine: autolockDeadine, encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: buildConfig.isICloudEnabled) guard let appGroupUrl = maybeAppGroupUrl else { self.mainWindow?.presentNative(UIAlertController(title: nil, message: "Error 2", preferredStyle: .alert)) @@ -1540,7 +1633,8 @@ private class UserInterfaceStyleObserverWindow: UIWindow { if AppCache.wasOnboardingShown { onNicegramOnboardingComplete() } else { - if let rootController = window.rootViewController { + if #available(iOS 15.0, *), + let rootController = window.rootViewController { let controller = onboardingController( onComplete: { [weak rootController] in AppCache.wasOnboardingShown = true @@ -2137,16 +2231,6 @@ private class UserInterfaceStyleObserverWindow: UIWindow { self.fetchGlobalNGSettings() //self.fetchPremium() } - - let _ = (self.context.get() - |> take(1) - |> deliverOnMainQueue).start(next: { context in - if let context = context { - Queue().async { - self.fetchNGUserSettings(context.context.account.peerId.id._internalGetInt64Value()) - } - } - }) // SharedDisplayLinkDriver.shared.updateForegroundState(self.isActiveValue) @@ -2207,6 +2291,15 @@ private class UserInterfaceStyleObserverWindow: UIWindow { completionHandler(.newData) return } + + if let nonce = redactedPayload["verify_nonce"] as? String, let secret = redactedPayload["verify_secret"] as? String { + var firebaseRequestVerificationSecrets = self.firebaseRequestVerificationSecrets + firebaseRequestVerificationSecrets[nonce] = secret + self.firebaseRequestVerificationSecrets = firebaseRequestVerificationSecrets + + completionHandler(.newData) + return + } if userInfo["p"] == nil { completionHandler(.noData) diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index b206a10e12f..fb0ac7b444a 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -396,6 +396,15 @@ final class AuthorizedApplicationContext { return } + if firstMessage.restrictionReason(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) != nil { + return + } + if let chatPeer = firstMessage.peers[firstMessage.id.peerId] { + if EnginePeer(chatPeer).restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) != nil { + return + } + } + if inAppNotificationSettings.displayPreviews { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } strongSelf.notificationController.enqueue(ChatMessageNotificationItem(context: strongSelf.context, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, messages: messages, threadData: threadData, tapAction: { diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index c1c1126a1c8..4ca7746f485 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -172,6 +172,17 @@ private final class AttachmentFileContext: AttachmentMediaPickerContext { return .single(nil) } + var hasCaption: Bool { + return false + } + + var captionIsAboveMedia: Signal { + return .single(false) + } + + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { + } + public var loadingProgress: Signal { return .single(nil) } @@ -183,10 +194,10 @@ private final class AttachmentFileContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { } - func schedule() { + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { } func mainButtonAction() { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index ff1bfab1713..db633a4e8b3 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -1217,14 +1217,7 @@ extension ChatControllerImpl { } } - let transformedMessages: [EnqueueMessage] - if let silentPosting = silentPosting { - transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting) - } else if let scheduleTime = scheduleTime { - transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime) - } else { - transformedMessages = strongSelf.transformEnqueueMessages(messages) - } + let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting ?? false, scheduleTime: scheduleTime) var forwardedMessages: [[EnqueueMessage]] = [] var forwardSourcePeerIds = Set() @@ -1276,12 +1269,14 @@ extension ChatControllerImpl { donateSendMessageIntent(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, intentContext: .chat, peerIds: [peerId]) } else if case let .customChatContents(customChatContents) = strongSelf.subject { switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: customChatContents.enqueueMessages(messages: messages) strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() case let .businessLinkSetup(link): if messages.count > 1 { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: "The message text limit is 4096 characters", actions: [ + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.BusinessLink_AlertTextLimitText, actions: [ TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) ]), in: .window(.root)) @@ -1313,6 +1308,40 @@ extension ChatControllerImpl { strongSelf.updateChatPresentationInterfaceState(interactive: true, { $0.updatedShowCommands(false) }) } + if case let .customChatContents(customChatContents) = self.subject { + customChatContents.hashtagSearchResultsUpdate = { [weak self] searchResult in + guard let self else { + return + } + let (results, state) = searchResult + let isEmpty = results.totalCount == 0 + if isEmpty { + self.alwaysShowSearchResultsAsList = true + } + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in + var updatedState = current + if let data = current.search { + let messageIndices = results.messages.map({ $0.index }).sorted() + var currentIndex = messageIndices.last + if let previousResultId = data.resultsState?.currentId { + for index in messageIndices { + if index.id >= previousResultId { + currentIndex = index + break + } + } + } + updatedState = updatedState.updatedSearch(data.withUpdatedResultsState(ChatSearchResultsState(messageIndices: messageIndices, currentId: currentIndex?.id, state: state, totalCount: results.totalCount, completed: results.completed))) + } + if isEmpty { + updatedState = updatedState.updatedDisplayHistoryFilterAsList(true) + } + return updatedState + }) + self.searchResult.set(.single((results, state, .general(scope: .channels, tags: nil, minDate: nil, maxDate: nil)))) + } + } + self.chatDisplayNode.requestUpdateChatInterfaceState = { [weak self] transition, saveInterfaceState, f in self?.updateChatPresentationInterfaceState(transition: transition, interactive: true, saveInterfaceState: saveInterfaceState, { $0.updatedInterfaceState(f) }) } @@ -1371,6 +1400,8 @@ extension ChatControllerImpl { self?.enqueueGifData(data) case let .sticker(image, isMemoji): self?.enqueueStickerImage(image, isMemoji: isMemoji) + case let .animatedSticker(data): + self?.enqueueAnimatedStickerData(data) } } self.chatDisplayNode.updateTypingActivity = { [weak self] value in @@ -1449,7 +1480,9 @@ extension ChatControllerImpl { return } - if let resultsState = self.presentationInterfaceState.search?.resultsState, !resultsState.messageIndices.isEmpty { + if case let .customChatContents(contents) = self.presentationInterfaceState.subject, case .hashTagSearch = contents.kind { + self.chatDisplayNode.historyNode.scrollToEndOfHistory() + } else if let resultsState = self.presentationInterfaceState.search?.resultsState, !resultsState.messageIndices.isEmpty { if let currentId = resultsState.currentId, let index = resultsState.messageIndices.firstIndex(where: { $0.id == currentId }) { if index != resultsState.messageIndices.count - 1 { self.interfaceInteraction?.navigateMessageSearch(.later) @@ -1875,7 +1908,7 @@ extension ChatControllerImpl { } var updated = state.updatedInterfaceState { interfaceState in - return interfaceState.withUpdatedEditMessage(ChatEditMessageState(messageId: messageId, inputState: ChatTextInputState(inputText: inputText), disableUrlPreviews: disableUrlPreviews, inputTextMaxLength: inputTextMaxLength)) + return interfaceState.withUpdatedEditMessage(ChatEditMessageState(messageId: messageId, inputState: ChatTextInputState(inputText: inputText), disableUrlPreviews: disableUrlPreviews, inputTextMaxLength: inputTextMaxLength, mediaCaptionIsAbove: nil)) } let (updatedState, updatedPreviewQueryState) = updatedChatEditInterfaceMessageState(context: strongSelf.context, state: updated, message: message) @@ -2215,7 +2248,20 @@ extension ChatControllerImpl { } } - let text = trimChatInputText(convertMarkdownToAttributes(editMessage.inputState.inputText)) + var invertedMediaAttribute: InvertMediaMessageAttribute? + if let attribute = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) { + invertedMediaAttribute = attribute as? InvertMediaMessageAttribute + } + + if let mediaCaptionIsAbove = editMessage.mediaCaptionIsAbove { + if mediaCaptionIsAbove { + invertedMediaAttribute = InvertMediaMessageAttribute() + } else { + invertedMediaAttribute = nil + } + } + + let text = trimChatInputText(convertMarkdownToAttributes(expandedInputStateAttributedString(editMessage.inputState.inputText))) let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) var entitiesAttribute: TextEntitiesMessageAttribute? @@ -2300,7 +2346,7 @@ extension ChatControllerImpl { let currentWebpagePreviewAttribute = currentMessage.webpagePreviewAttribute ?? WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: nil, isManuallyAdded: true, isSafe: false) if currentMessage.text != text.string || currentEntities != entities || updatingMedia || webpagePreviewAttribute != currentWebpagePreviewAttribute || disableUrlPreview { - strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview) + strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertedMediaAttribute, disableUrlPreview: disableUrlPreview) } } @@ -3146,13 +3192,13 @@ extension ChatControllerImpl { } else { var contextItems: [ContextMenuItem] = [] contextItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_PinMessagesFor(EnginePeer(peer).compactDisplayTitle).string, textColor: .primary, icon: { _ in nil }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { pinAction(true, false) }) }))) contextItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_PinMessagesForMe, textColor: .primary, icon: { _ in nil }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { pinAction(true, true) }) }))) @@ -3165,13 +3211,13 @@ extension ChatControllerImpl { var contextItems: [ContextMenuItem] = [] contextItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_PinMessageAlert_PinAndNotifyMembers, textColor: .primary, icon: { _ in nil }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { pinAction(true, false) }) }))) contextItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_PinMessageAlert_OnlyPin, textColor: .primary, icon: { _ in nil }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { pinAction(false, false) }) }))) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift index 611e1b98967..9217def4356 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift @@ -476,7 +476,7 @@ extension ChatControllerImpl { self.updateDownButtonVisibility() } - func sendMediaRecording(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, viewOnce: Bool = false) { + func sendMediaRecording(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, viewOnce: Bool = false, messageEffect: ChatSendMessageEffect? = nil) { self.chatDisplayNode.updateRecordedMediaDeleted(false) guard let recordedMediaPreview = self.presentationInterfaceState.interfaceState.mediaDraftState else { @@ -517,6 +517,9 @@ extension ChatControllerImpl { if viewOnce { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) } + if let messageEffect { + attributes.append(EffectMessageAttribute(id: messageEffect.id)) + } let messages: [EnqueueMessage] = [.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: audio.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(audio.fileSize), attributes: [.Audio(isVoice: true, duration: Int(audio.duration), title: nil, performer: nil, waveform: waveformBuffer)])), threadId: self.chatLocation.threadId, replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])] @@ -542,7 +545,7 @@ extension ChatControllerImpl { donateSendMessageIntent(account: self.context.account, sharedContext: self.context.sharedContext, intentContext: .chat, peerIds: [peerId]) case .video: - self.videoRecorderValue?.sendVideoRecording(silentPosting: silentPosting, scheduleTime: scheduleTime) + self.videoRecorderValue?.sendVideoRecording(silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect) } } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift index 092847181da..41fe42586fa 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift @@ -29,7 +29,7 @@ extension ChatControllerImpl { guard let self else { return } - self.navigateToMessage(from: fromId, to: .id(id, params), forceInCurrentChat: fromId.peerId == id.peerId) + self.navigateToMessage(from: fromId, to: .id(id, params), forceInCurrentChat: fromId.peerId == id.peerId && !params.forceNew, forceNew: params.forceNew) } let _ = (self.context.engine.data.get( @@ -72,6 +72,7 @@ extension ChatControllerImpl { scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, forceInCurrentChat: Bool = false, + forceNew: Bool = false, dropStack: Bool = false, animated: Bool = true, completion: (() -> Void)? = nil, @@ -84,8 +85,10 @@ extension ChatControllerImpl { } var fromIndex: MessageIndex? + var fromMessage: Message? if let fromId = fromId, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(fromId) { fromIndex = message.index + fromMessage = message } else { if let message = self.chatDisplayNode.historyNode.anchorMessageInCurrentHistoryView() { fromIndex = message.index @@ -104,14 +107,20 @@ extension ChatControllerImpl { if case let .peer(peerId) = self.chatLocation, messageLocation.peerId == peerId, !isPinnedMessages, !isScheduledMessages { forceInCurrentChat = true } - if case .customChatContents = self.chatLocation { + if case .customChatContents = self.chatLocation, !forceNew { forceInCurrentChat = true } - if isPinnedMessages, let messageId = messageLocation.messageId { + if isPinnedMessages || forceNew, let messageId = messageLocation.messageId { + let peerSignal: Signal + if forceNew, let fromMessage, let peer = fromMessage.peers[fromMessage.id.peerId] { + peerSignal = .single(EnginePeer(peer)) + } else { + peerSignal = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)) + } let _ = (combineLatest( - self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)), - self.context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .local) + peerSignal, + self.context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: forceNew ? .cloud(skipLocal: false) : .local) |> `catch` { _ in return .single(.result([])) } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index 94f05aae8dd..7b01d7f4d48 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -112,7 +112,7 @@ extension ChatControllerImpl { // MARK: Nicegram HideReactions, account added if canAddMessageReactions(message: topMessage, account: context.account), let allowedReactions = allowedReactions, !topReactions.isEmpty { - actions.reactionItems = topReactions.map(ReactionContextItem.reaction) + actions.reactionItems = topReactions.map { ReactionContextItem.reaction(item: $0, icon: .none) } actions.selectedReactionItems = selectedReactions.reactions if message.areReactionsTags(accountPeerId: self.context.account.peerId) { if self.presentationInterfaceState.isPremium { @@ -132,7 +132,7 @@ extension ChatControllerImpl { if !actions.reactionItems.isEmpty { let reactionItems: [EmojiComponentReactionItem] = actions.reactionItems.compactMap { item -> EmojiComponentReactionItem? in switch item { - case let .reaction(reaction): + case let .reaction(reaction, _): return EmojiComponentReactionItem(reaction: reaction.reaction.rawValue, file: reaction.stillAnimation) default: return nil diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift new file mode 100644 index 00000000000..3fefe30c175 --- /dev/null +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift @@ -0,0 +1,31 @@ +import Foundation +import TelegramCore +import FactCheckAlertController + +extension ChatControllerImpl { + func openEditMessageFactCheck(messageId: EngineMessage.Id) { + guard let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) else { + return + } + var currentText: String = "" + var currentEntities: [MessageTextEntity] = [] + for attribute in message.attributes { + if let attribute = attribute as? FactCheckMessageAttribute, case let .Loaded(text, entities, _) = attribute.content { + currentText = text + currentEntities = entities + break + } + } + let controller = factCheckAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, value: currentText, entities: currentEntities, characterLimit: 4096, apply: { [weak self] text, entities in + guard let self else { + return + } + if !currentText.isEmpty && text.isEmpty { + let _ = self.context.engine.messages.deleteMessageFactCheck(messageId: messageId).startStandalone() + } else { + let _ = self.context.engine.messages.editMessageFactCheck(messageId: messageId, text: text, entities: entities).startStandalone() + } + }) + self.present(controller, in: .window(.root)) + } +} diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift index ae57377d9c1..2c4108565f6 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift @@ -131,8 +131,14 @@ extension ChatControllerImpl { switch navigation { case let .info(params): var recommendedChannels = false - if let params, params.switchToRecommendedChannels { - recommendedChannels = true + if let params { + if params.switchToRecommendedChannels { + recommendedChannels = true + } + if params.ignoreInSavedMessages && currentPeerId == self.context.account.peerId { + self.playShakeAnimation() + return + } } self.navigationButtonAction(.openChatInfo(expandAvatar: expandAvatar, recommendedChannels: recommendedChannels)) case let .chat(textInputState, _, _): @@ -186,6 +192,9 @@ extension ChatControllerImpl { if case let .info(params) = navigation, let params, params.switchToRecommendedChannels { mode = .recommendedChannels } + if peer.id == strongSelf.context.account.peerId { + mode = .myProfile + } var expandAvatar = expandAvatar if peer.smallProfileImage == nil { expandAvatar = false diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift new file mode 100644 index 00000000000..b332e0a4d09 --- /dev/null +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift @@ -0,0 +1,251 @@ +import Foundation +import UIKit +import SwiftSignalKit +import Postbox +import TelegramCore +import AsyncDisplayKit +import Display +import TelegramNotices +import ContextUI +import AccountContext +import ChatMessageItemView +import ChatMessageItemCommon +import AvatarNode +import UndoUI +import MessageUI +import PeerInfoUI + +extension ChatControllerImpl: MFMessageComposeViewControllerDelegate { + func openPhoneContextMenu(number: String, peer: EnginePeer?, message: Message, contentNode: ContextExtractedContentContainingNode, messageNode: ASDisplayNode, frame: CGRect, anyRecognizer: UIGestureRecognizer?, location: CGPoint?) -> Void { + if self.presentationInterfaceState.interfaceState.selectionState != nil { + return + } + + self.dismissAllTooltips() + + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer + let gesture: ContextGesture? = anyRecognizer as? ContextGesture + + if let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) { + (self.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures() + self.chatDisplayNode.cancelInteractiveKeyboardGestures() + var updatedMessages = messages + for i in 0 ..< updatedMessages.count { + if updatedMessages[i].id == message.id { + let message = updatedMessages.remove(at: i) + updatedMessages.insert(message, at: 0) + break + } + } + + self.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + + let source: ContextContentSource + if let location = location { + source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) + } else { + source = .extracted(ChatMessagePhoneContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) + } + + let phoneNumber: String + if let peer, case let .user(user) = peer, let phone = user.phone { + phoneNumber = "+\(phone)" + } else { + phoneNumber = number + } + + var items: [ContextMenuItem] = [] + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_AddToContacts, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + guard let self, let c else { + return + } + let basicData = DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [ + DeviceContactPhoneNumberData(label: "", value: phoneNumber) + ]) + let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + + pushContactContextOptionsController(context: self.context, contextController: c, presentationData: self.presentationData, peer: nil, contactData: contactData, parentController: self, push: { [weak self] c in + self?.push(c) + }) + })) + ) + items.append(.separator) + if let peer { + if peer.id == self.context.account.peerId { + + } else { + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_SendMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + guard let self else { + return + } + self.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil) + })) + ) + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_TelegramVoiceCall, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + self.controllerInteraction?.callPeer(peer.id, false) + })) + ) + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_TelegramVideoCall, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoCall"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + self.controllerInteraction?.callPeer(peer.id, true) + })) + ) + } + } else { + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_InviteToTelegram, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Telegram"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + self.inviteToTelegram(numbers: [number]) + })) + ) + } + if number.hasPrefix("+888") { + + } else { + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_CallViaCarrier, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PhoneCall"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + self.openUrl("tel:\(phoneNumber)", concealed: false) + })) + ) + } + + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_CopyNumber, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + + UIPasteboard.general.string = number + + self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_PhoneCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })) + ) + + items.append(.separator) + if let peer { + let avatarSize = CGSize(width: 28.0, height: 28.0) + let avatarSignal = peerAvatarCompleteImage(account: self.context.account, peer: peer, size: avatarSize) + + let subtitle = NSMutableAttributedString(string: self.presentationData.strings.Chat_Context_Phone_ViewProfile + " >") + if let range = subtitle.string.range(of: ">"), let arrowImage = UIImage(bundleImageName: "Item List/InlineTextRightArrow") { + subtitle.addAttribute(.attachment, value: arrowImage, range: NSRange(range, in: subtitle.string)) + subtitle.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: subtitle.string)) + } + + items.append( + .action(ContextMenuActionItem(text: peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), textLayout: .secondLineWithAttributedValue(subtitle), icon: { theme in return nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), iconPosition: .left, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + self.openPeer(peer: peer, navigation: .info(ChatControllerInteractionNavigateToPeer.InfoParams(ignoreInSavedMessages: true)), fromMessage: nil) + })) + ) + } else { + let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_NotOnTelegram, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)) + ) + } + + self.canReadHistory.set(false) + + let controller = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false) + controller.dismissed = { [weak self] in + self?.canReadHistory.set(true) + } + + self.window?.presentInGlobalOverlay(controller) + } + } + + private func inviteToTelegram(numbers: [String]) { + if MFMessageComposeViewController.canSendText() { + let composer = MFMessageComposeViewController() + composer.messageComposeDelegate = self + composer.recipients = Array(Set(numbers)) + let url = self.presentationData.strings.InviteText_URL + let body = self.presentationData.strings.InviteText_SingleContact(url).string + composer.body = body + self.messageComposeController = composer + if let window = self.view.window { + window.rootViewController?.present(composer, animated: true) + } + } + } + + @objc public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { + self.messageComposeController = nil + + controller.dismiss(animated: true, completion: nil) + } +} + +private final class ChatMessagePhoneContextExtractedContentSource: ContextExtractedContentSource { + let keepInPlace: Bool = false + let ignoreContentTouches: Bool = true + let blurBackground: Bool = true + let adjustContentHorizontally = true + + private weak var chatNode: ChatControllerNode? + private let contentNode: ContextExtractedContentContainingNode + + var shouldBeDismissed: Signal { + return .single(false) + } + + init(chatNode: ChatControllerNode, contentNode: ContextExtractedContentContainingNode) { + self.chatNode = chatNode + self.contentNode = contentNode + } + + func takeView() -> ContextControllerTakeViewInfo? { + guard let chatNode = self.chatNode else { + return nil + } + + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + transition.updateAlpha(node: self.contentNode.contentNode, alpha: 1.0) + + return ContextControllerTakeViewInfo(containingItem: .node(self.contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) + } + + func putBack() -> ContextControllerPutBackViewInfo? { + guard let chatNode = self.chatNode else { + return nil + } + + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + transition.updateAlpha(node: self.contentNode.contentNode, alpha: 0.0, completion: { _ in + self.contentNode.removeFromSupernode() + }) + + return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) + } +} diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift new file mode 100644 index 00000000000..937c739c156 --- /dev/null +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift @@ -0,0 +1,231 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramUIPreferences +import AccountContext +import MediaPickerUI +import MediaPasteboardUI +import LegacyMediaPickerUI +import MediaEditor + +extension ChatControllerImpl { + func displayPasteMenu(_ subjects: [MediaPickerScreen.Subject.Media]) { + let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in + let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) + return entry ?? GeneratedMediaStoreSettings.defaultSettings + } + |> deliverOnMainQueue).startStandalone(next: { [weak self] settings in + if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + strongSelf.chatDisplayNode.dismissInput() + let controller = mediaPasteboardScreen( + context: strongSelf.context, + updatedPresentationData: strongSelf.updatedPresentationData, + peer: EnginePeer(peer), + subjects: subjects, + presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in + if let strongSelf = self { + strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + }) + } + }, + getSourceRect: nil + ) + controller.navigationPresentation = .flatModal + strongSelf.push(controller) + } + }) + } + + func enqueueGifData(_ data: Data) { + self.enqueueMediaMessageDisposable.set((legacyEnqueueGifMessage(account: self.context.account, data: data) |> deliverOnMainQueue).startStrict(next: { [weak self] message in + if let strongSelf = self { + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + strongSelf.sendMessages([message].map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }) + } + })) + } + + func enqueueVideoData(_ data: Data) { + self.enqueueMediaMessageDisposable.set((legacyEnqueueGifMessage(account: self.context.account, data: data) |> deliverOnMainQueue).startStrict(next: { [weak self] message in + if let strongSelf = self { + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + strongSelf.sendMessages([message].map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }) + } + })) + } + + func enqueueStickerImage(_ image: UIImage, isMemoji: Bool) { + let size = image.size.aspectFitted(CGSize(width: 512.0, height: 512.0)) + self.enqueueMediaMessageDisposable.set((convertToWebP(image: image, targetSize: size, targetBoundingSize: size, quality: 0.9) |> deliverOnMainQueue).startStrict(next: { [weak self] data in + if let strongSelf = self, !data.isEmpty { + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + + var fileAttributes: [TelegramMediaFileAttribute] = [] + fileAttributes.append(.FileName(fileName: "sticker.webp")) + fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) + fileAttributes.append(.ImageSize(size: PixelDimensions(size))) + + let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes) + let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + strongSelf.sendMessages([message].map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }) + } + })) + } + + func enqueueStickerFile(_ file: TelegramMediaFile) { + let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + + let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject + self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + self.sendMessages([message].map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }) + + Queue.mainQueue().after(3.0) { + if let message = self.chatDisplayNode.historyNode.lastVisbleMesssage(), let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, file.isSticker { + self.context.engine.stickers.addRecentlyUsedSticker(fileReference: .message(message: MessageReference(message), media: file)) + } + } + } + + func enqueueAnimatedStickerData(_ data: Data) { + guard let animatedImage = UIImage.animatedImageFromData(data: data), let thumbnailImage = animatedImage.images.first else { + return + } + + let dimensions = PixelDimensions(width: 1080, height: 1920) + let image = generateImage(dimensions.cgSize, opaque: false, scale: 1.0, rotatedContext: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + })! + + let blackImage = generateImage(dimensions.cgSize, opaque: true, scale: 1.0, rotatedContext: { size, context in + context.setFillColor(UIColor.black.cgColor) + context.fill(CGRect(origin: .zero, size: size)) + })! + + let stickerEntity = DrawingStickerEntity(content: .animatedImage(data, thumbnailImage)) + stickerEntity.referenceDrawingSize = dimensions.cgSize + stickerEntity.position = CGPoint(x: dimensions.cgSize.width / 2.0, y: dimensions.cgSize.height / 2.0) + stickerEntity.scale = 3.5 + + let entities: [CodableDrawingEntity] = [ + .sticker(stickerEntity) + ] + + let values = MediaEditorValues( + peerId: self.context.account.peerId, + originalDimensions: dimensions, + cropOffset: .zero, + cropRect: nil, + cropScale: 1.0, + cropRotation: 1.0, + cropMirroring: false, + cropOrientation: .up, + gradientColors: [.clear, .clear], + videoTrimRange: nil, + videoIsMuted: false, + videoIsFullHd: false, + videoIsMirrored: false, + videoVolume: nil, + additionalVideoPath: nil, + additionalVideoIsDual: false, + additionalVideoPosition: nil, + additionalVideoScale: nil, + additionalVideoRotation: nil, + additionalVideoPositionChanges: [], + additionalVideoTrimRange: nil, + additionalVideoOffset: nil, + additionalVideoVolume: nil, + nightTheme: false, + drawing: nil, + maskDrawing: blackImage, + entities: entities, + toolValues: [:], + audioTrack: nil, + audioTrackTrimRange: nil, + audioTrackOffset: nil, + audioTrackVolume: nil, + audioTrackSamples: nil, + qualityPreset: nil + ) + + let configuration = recommendedVideoExportConfiguration(values: values, duration: animatedImage.duration, frameRate: 30.0, isSticker: true) + + let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).webm" + let videoExport = MediaEditorVideoExport( + postbox: self.context.account.postbox, + subject: .image(image: image), + configuration: configuration, + outputPath: path + ) + videoExport.start() + + let _ = (videoExport.status + |> deliverOnMainQueue).startStandalone(next: { [weak self] status in + guard let self else { + return + } + switch status { + case .completed: + var fileAttributes: [TelegramMediaFileAttribute] = [] + fileAttributes.append(.FileName(fileName: "sticker.webm")) + fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) + fileAttributes.append(.Video(duration: animatedImage.duration, size: PixelDimensions(width: 512, height: 512), flags: [], preloadSize: nil)) + + let previewRepresentations: [TelegramMediaImageRepresentation] = [] +// if let thumbnailResource { +// previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil)) +// } + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + self.context.account.postbox.mediaBox.copyResourceData(resource.id, fromTempPath: path) + + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/webm", size: 0, attributes: fileAttributes) + self.enqueueStickerFile(file) + default: + break + } + }) + +// self.stickerVideoExport = videoExport + } +} diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPlayMessageEffect.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPlayMessageEffect.swift new file mode 100644 index 00000000000..62cbe695e24 --- /dev/null +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPlayMessageEffect.swift @@ -0,0 +1,22 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramUIPreferences +import AccountContext +import ChatMessageItemView + +extension ChatControllerImpl { + func playMessageEffect(message: Message) { + var messageItemNode: ChatMessageItemView? + self.chatDisplayNode.historyNode.forEachVisibleMessageItemNode { itemNode in + if let item = itemNode.item, item.message.id == message.id { + messageItemNode = itemNode + } + } + + messageItemNode?.playMessageEffect(force: true) + } +} diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 2e09f56eae8..179f566e64a 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -149,13 +149,14 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode: for media in message.media { if let media = media as? TelegramMediaFile, media.isMusic { isMusic = true + if !message.text.isEmpty { + hasCaptions = true + } } else if media is TelegramMediaDice { isDice = true - } else { + } else if media is TelegramMediaImage || media is TelegramMediaFile { if !message.text.isEmpty { - if media is TelegramMediaImage || media is TelegramMediaFile { - hasCaptions = true - } + hasCaptions = true } } } @@ -353,7 +354,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch subItems.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in - c.popItems() + c?.popItems() }))) subItems.append(.separator) @@ -378,7 +379,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch f(.default) }))) - c.pushItems(items: .single(ContextController.Items(content: .list(subItems), dismissed: { [weak contentNode] in + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems), dismissed: { [weak contentNode] in guard let contentNode else { return } diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift index 29e763d7c7a..cbc77c660a4 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift @@ -1,4 +1,4 @@ -// MARK: Nicegram +// MARK: Nicegram TranslateEnteredMessage import NGTranslate import NGUI // @@ -13,6 +13,16 @@ import TelegramCore import TelegramNotices import ChatSendMessageActionUI import AccountContext +import TopMessageReactions +import ReactionSelectionNode +import ChatControllerInteraction +import ChatSendAudioMessageContextPreview + +extension ChatSendMessageEffect { + convenience init(_ effect: ChatSendMessageActionSheetController.SendParameters.Effect) { + self.init(id: effect.id) + } +} func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, node: ASDisplayNode, gesture: ContextGesture) { guard let peerId = selfController.chatLocation.peerId, let textInputView = selfController.chatDisplayNode.textInputView(), let layout = selfController.validLayout else { @@ -32,30 +42,46 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no hasEntityKeyboard = true } - let _ = (selfController.context.account.viewTracker.peerView(peerId) - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak selfController] peerView in + let effectItems: Signal<[ReactionItem]?, NoError> + if peerId != selfController.context.account.peerId && peerId.namespace == Namespaces.Peer.CloudUser { + effectItems = effectMessageReactions(context: selfController.context) + |> map(Optional.init) + } else { + effectItems = .single(nil) + } + + let availableMessageEffects = selfController.context.availableMessageEffects |> take(1) + let hasPremium = selfController.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: selfController.context.account.peerId)) + |> map { peer -> Bool in + guard case let .user(user) = peer else { + return false + } + return user.isPremium + } + + let editMessages: Signal<[EngineMessage], NoError> + if let editMessage = selfController.presentationInterfaceState.interfaceState.editMessage { + editMessages = selfController.context.engine.data.get( + TelegramEngine.EngineData.Item.Messages.MessageGroup(id: editMessage.messageId) + ) + } else { + editMessages = .single([]) + } + + let _ = (combineLatest( + selfController.context.account.viewTracker.peerView(peerId) |> take(1), + effectItems, + availableMessageEffects, + hasPremium, + editMessages + ) + |> deliverOnMainQueue).startStandalone(next: { [weak selfController] peerView, effectItems, availableMessageEffects, hasPremium, editMessages in guard let selfController, let peer = peerViewMainPeer(peerView) else { return } - var sendWhenOnlineAvailable = false - if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status { - let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) - if currentTime > until { - sendWhenOnlineAvailable = true - } - } - if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 { - sendWhenOnlineAvailable = false - } - - if sendWhenOnlineAvailable { - let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: selfController.context.sharedContext.accountManager, count: 4).startStandalone() - } // MARK: Nicegram TranslateEnteredMessage - let peerId = selfController.presentationInterfaceState.chatLocation.peerId - let isSecretChat = (peerId?.namespace == Namespaces.Peer.SecretChat) + let isSecretChat = (peerId.namespace == Namespaces.Peer.SecretChat) let inputText = selfController.presentationInterfaceState.interfaceState.effectiveInputState.inputText.string let isInputTextEmpty = inputText @@ -63,80 +89,244 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no .isEmpty let canTranslate = !isSecretChat && !isInputTextEmpty + + let nicegramData = ChatSendMessageContextNicegramData( + canTranslate: canTranslate, + translate: { [weak selfController] in + guard let selfController else { return } + let chatId = selfController.chatLocation.peerId + let textToTranslate = selfController.presentationInterfaceState.interfaceState.effectiveInputState.inputText.string + let _ = (translateEnteredText(text: textToTranslate, chatId: chatId, context: selfController.context) + |> deliverOnMainQueue).start( + next: { translated in + selfController.updateChatPresentationInterfaceState(interactive: true, { state in + let newTextInputState = ChatTextInputState(inputText: NSAttributedString(string: translated)) + return state.updatedInterfaceState { interfaceState in + return interfaceState.withUpdatedEffectiveInputState(newTextInputState) + } + }) + }, error: { error in + let errorDescription: String + switch error { + case .toLanguageNotFound: + errorDescription = "Messages.TranslateError.ToLanguageNotFound" + case .translate: + errorDescription = "Messages.TranslateError" + } + let c = getIAPErrorController(context: selfController.context, errorDescription, selfController.presentationData) + selfController.controllerInteraction?.presentGlobalOverlayController(c, nil) + }) + }, + chooseLanguage: { [weak selfController] in + guard let selfController else { return } + let chatId = selfController.chatLocation.peerId + let _ = (getLanguageCode(forChatWith: chatId, context: selfController.context) + |> deliverOnMainQueue).start(next: { code in + let c = languageListController(context: selfController.context, selectedLanguageCode: code, selectLanguage: { code in + setLanguageCode(code, forChatWith: chatId) + }) + c.navigationPresentation = .modal + selfController.push(c) + }) + } + ) // - let controller = ChatSendMessageActionSheetController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, peerId: selfController.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputView, canSendWhenOnline: sendWhenOnlineAvailable, completion: { [weak selfController] in - guard let selfController else { + if let editMessage = selfController.presentationInterfaceState.interfaceState.editMessage { + if editMessages.isEmpty { return } - selfController.supportedOrientations = previousSupportedOrientations - }, sendMessage: { [weak selfController] mode in - guard let selfController else { - return + + var mediaPreview: ChatSendMessageContextScreenMediaPreview? + if editMessages.contains(where: { message in + return message.media.contains(where: { media in + if media is TelegramMediaImage { + return true + } else if let file = media as? TelegramMediaFile, file.isVideo { + return true + } + return false + }) + }) { + mediaPreview = ChatSendGroupMediaMessageContextPreview( + context: selfController.context, + presentationData: selfController.presentationData, + wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode, + messages: editMessages + ) } - switch mode { - case .generic: - selfController.controllerInteraction?.sendCurrentMessage(false) - case .silently: - selfController.controllerInteraction?.sendCurrentMessage(true) - case .whenOnline: - selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp) { [weak selfController] in + + let mediaCaptionIsAbove: Bool + if let value = editMessage.mediaCaptionIsAbove { + mediaCaptionIsAbove = value + } else { + mediaCaptionIsAbove = editMessages.contains(where: { + $0.attributes.contains(where: { + $0 is InvertMediaMessageAttribute + }) + }) + } + + let controller = makeChatSendMessageActionSheetController( + // MARK: Nicegram TranslateEnteredMessage + nicegramData: nicegramData, + // + context: selfController.context, + updatedPresentationData: selfController.updatedPresentationData, + peerId: selfController.presentationInterfaceState.chatLocation.peerId, + params: .editMessage(SendMessageActionSheetControllerParams.EditMessage( + messages: editMessages, + mediaPreview: mediaPreview, + mediaCaptionIsAbove: (mediaCaptionIsAbove, { [weak selfController] updatedMediaCaptionIsAbove in + guard let selfController else { + return + } + selfController.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in + return state.updatedInterfaceState { interfaceState in + guard var editMessage = interfaceState.editMessage else { + return interfaceState + } + editMessage.mediaCaptionIsAbove = updatedMediaCaptionIsAbove + return interfaceState.withUpdatedEditMessage(editMessage) + } + }) + }) + )), + hasEntityKeyboard: hasEntityKeyboard, + gesture: gesture, + sourceSendButton: node, + textInputView: textInputView, + emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider, + wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode, + completion: { [weak selfController] in guard let selfController else { return } - selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: selfController.presentationInterfaceState.subject != .scheduledMessages, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } - }) - selfController.openScheduledMessages() + selfController.supportedOrientations = previousSupportedOrientations + }, + sendMessage: { [weak selfController] mode, parameters in + guard let selfController else { + return + } + selfController.interfaceInteraction?.editMessage() + }, + schedule: { _ in + }, openPremiumPaywall: { [weak selfController] c in + guard let selfController else { + return + } + selfController.push(c) + }, + reactionItems: nil, + availableMessageEffects: nil, + isPremium: hasPremium + ) + selfController.sendMessageActionsController = controller + if layout.isNonExclusive { + selfController.present(controller, in: .window(.root)) + } else { + selfController.presentInGlobalOverlay(controller, with: nil) + } + } else { + var sendWhenOnlineAvailable = false + if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status { + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if currentTime > until { + sendWhenOnlineAvailable = true } } - }, /* MARK: Nicegram TranslateEnteredMessage (canTranslate, translate, chooseLanguage) */ canTranslate: canTranslate, translate: { [weak selfController] in - guard let selfController else { return } - let chatId = selfController.chatLocation.peerId - let textToTranslate = selfController.presentationInterfaceState.interfaceState.effectiveInputState.inputText.string - let _ = (translateEnteredText(text: textToTranslate, chatId: chatId, context: selfController.context) - |> deliverOnMainQueue).start( - next: { translated in - selfController.updateChatPresentationInterfaceState(interactive: true, { state in - let newTextInputState = ChatTextInputState(inputText: NSAttributedString(string: translated)) - return state.updatedInterfaceState { interfaceState in - return interfaceState.withUpdatedEffectiveInputState(newTextInputState) - } - }) - }, error: { error in - let errorDescription: String - switch error { - case .toLanguageNotFound: - errorDescription = "Messages.TranslateError.ToLanguageNotFound" - case .translate: - errorDescription = "Messages.TranslateError" + if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 { + sendWhenOnlineAvailable = false + } + + if sendWhenOnlineAvailable { + let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: selfController.context.sharedContext.accountManager, count: 4).startStandalone() + } + + var mediaPreview: ChatSendMessageContextScreenMediaPreview? + if let videoRecorderValue = selfController.videoRecorderValue { + mediaPreview = videoRecorderValue.makeSendMessageContextPreview() + } + if let mediaDraftState = selfController.presentationInterfaceState.interfaceState.mediaDraftState { + if case let .audio(audio) = mediaDraftState { + mediaPreview = ChatSendAudioMessageContextPreview( + context: selfController.context, + presentationData: selfController.presentationData, + wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode, + waveform: audio.waveform + ) } - let c = getIAPErrorController(context: selfController.context, errorDescription, selfController.presentationData) - selfController.controllerInteraction?.presentGlobalOverlayController(c, nil) - }) - }, chooseLanguage: { [weak selfController] in - guard let selfController else { return } - let chatId = selfController.chatLocation.peerId - let _ = (getLanguageCode(forChatWith: chatId, context: selfController.context) - |> deliverOnMainQueue).start(next: { code in - let c = languageListController(context: selfController.context, selectedLanguageCode: code, selectLanguage: { code in - setLanguageCode(code, forChatWith: chatId) - }) - c.navigationPresentation = .modal - selfController.push(c) - }) - }, schedule: { [weak selfController] in - guard let selfController else { - return } - selfController.controllerInteraction?.scheduleCurrentMessage() - }) - controller.emojiViewProvider = selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider - selfController.sendMessageActionsController = controller - if layout.isNonExclusive { - selfController.present(controller, in: .window(.root)) - } else { - selfController.presentInGlobalOverlay(controller, with: nil) + + let controller = makeChatSendMessageActionSheetController( + // MARK: Nicegram TranslateEnteredMessage + nicegramData: nicegramData, + // + context: selfController.context, + updatedPresentationData: selfController.updatedPresentationData, + peerId: selfController.presentationInterfaceState.chatLocation.peerId, + params: .sendMessage(SendMessageActionSheetControllerParams.SendMessage( + isScheduledMessages: false, + mediaPreview: mediaPreview, + mediaCaptionIsAbove: nil, + attachment: false, + canSendWhenOnline: sendWhenOnlineAvailable, + forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? [] + )), + hasEntityKeyboard: hasEntityKeyboard, + gesture: gesture, + sourceSendButton: node, + textInputView: textInputView, + emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider, + wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode, + completion: { [weak selfController] in + guard let selfController else { + return + } + selfController.supportedOrientations = previousSupportedOrientations + }, + sendMessage: { [weak selfController] mode, parameters in + guard let selfController else { + return + } + switch mode { + case .generic: + selfController.controllerInteraction?.sendCurrentMessage(false, parameters?.effect.flatMap(ChatSendMessageEffect.init)) + case .silently: + selfController.controllerInteraction?.sendCurrentMessage(true, parameters?.effect.flatMap(ChatSendMessageEffect.init)) + case .whenOnline: + selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: parameters?.effect.flatMap(ChatSendMessageEffect.init)) { [weak selfController] in + guard let selfController else { + return + } + selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: selfController.presentationInterfaceState.subject != .scheduledMessages, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } + }) + selfController.openScheduledMessages() + } + } + }, + schedule: { [weak selfController] params in + guard let selfController else { + return + } + selfController.controllerInteraction?.scheduleCurrentMessage(params) + }, openPremiumPaywall: { [weak selfController] c in + guard let selfController else { + return + } + selfController.push(c) + }, + reactionItems: (!textInputView.text.isEmpty || mediaPreview != nil) ? effectItems : nil, + availableMessageEffects: availableMessageEffects, + isPremium: hasPremium + ) + selfController.sendMessageActionsController = controller + if layout.isNonExclusive { + selfController.present(controller, in: .window(.root)) + } else { + selfController.presentInGlobalOverlay(controller, with: nil) + } } }) } diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 09780c583b9..71709a4294d 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -223,6 +223,8 @@ func updateChatPresentationInterfaceStateImpl( var canHaveUrlPreview = true if case let .customChatContents(customChatContents) = updatedChatPresentationInterfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: break case .businessLinkSetup: diff --git a/submodules/TelegramUI/Sources/ChatAgeRestrictionAlertController.swift b/submodules/TelegramUI/Sources/ChatAgeRestrictionAlertController.swift new file mode 100644 index 00000000000..b1cfa2f40bc --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatAgeRestrictionAlertController.swift @@ -0,0 +1,303 @@ +import Foundation +import UIKit +import SwiftSignalKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import AppBundle +import AvatarNode +import CheckNode +import Markdown + +private let textFont = Font.regular(13.0) +private let boldTextFont = Font.semibold(13.0) + +private func formattedText(_ text: String, color: UIColor, textAlignment: NSTextAlignment = .natural) -> NSAttributedString { + return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: color), bold: MarkdownAttributeSet(font: boldTextFont, textColor: color), link: MarkdownAttributeSet(font: textFont, textColor: color), linkAttribute: { _ in return nil}), textAlignment: textAlignment) +} + +private final class ChatAgeRestrictionAlertContentNode: AlertContentNode { + private let strings: PresentationStrings + private let title: String + private let text: String + + private let titleNode: ImmediateTextNode + private let textNode: ASTextNode + + private let alwaysCheckNode: InteractiveCheckNode + private let alwaysLabelNode: ASTextNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private var validLayout: CGSize? + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + var alwaysShow: Bool = false { + didSet { + self.alwaysCheckNode.setSelected(self.alwaysShow, animated: true) + } + } + + init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, title: String, text: String, actions: [TextAlertAction]) { + self.strings = strings + self.title = title + self.text = text + + self.titleNode = ImmediateTextNode() + self.titleNode.displaysAsynchronously = false + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.textAlignment = .center + + self.textNode = ASTextNode() + self.textNode.displaysAsynchronously = false + self.textNode.maximumNumberOfLines = 0 + self.textNode.lineSpacing = 0.1 + + self.alwaysCheckNode = InteractiveCheckNode(theme: CheckNodeTheme(backgroundColor: theme.accentColor, strokeColor: theme.contrastColor, borderColor: theme.controlBorderColor, overlayBorder: false, hasInset: false, hasShadow: false)) + self.alwaysLabelNode = ASTextNode() + self.alwaysLabelNode.maximumNumberOfLines = 2 + self.alwaysLabelNode.isUserInteractionEnabled = true + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + + self.addSubnode(self.alwaysCheckNode) + self.addSubnode(self.alwaysLabelNode) + + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + self.alwaysCheckNode.valueChanged = { [weak self] value in + if let strongSelf = self { + strongSelf.alwaysShow = !strongSelf.alwaysShow + } + } + + self.updateTheme(theme) + } + + override func didLoad() { + super.didLoad() + + self.alwaysLabelNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.alwaysTap(_:)))) + } + + @objc private func alwaysTap(_ gestureRecognizer: UITapGestureRecognizer) { + if self.alwaysCheckNode.isUserInteractionEnabled { + self.alwaysShow = !self.alwaysShow + } + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) + + self.alwaysLabelNode.attributedText = formattedText("Always show 18+ media", color: theme.primaryColor) + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width, 270.0) + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 17.0) + + let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 32.0, height: size.height)) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) + origin.y += titleSize.height + 6.0 + + var entriesHeight: CGFloat = 0.0 + + let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) + origin.y += textSize.height + + if self.alwaysLabelNode.supernode != nil { + origin.y += 21.0 + entriesHeight += 21.0 + + let checkSize = CGSize(width: 22.0, height: 22.0) + let condensedSize = CGSize(width: size.width - 76.0, height: size.height) + + let alwaysSize = self.alwaysLabelNode.measure(condensedSize) + let totalWidth = checkSize.width + alwaysSize.width + 12.0 + let originX = floorToScreenPixels((size.width - totalWidth) / 2.0) + transition.updateFrame(node: self.alwaysLabelNode, frame: CGRect(origin: CGPoint(x: originX + checkSize.width + 12.0, y: origin.y), size: alwaysSize)) + transition.updateFrame(node: self.alwaysCheckNode, frame: CGRect(origin: CGPoint(x: originX, y: origin.y - 2.0), size: checkSize)) + origin.y += alwaysSize.height + entriesHeight += alwaysSize.height + } + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = TextAlertContentActionLayout.vertical + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) + + let contentWidth = max(size.width, minActionsWidth) + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + let resultSize = CGSize(width: contentWidth, height: titleSize.height + textSize.height + entriesHeight + actionsHeight + 8.0 + insets.top + insets.bottom) + + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + return resultSize + } +} + +public func chatAgeRestrictionAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, completion: @escaping (Bool) -> Void) -> AlertController { + let theme = defaultDarkColorPresentationTheme + let presentationData: PresentationData + if let updatedPresentationData { + presentationData = updatedPresentationData.initial + } else { + presentationData = context.sharedContext.currentPresentationData.with { $0 } + } + let strings = presentationData.strings + + //TODO:localize + var dismissImpl: ((Bool) -> Void)? + var getContentNodeImpl: (() -> ChatAgeRestrictionAlertContentNode?)? + let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: "View Anyway", action: { + if let alwaysShow = getContentNodeImpl?()?.alwaysShow { + completion(alwaysShow) + } else { + completion(false) + } + dismissImpl?(true) + }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissImpl?(true) + })] + + let title = "18+ Content" + let text = "This media may contain sensitive content suitable only for adults.\nDo you still want to view it?" + + let contentNode = ChatAgeRestrictionAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, actions: actions) + getContentNodeImpl = { [weak contentNode] in + return contentNode + } + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) + dismissImpl = { [weak controller] animated in + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + return controller +} diff --git a/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift index fbe13ebdb1d..b17db6edb03 100644 --- a/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift @@ -194,6 +194,8 @@ final class ChatBusinessLinkTitlePanelNode: ChatTitleAccessoryPanelNode { break case let .businessLinkSetup(link): self.link = link + case .hashTagSearch: + break } default: break diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index b527be081d6..87c8ecc9bdb 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -127,6 +127,7 @@ import ChatNavigationButton import WebsiteType import ChatQrCodeScreen import PeerInfoScreen +import MediaEditor import MediaEditorScreen import WallpaperGalleryScreen import WallpaperGridScreen @@ -137,6 +138,8 @@ import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem import AdsInfoScreen +import MessageUI +import PhoneNumberFormat public enum ChatControllerPeekActions { case standard @@ -194,8 +197,12 @@ func isTopmostChatController(_ controller: ChatControllerImpl) -> Bool { if let _ = controller.navigationController { var hasOther = false controller.window?.forEachController({ c in - if c is ChatControllerImpl && controller !== c { - hasOther = true + if c is ChatControllerImpl { + if controller !== c { + hasOther = true + } else { + hasOther = false + } } }) if hasOther { @@ -234,6 +241,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var validLayout: ContainerViewLayout? public weak var parentController: ViewController? + public weak var customNavigationController: NavigationController? let currentChatListFilter: Int32? let chatNavigationStack: [ChatNavigationStackItem] @@ -328,7 +336,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let editingMessage = ValuePromise(nil, ignoreRepeated: true) let startingBot = ValuePromise(false, ignoreRepeated: true) let unblockingPeer = ValuePromise(false, ignoreRepeated: true) - let searching = ValuePromise(false, ignoreRepeated: true) + public let searching = ValuePromise(false, ignoreRepeated: true) let searchResult = Promise<(SearchMessagesResult, SearchMessagesState, SearchMessagesLocation)?>() let loadingMessage = Promise(nil) let performingInlineSearch = ValuePromise(false, ignoreRepeated: true) @@ -603,6 +611,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var networkSpeedEventsDisposable: Disposable? + var stickerVideoExport: MediaEditorVideoExport? + + var messageComposeController: MFMessageComposeViewController? + public var alwaysShowSearchResultsAsList: Bool = false { didSet { self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList) @@ -610,6 +622,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + public var includeSavedPeersInSearchResults: Bool = false { + didSet { + self.chatDisplayNode.includeSavedPeersInSearchResults = self.includeSavedPeersInSearchResults + } + } + + public var showListEmptyResults: Bool = false { + didSet { + self.chatDisplayNode.showListEmptyResults = self.showListEmptyResults + } + } + public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, mode: ChatControllerPresentationMode = .standard(.default), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = [], customChatNavigationStack: [EnginePeer.Id]? = nil) { let _ = ChatControllerCount.modify { value in return value + 1 @@ -689,6 +713,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return interfaceState.withUpdatedEffectiveInputState(ChatTextInputState(inputText: chatInputStateStringWithAppliedEntities(link.message, entities: link.entities))) }) } + case .hashTagSearch: + break } } @@ -750,6 +776,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case let .customChatContents(customChatContents) = strongSelf.presentationInterfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + return true case let .quickReplyMessageInput(_, shortcutType): if let historyView = strongSelf.chatDisplayNode.historyNode.originalHistoryView, historyView.entries.isEmpty { @@ -1036,8 +1064,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if canSetupAutoremoveTimeout { strongSelf.presentAutoremoveSetup() } - case .paymentSent: - strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + case let .paymentSent(currency, _, _, _, _): + if currency == "XTR" { + let _ = (context.engine.payments.requestBotPaymentReceipt(messageId: message.id) + |> deliverOnMainQueue).start(next: { [weak self] receipt in + guard let self else { + return + } + self.push(self.context.sharedContext.makeStarsReceiptScreen(context: self.context, receipt: receipt)) + }) + } else { + strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } /*for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute { //strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId)) @@ -1338,7 +1376,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { transitionCompletion() }, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() + return self?.getCaptionPanelView(isFile: false) }, sendMessagesWithSignals: { [weak self] signals, _, _ in if let strongSelf = self { strongSelf.enqueueMediaMessages(signals: signals, silentPosting: false) @@ -1726,7 +1764,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.navigateToMessage(from: nil, to: .id(id, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: false) }, navigateToThreadMessage: { [weak self] peerId, threadId, messageId in if let context = self?.context, let navigationController = self?.effectiveNavigationController { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, keepStack: .always).startStandalone() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone() } }, tapMessage: nil, clickThroughMessage: { [weak self] in self?.chatDisplayNode.dismissInput() @@ -1751,12 +1789,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: text as NSString) }) } - }, sendCurrentMessage: { [weak self] silentPosting in + }, sendCurrentMessage: { [weak self] silentPosting, messageEffect in if let strongSelf = self { if let _ = strongSelf.presentationInterfaceState.interfaceState.mediaDraftState { - strongSelf.sendMediaRecording(silentPosting: silentPosting) + strongSelf.sendMediaRecording(silentPosting: silentPosting, messageEffect: messageEffect) } else { - strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting) + strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting, messageEffect: messageEffect) } } }, sendMessage: { [weak self] text in @@ -2315,27 +2353,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } }, openUrl: { [weak self] urlData in - if let strongSelf = self { - let url = urlData.url - let concealed = urlData.concealed - let message = urlData.message - let progress = urlData.progress - let forceExternal = urlData.external ?? false - - var skipConcealedAlert = false - if let author = message?.author, author.isVerified { - skipConcealedAlert = true - } - - if let message, let adAttribute = message.attributes.first(where: { $0 is AdMessageAttribute }) as? AdMessageAttribute { - strongSelf.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId) - } - - if let performOpenURL = strongSelf.performOpenURL { - performOpenURL(message, url, progress) - } else { - strongSelf.openUrl(url, concealed: concealed, forceExternal: forceExternal, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution, progress: progress) - } + guard let strongSelf = self else { + return + } + let url = urlData.url + let concealed = urlData.concealed + let message = urlData.message + let progress = urlData.progress + let forceExternal = urlData.external ?? false + + var skipConcealedAlert = false + if let author = message?.author, author.isVerified { + skipConcealedAlert = true + } + + if let message, let adAttribute = message.attributes.first(where: { $0 is AdMessageAttribute }) as? AdMessageAttribute { + strongSelf.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId) + } + + if let performOpenURL = strongSelf.performOpenURL { + performOpenURL(message, url, progress) + } else { + strongSelf.openUrl(url, concealed: concealed, forceExternal: forceExternal, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution, progress: progress) } }, shareCurrentLocation: { [weak self] in if let strongSelf = self { @@ -2704,19 +2743,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { - let peerSignal: Signal - guard let peerId = strongSelf.chatLocation.peerId else { - return - } - peerSignal = strongSelf.context.account.postbox.loadedPeerWithId(peerId) - |> map(Optional.init) - let _ = (peerSignal - |> deliverOnMainQueue).startStandalone(next: { peer in - if let strongSelf = self { - let searchController = HashtagSearchController(context: strongSelf.context, peer: peer.flatMap(EnginePeer.init), query: hashtag) - strongSelf.effectiveNavigationController?.pushViewController(searchController) - } - }) + strongSelf.openHashtag(hashtag, peerName: nil) } }), ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in @@ -2920,7 +2947,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let invoice = media as? TelegramMediaInvoice { strongSelf.chatDisplayNode.dismissInput() if let receiptMessageId = invoice.receiptMessageId { - strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + if invoice.currency == "XTR" { + let _ = (strongSelf.context.engine.payments.requestBotPaymentReceipt(messageId: receiptMessageId) + |> deliverOnMainQueue).start(next: { [weak self] receipt in + guard let strongSelf = self else { + return + } + strongSelf.push(strongSelf.context.sharedContext.makeStarsReceiptScreen(context: strongSelf.context, receipt: receipt)) + }) + } else { + strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } } else { let inputData = Promise() inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .message(message.id)) @@ -2928,22 +2965,43 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G |> `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 starsContext = strongSelf.context.starsContext { + let starsInputData = combineLatest( + inputData.get(), + starsContext.state + ) + |> 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 { - return false + let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let strongSelf = self else { + return } - - if case .info = action { - strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - return true + let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .message(messageId), inputData: starsInputData, completion: { _ in }) + 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 } - return false - }), in: .current) - }), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + 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) + }), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } } } } @@ -3271,11 +3329,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { strongSelf.context.sharedContext.applicationBindings.openAppStorePage() } - }, displayMessageTooltip: { [weak self] messageId, text, node, nodeRect in + }, displayMessageTooltip: { [weak self] messageId, text, isFactCheck, node, nodeRect in if let strongSelf = self { if let node = node { strongSelf.messageTooltipController?.dismiss() - let tooltipController = TooltipController(content: .text(text), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true) + + let padding: CGFloat + let timeout: Double + let balancedTextLayout: Bool + let alignment: TooltipController.Alignment + let innerPadding: UIEdgeInsets + if isFactCheck { + timeout = 5.0 + padding = 20.0 + balancedTextLayout = true + alignment = .natural + innerPadding = UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0) + } else { + timeout = 2.0 + padding = 8.0 + balancedTextLayout = false + alignment = .center + innerPadding = .zero + } + + let tooltipController = TooltipController(content: .text(text), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: balancedTextLayout, alignment: alignment, isBlurred: true, timeout: timeout, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: padding, innerPadding: innerPadding) strongSelf.messageTooltipController = tooltipController tooltipController.dismissed = { [weak tooltipController] _ in if let strongSelf = self, let tooltipController = tooltipController, strongSelf.messageTooltipController === tooltipController { @@ -3321,14 +3399,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } - }, scheduleCurrentMessage: { [weak self] in + }, scheduleCurrentMessage: { [weak self] params in if let strongSelf = self { strongSelf.presentScheduleTimePicker(completion: { [weak self] time in if let strongSelf = self { if let _ = strongSelf.presentationInterfaceState.interfaceState.mediaDraftState { - strongSelf.sendMediaRecording(scheduleTime: time) + strongSelf.sendMediaRecording(scheduleTime: time, messageEffect: (params?.effect).flatMap { + return ChatSendMessageEffect(id: $0.id) + }) } else { - strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: time) { [weak self] in + let silentPosting = strongSelf.presentationInterfaceState.interfaceState.silentPosting + strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting, scheduleTime: time, messageEffect: (params?.effect).flatMap { + return ChatSendMessageEffect(id: $0.id) + }) { [weak self] in if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: strongSelf.presentationInterfaceState.subject != .scheduledMessages, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } @@ -3671,6 +3754,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, delay: true) }))) } + if !isChannel { + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuSearchMessages, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + f(.dismissWithoutContent) + + guard let strongSelf = self else { + return + } + strongSelf.activateSearch(domain: .member(peer._asPeer())) + }))) + } strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() @@ -3756,7 +3851,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() + return self?.getCaptionPanelView(isFile: true) }, sendMessagesWithSignals: { [weak self] signals, _, _ in if let strongSelf = self { strongSelf.interfaceInteraction?.setupEditMessage(messageId, { _ in }) @@ -4102,12 +4197,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.present(controller, in: .window(.root)) } }) - }, activateAdAction: { [weak self] messageId in + }, activateAdAction: { [weak self] messageId, progress in guard let self, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let adAttribute = message.adAttribute else { return } self.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId) - self.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: adAttribute.url, concealed: false, external: true)) + self.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: adAttribute.url, concealed: false, external: true, progress: progress)) }, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId, maxQuantity in guard let self else { return @@ -4636,6 +4731,45 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } self.openStickerEditor() + }, openPhoneContextMenu: { [weak self] phoneData in + guard let self else { + return + } + phoneData.progress?.set(.single(true)) + + let _ = (self.context.engine.peers.resolvePeerByPhone(phone: phoneData.number) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self else { + return + } + phoneData.progress?.set(.single(false)) + + self.openPhoneContextMenu(number: phoneData.number, peer: peer, message: phoneData.message, contentNode: phoneData.contentNode, messageNode: phoneData.messageNode, frame: phoneData.messageNode.bounds, anyRecognizer: nil, location: nil) + }) + }, openAgeRestrictedMessageMedia: { [weak self] message, reveal in + guard let self else { + return + } + let controller = chatAgeRestrictionAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, completion: { [weak self] alwaysShow in + guard let self else { + return + } + if alwaysShow { + self.present(UndoOverlayController(presentationData: self.presentationData, content: .info(title: nil, text: "You can update the visibility of sensitive media in [Data and Storage > Show 18+ Content]().", timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) + } + reveal() + }) + self.present(controller, in: .window(.root)) + }, playMessageEffect: { [weak self] message in + guard let self else { + return + } + self.playMessageEffect(message: message) + }, editMessageFactCheck: { [weak self] messageId in + guard let self else { + return + } + self.openEditMessageFactCheck(messageId: messageId) }, requestMessageUpdate: { [weak self] id, scroll in if let self { self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll) @@ -6378,6 +6512,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case let .customChatContents(customChatContents) = self.subject { switch customChatContents.kind { + case .hashTagSearch: + break case let .quickReplyMessageInput(shortcut, shortcutType): switch shortcutType { case .generic: @@ -9133,7 +9269,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { + func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, parameters: ChatSendMessageActionSheetController.SendParameters? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!) |> deliverOnMainQueue).startStrict(next: { [weak self] items in if let strongSelf = self { @@ -9187,6 +9323,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G usedCorrelationId = correlationId completionImpl = nil } + + if let parameters { + if let effect = parameters.effect { + message = message.withUpdatedAttributes { attributes in + var attributes = attributes + attributes.append(EffectMessageAttribute(id: effect.id)) + return attributes + } + } + if parameters.textIsAboveMedia { + message = message.withUpdatedAttributes { attributes in + var attributes = attributes + attributes.append(InvertMediaMessageAttribute()) + return attributes + } + } + } + mappedMessages.append(message) } @@ -9256,123 +9410,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } })) } - - func displayPasteMenu(_ subjects: [MediaPickerScreen.Subject.Media]) { - let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in - let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) - return entry ?? GeneratedMediaStoreSettings.defaultSettings - } - |> deliverOnMainQueue).startStandalone(next: { [weak self] settings in - if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { - strongSelf.chatDisplayNode.dismissInput() - let controller = mediaPasteboardScreen( - context: strongSelf.context, - updatedPresentationData: strongSelf.updatedPresentationData, - peer: EnginePeer(peer), - subjects: subjects, - presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in - if let strongSelf = self { - strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) - }) - } - }, - getSourceRect: nil - ) - controller.navigationPresentation = .flatModal - strongSelf.push(controller) - } - }) - } - - func enqueueGifData(_ data: Data) { - self.enqueueMediaMessageDisposable.set((legacyEnqueueGifMessage(account: self.context.account, data: data) |> deliverOnMainQueue).startStrict(next: { [weak self] message in - if let strongSelf = self { - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - strongSelf.sendMessages([message].map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }) - } - })) - } - - func enqueueVideoData(_ data: Data) { - self.enqueueMediaMessageDisposable.set((legacyEnqueueGifMessage(account: self.context.account, data: data) |> deliverOnMainQueue).startStrict(next: { [weak self] message in - if let strongSelf = self { - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - strongSelf.sendMessages([message].map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }) - } - })) - } - - func enqueueStickerImage(_ image: UIImage, isMemoji: Bool) { - let size = image.size.aspectFitted(CGSize(width: 512.0, height: 512.0)) - self.enqueueMediaMessageDisposable.set((convertToWebP(image: image, targetSize: size, targetBoundingSize: size, quality: 0.9) |> deliverOnMainQueue).startStrict(next: { [weak self] data in - if let strongSelf = self, !data.isEmpty { - let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) - - var fileAttributes: [TelegramMediaFileAttribute] = [] - fileAttributes.append(.FileName(fileName: "sticker.webp")) - fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) - fileAttributes.append(.ImageSize(size: PixelDimensions(size))) - - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes) - let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - strongSelf.sendMessages([message].map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }) - } - })) - } - - func enqueueStickerFile(_ file: TelegramMediaFile) { - let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - - let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject - self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - self.sendMessages([message].map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }) - - Queue.mainQueue().after(3.0) { - if let message = self.chatDisplayNode.historyNode.lastVisbleMesssage(), let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, file.isSticker { - self.context.engine.stickers.addRecentlyUsedSticker(fileReference: .message(message: MessageReference(message), media: file)) - } - } - } - + func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true) { if !canSendMessagesToChat(self.presentationInterfaceState) { return @@ -9471,14 +9509,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G func updateDownButtonVisibility() { let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil || self.presentationInterfaceState.interfaceState.mediaDraftState != nil - if let search = self.presentationInterfaceState.search, let results = search.resultsState, results.messageIndices.count != 0 { + var ignoreSearchState = false + if case let .customChatContents(contents) = self.subject, case .hashTagSearch = contents.kind { + ignoreSearchState = true + } + + if !ignoreSearchState, let search = self.presentationInterfaceState.search, let results = search.resultsState, results.messageIndices.count != 0 { var resultIndex: Int? if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) { resultIndex = index } else { resultIndex = nil } - if let resultIndex { self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState( up: ChatHistoryNavigationButtons.ButtonState(isEnabled: resultIndex != 0), @@ -9507,7 +9549,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil let displayMentionButton = (self.chatDisplayNode.navigateButtons.mentionCount != 0) let displayReactionButton = (self.chatDisplayNode.navigateButtons.reactionsCount != 0) - self.chatDisplayNode.ngAiOverlayNode.isHidden = self.shouldDisplayDownButton || recordingMediaMessage || displayMentionButton || displayReactionButton + self.chatDisplayNode.nicegramOverlayNode.isHidden = self.shouldDisplayDownButton || recordingMediaMessage || displayMentionButton || displayReactionButton } // @@ -9630,9 +9672,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func openHashtag(_ hashtag: String, peerName: String?) { - guard let peerId = self.chatLocation.peerId else { - return - } let _ = self.presentVoiceMessageDiscardAlert(action: { if self.resolvePeerByNameDisposable == nil { self.resolvePeerByNameDisposable = MetaDisposable() @@ -9653,14 +9692,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return .single(nil) } } - } else { + } else if let peerId = self.chatLocation.peerId { resolveSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) + } else { + resolveSignal = .single(nil) } var cancelImpl: (() -> Void)? let presentationData = self.presentationData let progressSignal = Signal { [weak self] subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { cancelImpl?() })) self?.present(controller, in: .window(.root)) @@ -9671,7 +9712,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) + |> delay(0.25, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() resolveSignal = resolveSignal @@ -9685,9 +9726,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.resolvePeerByNameDisposable?.set((resolveSignal |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self, !hashtag.isEmpty { - let searchController = HashtagSearchController(context: strongSelf.context, peer: peer.flatMap(EnginePeer.init), query: hashtag) - strongSelf.effectiveNavigationController?.pushViewController(searchController) + if let self, !hashtag.isEmpty { + var publicPosts = false + if let peer = self.presentationInterfaceState.renderedPeer, let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info, !(channel.addressName ?? "").isEmpty { + publicPosts = true + } else if case let .customChatContents(contents) = self.subject, case let .hashTagSearch(publicPostsValue) = contents.kind { + publicPosts = publicPostsValue + } + let searchController = HashtagSearchController(context: self.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, publicPosts: publicPosts) + self.effectiveNavigationController?.pushViewController(searchController) } })) }) @@ -9740,7 +9787,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G func addPeerContact() { if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramUser, let peerStatusSettings = self.presentationInterfaceState.contactStatus?.peerStatusSettings, let contactData = DeviceContactExtendedData(peer: EnginePeer(peer)) { - self.present(context.sharedContext.makeDeviceContactInfoController(context: context, subject: .create(peer: peer, contactData: contactData, isSharing: true, shareViaException: peerStatusSettings.contains(.addExceptionWhenAddingContact), completion: { [weak self] peer, stableId, contactData in + self.present(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: self.context), environment: ShareControllerAppEnvironment(sharedContext: self.context.sharedContext), subject: .create(peer: peer, contactData: contactData, isSharing: true, shareViaException: peerStatusSettings.contains(.addExceptionWhenAddingContact), completion: { [weak self] peer, stableId, contactData in guard let strongSelf = self else { return } @@ -9800,11 +9847,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?, progress: Promise? = nil, forceExternal: Bool = false, concealed: Bool = false, commit: @escaping () -> Void = {}) { - guard let peerId = self.chatLocation.peerId else { - return - } + let urlContext: OpenURLContext + let message = sourceMessageId.flatMap { self.chatDisplayNode.historyNode.messageInCurrentHistoryView($0) } - self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(peerId: peerId, message: message, updatedPresentationData: self.updatedPresentationData), navigationController: self.effectiveNavigationController, forceExternal: forceExternal, openPeer: { [weak self] peerId, navigation in + if let peerId = self.chatLocation.peerId { + urlContext = .chat(peerId: peerId, message: message, updatedPresentationData: self.updatedPresentationData) + } else { + urlContext = .generic + } + self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: urlContext, navigationController: self.effectiveNavigationController, forceExternal: forceExternal, openPeer: { [weak self] peerId, navigation in guard let strongSelf = self else { return } @@ -9880,7 +9931,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, sendFile: nil, sendSticker: { [weak self] f, sourceView, sourceRect in return self?.interfaceInteraction?.sendSticker(f, true, sourceView, sourceRect, nil, []) ?? false - }, requestMessageActionUrlAuth: { [weak self] subject in + }, sendEmoji: { [weak self] text, attribute in + guard let self, canSendMessagesToChat(self.presentationInterfaceState) else { + return + } + self.controllerInteraction?.sendEmoji(text, attribute, false) + }, + requestMessageActionUrlAuth: { [weak self] subject in if case let .url(url) = subject { self?.controllerInteraction?.requestMessageActionUrlAuth(url, subject) } @@ -9929,7 +9986,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let disposable = openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in self?.present(c, in: .window(.root)) }, openResolved: { [weak self] resolved in - self?.openResolved(result: resolved, sourceMessageId: message?.id, forceExternal: forceExternal, concealed: concealed, commit: commit) + self?.openResolved(result: resolved, sourceMessageId: message?.id, progress: progress, forceExternal: forceExternal, concealed: concealed, commit: commit) }, progress: progress) self.navigationActionDisposable.set(disposable) }, performAction: true) @@ -10786,6 +10843,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else if case let .overlay(navigationController) = self.presentationInterfaceState.mode { return navigationController } else { + if let navigationController = self.customNavigationController { + return navigationController + } return nil } } diff --git a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift index 0f361cc0ec6..642feb20c27 100644 --- a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift +++ b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift @@ -488,7 +488,7 @@ extension ChatControllerImpl { f(.dismissWithoutContent) commit() } else { - c.dismiss(completion: { + c?.dismiss(completion: { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { commit() }) @@ -541,7 +541,7 @@ extension ChatControllerImpl { f(.dismissWithoutContent) commit() } else { - c.dismiss(completion: { + c?.dismiss(completion: { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { commit() }) diff --git a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift index 2ae15a3198c..157cfe8420f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift +++ b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift @@ -141,7 +141,7 @@ extension ChatControllerImpl { }), in: .current) } } - controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions in + controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions, _ in guard let strongSelf = self, let strongController = controller else { return } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 99027c8412f..9dfdf975190 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -9,6 +9,11 @@ import UndoUI // MARK: Nicegram ChatBanner import FeatChatBanner // +// MARK: Nicegram TgChatButton +import FeatTgChatButton +import NGUtils +import NicegramWallet +// import Foundation import UIKit import AsyncDisplayKit @@ -155,6 +160,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode? var alwaysShowSearchResultsAsList: Bool = false + var includeSavedPeersInSearchResults: Bool = false + var showListEmptyResults: Bool = false + private var skippedShowSearchResultsAsListAnimationOnce: Bool = false var inlineSearchResults: ComponentView? private var inlineSearchResultsReadyDisposable: Disposable? @@ -218,37 +226,20 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { private var inputMediaNodeDataDisposable: Disposable? private var inputMediaNodeStateContext = ChatEntityKeyboardInputNode.StateContext() + // MARK: Nicegram + private let nicegramOverlayView = TgChatOverlayView() + lazy var nicegramOverlayNode = ASDisplayNode { [nicegramOverlayView] in + nicegramOverlayView + } + // + // MARK: Nicegram AiChat - lazy var ngAiOverlayNode: ASDisplayNode = { - if #available(iOS 13.0, *) { - let handlers = TgChatAiOverlayHandlers( - useAnswer: { [weak self] text, image in - guard let self else { return } - if let image { - self.paste(.images([image])) - } else if let text { - let newTextInputState = ChatTextInputState(inputText: NSAttributedString(string: text)) - self.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), true) { - $0.withUpdatedEffectiveInputState(newTextInputState) - } - } - } - ) - - let viewModel = TgChatAiOverlayViewModelImpl(handlers: handlers) - - let tgTheme = self.chatPresentationInterfaceState.theme - let view = TgChatAiOverlayView( - viewModel: viewModel, - theme: TgChatAiOverlayTheme( - buttonBackgroundColor: tgTheme.chat.inputPanel.panelBackgroundColor, - buttonForegroundColor: tgTheme.chat.historyNavigation.foregroundColor - ) - ) - return ASDisplayNode { view } - } else { - return ASDisplayNode() - } + private lazy var tgChatAiViewModel = { + TgChatAiViewModel( + useAnswer: { [weak self] text, image in + self?.setInput(text: text, image: image) + } + ) }() // @@ -517,7 +508,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { var messageMedia = message.media var hasDice = false - if hideNames { + if hideNames || options.hideCaptions { for media in message.media { if options.hideCaptions { if media is TelegramMediaImage || media is TelegramMediaFile { @@ -673,10 +664,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } var historyNodeRotated = true + var isChatPreview = false switch chatPresentationInterfaceState.mode { case let .standard(standardMode): if case .embedded(true) = standardMode { historyNodeRotated = false + } else if case .previewing = standardMode { + isChatPreview = true } default: break @@ -685,7 +679,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.controllerInteraction.chatIsRotated = historyNodeRotated var getMessageTransitionNode: (() -> ChatMessageTransitionNodeImpl?)? - self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: controller?.updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: nil, source: source, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), rotated: historyNodeRotated, messageTransitionNode: { + self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: controller?.updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: nil, source: source, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), rotated: historyNodeRotated, isChatPreview: isChatPreview, messageTransitionNode: { return getMessageTransitionNode?() }) @@ -876,12 +870,33 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.addSubnode(self.messageTransitionNode) self.contentContainerNode.addSubnode(self.navigateButtons) - // MARK: Nicegram AiChat - if AiChatUITgHelper.shouldShowAiBotInTgChat() { - self.addSubnode(self.ngAiOverlayNode) + + // MARK: Nicegram + if #available(iOS 15.0, *) { + nicegramOverlayView.openAiChat = { [weak self] in + guard let self else { return } + Task { @MainActor in + AiChatUITgHelper.tryRouteToAiChatWidget( + useAnswer: { [weak self] text, image in + self?.setInput(text: text, image: image) + } + ) + } + } + nicegramOverlayView.openWallet = { [weak self] in + self?.openNicegramWallet() + } + + if NGSettings.showNicegramButtonInChat { + self.contentContainerNode.addSubnode(self.nicegramOverlayNode) + } } // + // MARK: Nicegram AiChat + tgChatAiViewModel.initialize() + // + // MARK: Nicegram ChatBanner if #available(iOS 15.0, *), let controller { self.addSubnode(self.ngBannerNode) @@ -925,7 +940,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.textInputPanelNode?.sendMessage = { [weak self] in if let strongSelf = self { if case .scheduledMessages = strongSelf.chatPresentationInterfaceState.subject, strongSelf.chatPresentationInterfaceState.editMessageState == nil { - strongSelf.controllerInteraction.scheduleCurrentMessage() + strongSelf.controllerInteraction.scheduleCurrentMessage(nil) } else { strongSelf.sendCurrentMessage() } @@ -984,6 +999,48 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.inlineSearchResultsReadyDisposable?.dispose() self.loadMoreSearchResultsDisposable?.dispose() } + + // MARK: Nicegram + @available(iOS 15.0, *) + @objc private func openNicegramWallet() { + Task { + guard let peerId = chatLocation.peerId else { + return + } + guard let contact = await WalletTgUtils.peerToWalletContact( + id: peerId, + context: context + ) else { + return + } + + let sendToChat: ((String?) -> Void)? + if (self.inputPanelNode as? ChatTextInputPanelNode) != nil { + sendToChat = { [weak self] text in + self?.setInput(text: text, image: nil) + } + } else { + sendToChat = nil + } + + await WalletEntryPoints.openInChatWidget( + contact: contact, + sendToChat: sendToChat + ) + } + } + + private func setInput(text: String?, image: UIImage?) { + if let image { + paste(.images([image])) + } else if let text { + let newTextInputState = ChatTextInputState(inputText: NSAttributedString(string: text)) + self.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), true) { + $0.withUpdatedEffectiveInputState(newTextInputState) + } + } + } + // override func didLoad() { super.didLoad() @@ -1124,7 +1181,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let emptyNode = ChatEmptyNode(context: self.context, interaction: self.interfaceInteraction) emptyNode.isHidden = self.restrictedNode != nil self.emptyNode = emptyNode - self.historyNodeContainer.supernode?.insertSubnode(emptyNode, aboveSubnode: self.historyNodeContainer) + + if let inlineSearchResultsView = self.inlineSearchResults?.view { + self.contentContainerNode.view.insertSubview(emptyNode.view, belowSubview: inlineSearchResultsView) + } else { + self.contentContainerNode.insertSubnode(emptyNode, aboveSubnode: self.historyNodeContainer) + } + if let (size, insets) = self.validEmptyNodeLayout { let mappedType: ChatEmptyNode.Subject.EmptyType switch emptyType { @@ -2246,13 +2309,14 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { transition.updateFrame(node: self.inputPanelBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: CGSize(width: apparentInputBackgroundFrame.size.width, height: UIScreenPixel))) transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame) self.navigateButtons.update(rect: apparentNavigateButtonsFrame, within: layout.size, transition: transition) - // MARK: Nicegram AiChat + + // MARK: Nicegram transition.updateFrame( - node: self.ngAiOverlayNode, + node: self.nicegramOverlayNode, frame: CGRect( origin: .zero, size: CGSize( - width: layout.size.width, + width: apparentNavigateButtonsFrame.maxX, height: apparentNavigateButtonsFrame.maxY ) ) @@ -2663,7 +2727,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - if let peerId = self.chatPresentationInterfaceState.chatLocation.peerId, displayInlineSearch { + if displayInlineSearch { + let peerId = self.chatPresentationInterfaceState.chatLocation.peerId + let inlineSearchResults: ComponentView var inlineSearchResultsTransition = Transition(transition) if let current = self.inlineSearchResults { @@ -2676,12 +2742,12 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let mappedContents: ChatInlineSearchResultsListComponent.Contents if let _ = self.chatPresentationInterfaceState.search?.resultsState { - mappedContents = .search(query: self.chatPresentationInterfaceState.search?.query ?? "", includeSavedPeers: self.alwaysShowSearchResultsAsList) + mappedContents = .search(query: self.chatPresentationInterfaceState.search?.query ?? "", includeSavedPeers: self.alwaysShowSearchResultsAsList && self.includeSavedPeersInSearchResults) } else if let historyFilter = self.chatPresentationInterfaceState.historyFilter { mappedContents = .tag(historyFilter.customTag) } else if let search = self.chatPresentationInterfaceState.search, self.alwaysShowSearchResultsAsList { if !search.query.isEmpty { - mappedContents = .search(query: search.query, includeSavedPeers: self.alwaysShowSearchResultsAsList) + mappedContents = .search(query: search.query, includeSavedPeers: self.alwaysShowSearchResultsAsList && self.includeSavedPeersInSearchResults) } else { mappedContents = .empty } @@ -2709,12 +2775,24 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { peerId: peerId, contents: mappedContents, insets: childContentInsets, + inputHeight: layout.inputHeight ?? 0.0, + showEmptyResults: self.showListEmptyResults, messageSelected: { [weak self] message in guard let self else { return } - if let historyFilter = self.chatPresentationInterfaceState.historyFilter, let reaction = ReactionsMessageAttribute.reactionFromMessageTag(tag: historyFilter.customTag), let peerId = self.chatLocation.peerId, historyFilter.isActive { + if case let .customChatContents(contents) = self.chatPresentationInterfaceState.subject, case .hashTagSearch = contents.kind { + self.controller?.navigateToMessage( + from: message.id, + to: .index(message.index), + scrollPosition: .center(.bottom), + rememberInStack: false, + forceInCurrentChat: false, + forceNew: true, + animated: true + ) + } else if let historyFilter = self.chatPresentationInterfaceState.historyFilter, let reaction = ReactionsMessageAttribute.reactionFromMessageTag(tag: historyFilter.customTag), let peerId = self.chatLocation.peerId, historyFilter.isActive { let _ = (self.context.engine.messages.searchMessages( location: .peer(peerId: peerId, fromId: nil, tags: nil, reactions: [reaction], threadId: self.chatLocation.threadId, minDate: nil, maxDate: nil), query: "", @@ -2896,46 +2974,51 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { guard let self, let controller = self.controller else { return } - guard let currentSearchState = controller.searchState, let currentResultsState = controller.presentationInterfaceState.search?.resultsState else { - return - } - self.loadMoreSearchResultsDisposable?.dispose() - self.loadMoreSearchResultsDisposable = (self.context.engine.messages.searchMessages(location: currentSearchState.location, query: currentSearchState.query, state: currentResultsState.state) - |> deliverOnMainQueue).startStrict(next: { [weak self] results, updatedState in - guard let self, let controller = self.controller else { + if case let .customChatContents(contents) = self.chatPresentationInterfaceState.subject { + contents.loadMore() + } else { + guard let currentSearchState = controller.searchState, let currentResultsState = controller.presentationInterfaceState.search?.resultsState else { return } - controller.searchResult.set(.single((results, updatedState, currentSearchState.location))) - - var navigateIndex: MessageIndex? - controller.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in - if let data = current.search { - let messageIndices = results.messages.map({ $0.index }).sorted() - var currentIndex = messageIndices.last - if let previousResultId = data.resultsState?.currentId { - for index in messageIndices { - if index.id >= previousResultId { - currentIndex = index - break + self.loadMoreSearchResultsDisposable?.dispose() + self.loadMoreSearchResultsDisposable = (self.context.engine.messages.searchMessages(location: currentSearchState.location, query: currentSearchState.query, state: currentResultsState.state) + |> deliverOnMainQueue).startStrict(next: { [weak self] results, updatedState in + guard let self, let controller = self.controller else { + return + } + + controller.searchResult.set(.single((results, updatedState, currentSearchState.location))) + + var navigateIndex: MessageIndex? + controller.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in + if let data = current.search { + let messageIndices = results.messages.map({ $0.index }).sorted() + var currentIndex = messageIndices.last + if let previousResultId = data.resultsState?.currentId { + for index in messageIndices { + if index.id >= previousResultId { + currentIndex = index + break + } } } + navigateIndex = currentIndex + return current.updatedSearch(data.withUpdatedResultsState(ChatSearchResultsState(messageIndices: messageIndices, currentId: currentIndex?.id, state: updatedState, totalCount: results.totalCount, completed: results.completed))) + } else { + return current + } + }) + if let navigateIndex = navigateIndex { + switch controller.chatLocation { + case .peer, .replyThread, .customChatContents: + controller.navigateToMessage(from: nil, to: .index(navigateIndex), forceInCurrentChat: true) } - navigateIndex = currentIndex - return current.updatedSearch(data.withUpdatedResultsState(ChatSearchResultsState(messageIndices: messageIndices, currentId: currentIndex?.id, state: updatedState, totalCount: results.totalCount, completed: results.completed))) - } else { - return current } + controller.updateItemNodesSearchTextHighlightStates() }) - if let navigateIndex = navigateIndex { - switch controller.chatLocation { - case .peer, .replyThread, .customChatContents: - controller.navigateToMessage(from: nil, to: .index(navigateIndex), forceInCurrentChat: true) - } - } - controller.updateItemNodesSearchTextHighlightStates() - }) + } } )), environment: {}, @@ -2950,7 +3033,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } self.skippedShowSearchResultsAsListAnimationOnce = true inlineSearchResultsView.layer.allowsGroupOpacity = true - self.contentContainerNode.view.insertSubview(inlineSearchResultsView, aboveSubview: self.historyNodeContainer.view) + if let emptyNode = self.emptyNode { + self.contentContainerNode.view.insertSubview(inlineSearchResultsView, aboveSubview: emptyNode.view) + } else { + self.contentContainerNode.view.insertSubview(inlineSearchResultsView, aboveSubview: self.historyNodeContainer.view) + } } inlineSearchResultsTransition.setFrame(view: inlineSearchResultsView, frame: CGRect(origin: CGPoint(), size: layout.size)) @@ -3243,7 +3330,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let restrictionText = restrictionText { if self.restrictedNode == nil { - let restrictedNode = ChatRecentActionsEmptyNode(theme: chatPresentationInterfaceState.theme, chatWallpaper: chatPresentationInterfaceState.chatWallpaper, chatBubbleCorners: chatPresentationInterfaceState.bubbleCorners) + let restrictedNode = ChatRecentActionsEmptyNode(theme: chatPresentationInterfaceState.theme, chatWallpaper: chatPresentationInterfaceState.chatWallpaper, chatBubbleCorners: chatPresentationInterfaceState.bubbleCorners, hasIcon: false) self.historyNodeContainer.supernode?.insertSubnode(restrictedNode, aboveSubnode: self.historyNodeContainer) self.restrictedNode = restrictedNode } @@ -3315,6 +3402,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.controller?.customNavigationBarContentNode = self.searchNavigationNode } self.searchNavigationNode?.update(presentationInterfaceState: self.chatPresentationInterfaceState) + + if case let .customChatContents(contents) = self.chatPresentationInterfaceState.subject, case .hashTagSearch = contents.kind { + activate = false + } if activate { self.searchNavigationNode?.activate() } @@ -3879,7 +3970,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } } else { - self.historyNode.scrollScreenToTop() + if let inlineSearchResultsView = self.inlineSearchResults?.view as? ChatInlineSearchResultsListComponent.View { + inlineSearchResultsView.scrollToTop() + } else { + self.historyNode.scrollScreenToTop() + } } } @@ -4038,7 +4133,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - func sendCurrentMessage(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, completion: @escaping () -> Void = {}) { + func sendCurrentMessage(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageEffect? = nil, completion: @escaping () -> Void = {}) { if let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { self.historyNode.justSentTextMessage = true @@ -4068,7 +4163,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { var messages: [EnqueueMessage] = [] - let effectiveInputText = effectivePresentationInterfaceState.interfaceState.composeInputState.inputText + let effectiveInputText = expandedInputStateAttributedString(effectivePresentationInterfaceState.interfaceState.composeInputState.inputText) let peerSpecificEmojiPack = (self.controller?.peerView?.cachedData as? CachedChannelData)?.emojiPack @@ -4241,6 +4336,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { var postEmptyMessages = false if case let .customChatContents(customChatContents) = self.chatPresentationInterfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: break case .businessLinkSetup: @@ -4248,6 +4345,14 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } + if !messages.isEmpty, let messageEffect { + messages[0] = messages[0].withUpdatedAttributes { attributes in + var attributes = attributes + attributes.append(EffectMessageAttribute(id: messageEffect.id)) + return attributes + } + } + if !messages.isEmpty || postEmptyMessages || self.chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil { if let forwardMessageIds = self.chatPresentationInterfaceState.interfaceState.forwardMessageIds { // MARK: Nicegram ForwardAsCopy diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 8f6a31dd57f..a056f61a5c2 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -32,6 +32,7 @@ import TelegramCallsUI import AutomaticBusinessMessageSetupScreen import MediaEditorScreen import CameraScreen +import ShareController extension ChatControllerImpl { enum AttachMenuSubject { @@ -308,11 +309,11 @@ extension ChatControllerImpl { completion(controller, mediaPickerContext) }, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in attachmentController?.mediaPickerContext = mediaPickerContext - }, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + }, completion: { [weak self] signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in if !inputText.string.isEmpty { self?.clearInputText() } - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) }) case .file: strongSelf.controllerNavigationDisposable.set(nil) @@ -404,7 +405,7 @@ extension ChatControllerImpl { completion(contactsController, contactsController.mediaPickerContext) strongSelf.controllerNavigationDisposable.set((contactsController.result |> deliverOnMainQueue).startStrict(next: { [weak self] peers in - if let strongSelf = self, let (peers, _, silent, scheduleTime, text) = peers { + if let strongSelf = self, let (peers, _, silent, scheduleTime, text, parameters) = peers { var textEnqueueMessage: EnqueueMessage? if let text = text, text.length > 0 { var attributes: [MessageAttribute] = [] @@ -455,6 +456,17 @@ extension ChatControllerImpl { enqueueMessages.append(message) } } + if !enqueueMessages.isEmpty { + enqueueMessages[enqueueMessages.count - 1] = enqueueMessages[enqueueMessages.count - 1].withUpdatedAttributes { attributes in + var attributes = attributes + if let parameters { + if let effect = parameters.effect { + attributes.append(EffectMessageAttribute(id: effect.id)) + } + } + return attributes + } + } strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) } else if let peer = peers.first { let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> @@ -470,14 +482,14 @@ extension ChatControllerImpl { |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in var stableId: String? let queryPhoneNumber = formatPhoneNumber(context: context, number: phoneNumber) - outer: for (id, data) in basicData { - for phoneNumber in data.phoneNumbers { - if formatPhoneNumber(context: context, number: phoneNumber.value) == queryPhoneNumber { - stableId = id - break outer + outer: for (id, data) in basicData { + for phoneNumber in data.phoneNumbers { + if formatPhoneNumber(context: context, number: phoneNumber.value) == queryPhoneNumber { + stableId = id + break outer + } } } - } if let stableId = stableId { return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil)) @@ -497,7 +509,7 @@ extension ChatControllerImpl { } } strongSelf.controllerNavigationDisposable.set((dataSignal - |> deliverOnMainQueue).startStrict(next: { peerAndContactData in + |> deliverOnMainQueue).startStrict(next: { peerAndContactData in if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 { if contactData.isPrimitive { let phone = contactData.basicData.phoneNumbers[0].value @@ -517,10 +529,16 @@ extension ChatControllerImpl { if let textEnqueueMessage = textEnqueueMessage { enqueueMessages.append(textEnqueueMessage) } - enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + var attributes: [MessageAttribute] = [] + if let parameters { + if let effect = parameters.effect { + attributes.append(EffectMessageAttribute(id: effect.id)) + } + } + enqueueMessages.append(.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) } else { - let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in + let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { return } @@ -878,7 +896,7 @@ extension ChatControllerImpl { }) } }, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() + return self?.getCaptionPanelView(isFile: false) }) } }, openFileGallery: { @@ -965,7 +983,7 @@ extension ChatControllerImpl { }) } }, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() + return self?.getCaptionPanelView(isFile: false) }, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }) @@ -1136,7 +1154,7 @@ extension ChatControllerImpl { self.present(actionSheet, in: .window(.root)) } - func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { + func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { var isScheduledMessages = false if case .scheduledMessages = self.presentationInterfaceState.subject { isScheduledMessages = true @@ -1208,10 +1226,10 @@ extension ChatControllerImpl { } } controller.getCaptionPanelView = { [weak self] in - return self?.getCaptionPanelView() + return self?.getCaptionPanelView(isFile: false) } - controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in - completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion) + controller.legacyCompletion = { signals, silently, scheduleTime, parameters, getAnimatedTransitionSource, sendCompletion in + completion(signals, silently, scheduleTime, parameters, getAnimatedTransitionSource, sendCompletion) } present(controller, mediaPickerContext) } @@ -1274,7 +1292,7 @@ extension ChatControllerImpl { }) })) controller.getCaptionPanelView = { [weak self] in - return self?.getCaptionPanelView() + return self?.getCaptionPanelView(isFile: fileMode) } strongSelf.effectiveNavigationController?.pushViewController(controller) } @@ -1309,7 +1327,7 @@ extension ChatControllerImpl { }) } }, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() + return self?.getCaptionPanelView(isFile: fileMode) }) controller.descriptionGenerator = legacyAssetPickerItemGenerator() controller.completionBlock = { [weak legacyController] signals, silentPosting, scheduleTime in @@ -1437,7 +1455,7 @@ extension ChatControllerImpl { return true } controller.getCaptionPanelView = { [weak strongSelf] in - return strongSelf?.getCaptionPanelView() + return strongSelf?.getCaptionPanelView(isFile: false) } present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } @@ -1491,7 +1509,7 @@ extension ChatControllerImpl { self.effectiveNavigationController?.pushViewController(contactsController) self.controllerNavigationDisposable.set((contactsController.result |> deliverOnMainQueue).startStrict(next: { [weak self] peers in - if let strongSelf = self, let (peers, _, _, _, _) = peers { + if let strongSelf = self, let (peers, _, _, _, _, _) = peers { if peers.count > 1 { var enqueueMessages: [EnqueueMessage] = [] for peer in peers { @@ -1590,7 +1608,7 @@ extension ChatControllerImpl { let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) strongSelf.sendMessages([message]) } else { - let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in + let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { return } @@ -1620,12 +1638,12 @@ extension ChatControllerImpl { })) } - func getCaptionPanelView() -> TGCaptionPanelView? { + func getCaptionPanelView(isFile: Bool) -> TGCaptionPanelView? { var isScheduledMessages = false if case .scheduledMessages = self.presentationInterfaceState.subject { isScheduledMessages = true } - return self.context.sharedContext.makeGalleryCaptionPanelView(context: self.context, chatLocation: self.presentationInterfaceState.chatLocation, isScheduledMessages: isScheduledMessages, customEmojiAvailable: self.presentationInterfaceState.customEmojiAvailable, present: { [weak self] c in + return self.context.sharedContext.makeGalleryCaptionPanelView(context: self.context, chatLocation: self.presentationInterfaceState.chatLocation, isScheduledMessages: isScheduledMessages, isFile: isFile, customEmojiAvailable: self.presentationInterfaceState.customEmojiAvailable, present: { [weak self] c in self?.present(c, in: .window(.root)) }, presentInGlobalOverlay: { [weak self] c in guard let self else { @@ -1719,7 +1737,7 @@ extension ChatControllerImpl { }) } }, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() + return self?.getCaptionPanelView(isFile: false) }, dismissedWithResult: { [weak self] in self?.attachmentController?.dismiss(animated: false, completion: nil) }, finishedTransitionIn: { [weak self] in diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift b/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift index 62ae3df62d2..c0e39f7d9f0 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift @@ -176,7 +176,8 @@ extension ChatControllerImpl { }), in: .current) } - self.push(calendarScreen) + self.effectiveNavigationController?.pushViewController(calendarScreen) + dismissCalendarScreen = { [weak calendarScreen] in calendarScreen?.dismiss(completion: nil) } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index ce7728a103e..e36db96f85e 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -85,7 +85,7 @@ extension ChatControllerImpl { a(.default) return } - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let self else { return } diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index 29924427880..623ebf78c81 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -161,8 +161,10 @@ func chatHistoryEntriesForView( } } - if skipViewOnceMedia, message.minAutoremoveOrClearTimeout != nil { - continue loop + if skipViewOnceMedia, let minAutoremoveOrClearTimeout = message.minAutoremoveOrClearTimeout { + if minAutoremoveOrClearTimeout <= 60 { + continue loop + } } var contentTypeHint: ChatMessageEntryContentType = .generic diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 2f541681be7..7d611ad9016 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -355,6 +355,7 @@ private func extractAssociatedData( currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?, + availableMessageEffects: AvailableMessageEffects?, savedMessageTags: SavedMessageTags?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, @@ -423,7 +424,7 @@ private func extractAssociatedData( automaticDownloadPeerId = message.peerId } - return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline) + return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline) } private extension ChatHistoryLocationInput { @@ -472,7 +473,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto var showNgBanner = false // - private let context: AccountContext + public let context: AccountContext private let chatLocation: ChatLocation private let chatLocationContextHolder: Atomic private let source: ChatHistoryListSource @@ -581,6 +582,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto private let extendedMediaProcessingManager = ChatMessageVisibleThrottledProcessingManager(interval: 5.0) private let translationProcessingManager = ChatMessageThrottledProcessingManager(submitInterval: 1.0) private let refreshStoriesProcessingManager = ChatMessageThrottledProcessingManager() + private let factCheckProcessingManager = ChatMessageThrottledProcessingManager(submitInterval: 1.0) let prefetchManager: InChatPrefetchManager private var currentEarlierPrefetchMessages: [(Message, Media)] = [] @@ -743,7 +745,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto return self._isReady.get() } - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tag: HistoryViewInputTag?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles, rotated: Bool = false, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tag: HistoryViewInputTag?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles, rotated: Bool = false, isChatPreview: Bool, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) { // MARK: Nicegram self.wantTrButton = usetrButton() // @@ -780,13 +782,15 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto self.prefetchManager = InChatPrefetchManager(context: context) var displayAdPeer: PeerId? - switch subject { - case .none, .message: - if case let .peer(peerId) = chatLocation { - displayAdPeer = peerId + if !isChatPreview { + switch subject { + case .none, .message: + if case let .peer(peerId) = chatLocation { + displayAdPeer = peerId + } + default: + break } - default: - break } var adMessages: Signal<(interPostInterval: Int32?, messages: [Message]), NoError> if case .bubbles = mode, let peerId = displayAdPeer { @@ -849,6 +853,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto let _ = translateMessageIds(context: context, messageIds: Array(messageIds.map(\.messageId)), toLang: toLang).startStandalone() } } + self.factCheckProcessingManager.process = { [weak self, weak context] messageIds in + if let context = context, let toLang = self?.toLang { + let _ = translateMessageIds(context: context, messageIds: Array(messageIds.map(\.messageId)), toLang: toLang).startStandalone() + } + } self.messageMentionProcessingManager.process = { [weak self, weak context] messageIds in if let strongSelf = self { @@ -1489,6 +1498,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } let availableReactions: Signal = (context as! AccountContextImpl).availableReactions + let availableMessageEffects: Signal = (context as! AccountContextImpl).availableMessageEffects + let savedMessageTags: Signal if chatLocation.peerId == self.context.account.peerId { savedMessageTags = context.engine.stickers.savedMessageTagData() @@ -1600,6 +1611,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, + availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, @@ -1612,7 +1624,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto audioTranscriptionTrial, chatThemes, deviceContactsNumbers - ).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers in + ).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers in let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId, chatHasBots, allAdMessages) = promises func applyHole() { @@ -1832,7 +1844,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto translateToLanguage = languageCode } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated) var includeEmbeddedSavedChatInfo = false if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId, !rotated { @@ -2141,6 +2153,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto if let strongSelf = self { if strongSelf.canReadHistoryValue != value { strongSelf.canReadHistoryValue = value + strongSelf.controllerInteraction.canReadHistory = value strongSelf.updateReadHistoryActions() if strongSelf.canReadHistoryValue && !strongSelf.suspendReadingReactions && !strongSelf.messageIdsScheduledForMarkAsSeen.isEmpty { @@ -2209,6 +2222,12 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto self.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll() self.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds) } + + if self.canReadHistoryValue { + self.forEachVisibleMessageItemNode { itemNode in + itemNode.unreadMessageRangeUpdated() + } + } } func takeGenericReactionEffect() -> String? { @@ -2490,6 +2509,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } var messageIdsToTranslate: [MessageId] = [] + var messageIdsToFactCheck: [MessageId] = [] if let translateToLanguage { let extendedRange: Int = 2 var wideIndexRange = (historyView.filteredEntries.count - 1 - visible.lastIndex - extendedRange, historyView.filteredEntries.count - 1 - visible.firstIndex + extendedRange) @@ -2506,22 +2526,30 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto guard message.adAttribute == nil && message.id.namespace == Namespaces.Message.Cloud else { continue } - if !message.text.isEmpty && message.author?.id != self.context.account.peerId { - if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage { - } else { - messageIdsToTranslate.append(message.id) - } + guard message.author?.id != self.context.account.peerId else { + continue + } + if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage { + continue + } + if !message.text.isEmpty { + messageIdsToTranslate.append(message.id) + } else if let _ = message.media.first(where: { $0 is TelegramMediaPoll }) { + messageIdsToTranslate.append(message.id) } case let .MessageGroupEntry(_, messages, _): for (message, _, _, _, _) in messages { guard message.adAttribute == nil && message.id.namespace == Namespaces.Message.Cloud else { continue } - if !message.text.isEmpty && message.author?.id != self.context.account.peerId { - if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage { - } else { - messageIdsToTranslate.append(message.id) - } + guard message.author?.id != self.context.account.peerId else { + continue + } + if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage { + continue + } + if !message.text.isEmpty { + messageIdsToTranslate.append(message.id) } } default: @@ -2574,6 +2602,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto var mediaRequiredValidation = false var hasUnseenReactions = false var storiesRequiredValidation = false + var factCheckRequired = false for attribute in message.attributes { if attribute is ViewCountMessageAttribute { if message.id.namespace == Namespaces.Message.Cloud { @@ -2595,6 +2624,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } } else if let _ = attribute as? ReplyStoryAttribute { storiesRequiredValidation = true + } else if let attribute = attribute as? FactCheckMessageAttribute, case .Pending = attribute.content { + factCheckRequired = true } } @@ -2655,6 +2686,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto if hasUnseenReactions { messageIdsWithUnseenReactions.append(message.id) } + if factCheckRequired { + messageIdsToFactCheck.append(message.id) + } if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.effectiveTopId == message.id { isTopReplyThreadMessageShownValue = true @@ -2676,6 +2710,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto var hasUnconsumedMention = false var hasUnconsumedContent = false var hasUnseenReactions = false + var factCheckRequired = false if message.tags.contains(.unseenPersonalMessage) { for attribute in message.attributes { if let attribute = attribute as? ConsumablePersonalMentionMessageAttribute, !attribute.pending { @@ -2705,6 +2740,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto hasUnconsumedContent = true } else if let attribute = attribute as? ReactionsMessageAttribute, attribute.hasUnseen { hasUnseenReactions = true + } else if let attribute = attribute as? FactCheckMessageAttribute, case .Pending = attribute.content { + factCheckRequired = true } } if hasUnconsumedMention && !hasUnconsumedContent { @@ -2713,6 +2750,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto if hasUnseenReactions { messageIdsWithUnseenReactions.append(message.id) } + if factCheckRequired { + messageIdsToFactCheck.append(message.id) + } if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.effectiveTopId == message.id { isTopReplyThreadMessageShownValue = true } @@ -2891,6 +2931,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto if !messageIdsToTranslate.isEmpty { self.translationProcessingManager.add(messageIdsToTranslate.map { MessageAndThreadId(messageId: $0, threadId: nil) }) } + if !messageIdsToFactCheck.isEmpty { + self.factCheckProcessingManager.add(messageIdsToFactCheck.map { MessageAndThreadId(messageId: $0, threadId: nil) }) + } if !visibleAdOpaqueIds.isEmpty { for opaqueId in visibleAdOpaqueIds { self.markAdAsSeen(opaqueId: opaqueId) @@ -3031,10 +3074,17 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto if self.chatHistoryLocationValue == historyView.locationInput { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount, highlight: false), id: self.takeNextHistoryLocationId()) } - } else if mathesLast && historyView.originalView.earlierId != nil { + } else if mathesLast { let locationInput: ChatHistoryLocation = .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount, highlight: false) - if self.chatHistoryLocationValue?.content != locationInput { - self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) + if historyView.originalView.earlierId != nil { + if self.chatHistoryLocationValue?.content != locationInput { + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) + } + } else if case let .customChatContents(customChatContents) = self.subject, case .hashTagSearch = customChatContents.kind { + if self.chatHistoryLocationValue?.content != locationInput { + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) + customChatContents.loadMore() + } } } } @@ -3560,12 +3610,18 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto if let currentRange = strongSelf.controllerInteraction.unreadMessageRange[rangeKey] { if currentRange.upperBound < (updatedIncomingId + 1) { - strongSelf.controllerInteraction.unreadMessageRange[UnreadMessageRangeKey(peerId: peerId, namespace: namespace)] = currentRange.lowerBound ..< (updatedIncomingId + 1) - unreadMessageRangeUpdated = true + let updatedRange = currentRange.lowerBound ..< (updatedIncomingId + 1) + if strongSelf.controllerInteraction.unreadMessageRange[rangeKey] != updatedRange { + strongSelf.controllerInteraction.unreadMessageRange[rangeKey] = updatedRange + unreadMessageRangeUpdated = true + } } } else { - strongSelf.controllerInteraction.unreadMessageRange[rangeKey] = (previousIncomingId + 1) ..< (updatedIncomingId + 1) - unreadMessageRangeUpdated = true + let updatedRange = (previousIncomingId + 1) ..< (updatedIncomingId + 1) + if strongSelf.controllerInteraction.unreadMessageRange[rangeKey] != updatedRange { + strongSelf.controllerInteraction.unreadMessageRange[rangeKey] = updatedRange + unreadMessageRangeUpdated = true + } } } case .indexBased: @@ -3579,6 +3635,33 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto //print("Read from \(previousPeerReadState) up to \(updatedPeerReadState)") } } + } else if case let .peer(peerId) = strongSelf.chatLocation, case let .peer(updatedReadStates) = transition.historyView.originalView.transientReadStates { + if let updatedPeerReadState = updatedReadStates[peerId] { + for (namespace, updatedState) in updatedPeerReadState.states { + switch updatedState { + case let .idBased(updatedIncomingId, _, _, _, _): + let rangeKey = UnreadMessageRangeKey(peerId: peerId, namespace: namespace) + + if let currentRange = strongSelf.controllerInteraction.unreadMessageRange[rangeKey] { + if currentRange.upperBound < (updatedIncomingId + 1) { + let updatedRange = currentRange.lowerBound ..< (updatedIncomingId + 1) + if strongSelf.controllerInteraction.unreadMessageRange[rangeKey] != updatedRange { + strongSelf.controllerInteraction.unreadMessageRange[rangeKey] = updatedRange + unreadMessageRangeUpdated = true + } + } + } else { + let updatedRange = (updatedIncomingId + 1) ..< (Int32.max - 1) + if strongSelf.controllerInteraction.unreadMessageRange[rangeKey] != updatedRange { + strongSelf.controllerInteraction.unreadMessageRange[rangeKey] = updatedRange + unreadMessageRangeUpdated = true + } + } + case .indexBased: + break + } + } + } } strongSelf.historyView = transition.historyView @@ -4010,8 +4093,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto // MARK: Nicegram AiChat, NuHub var insets = updateSizeAndInsets.insets var additionalBotInset: CGFloat = 0 - if AiChatUITgHelper.shouldShowAiBotInTgChat() { - additionalBotInset = max(additionalBotInset, 60) + if #available(iOS 15.0, *), NGSettings.showNicegramButtonInChat { + additionalBotInset = max(additionalBotInset, 44) } if showNgBanner { additionalBotInset = max(additionalBotInset, ChatBannerTgHelper.bannerHeight) @@ -4239,38 +4322,59 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto loop: for i in 0 ..< historyView.filteredEntries.count { switch historyView.filteredEntries[i] { - case let .MessageEntry(message, presentationData, read, location, selection, attributes): - if message.id == id { - let index = historyView.filteredEntries.count - 1 - i - let item: ListViewItem - switch self.mode { - case .bubbles: - // MARK: Nicegram, wantTrButton - item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders, wantTrButton: wantTrButton) - case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): - let displayHeader: Bool - switch displayHeaders { - case .none: - displayHeader = false - case .all: - displayHeader = true - case .allButLast: - displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId - } - item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) - } - let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) - - var scrollToItem: ListViewScrollToItem? - if scroll { - scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) + case let .MessageEntry(message, presentationData, read, location, selection, attributes): + if message.id == id { + let index = historyView.filteredEntries.count - 1 - i + let item: ListViewItem + switch self.mode { + case .bubbles: + // MARK: Nicegram, wantTrButton + item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders, wantTrButton: wantTrButton) + case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): + let displayHeader: Bool + switch displayHeaders { + case .none: + displayHeader = false + case .all: + displayHeader = true + case .allButLast: + displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId } - - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - break loop + item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } - default: - break + let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) + + var scrollToItem: ListViewScrollToItem? + if scroll { + scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) + } + + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + break loop + } + case let .MessageGroupEntry(_, messages, presentationData): + if messages.contains(where: { $0.0.id == id }) { + let index = historyView.filteredEntries.count - 1 - i + let item: ListViewItem + switch self.mode { + case .bubbles: + item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .group(messages: messages), disableDate: disableFloatingDateHeaders) + case .list: + assertionFailure() + item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) + } + let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) + + var scrollToItem: ListViewScrollToItem? + if scroll { + scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) + } + + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + break loop + } + default: + break } } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index c7b2d7274d0..1c23d5888a4 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -57,6 +57,8 @@ func serviceTasksForChatPresentationIntefaceState(context: AccountContext, chatP func inputContextQueriesForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState) -> [ChatPresentationInputQuery] { if case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + return [] case .quickReplyMessageInput: break case .businessLinkSetup: @@ -236,6 +238,8 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte if case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: break case .businessLinkSetup: diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 861cf5e9e4f..f8a1129b87f 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -200,6 +200,13 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: EngineCo return false } +private func canEditFactCheck(appConfig: AppConfiguration) -> Bool { + if let data = appConfig.data, let value = data["can_edit_factcheck"] as? Bool { + return value + } + return false +} + private func canViewReadStats(message: Message, participantCount: Int?, isMessageRead: Bool, isPremium: Bool, appConfig: AppConfiguration) -> Bool { guard let peer = message.peers[message.id.peerId] else { return false @@ -296,10 +303,12 @@ private func canViewReadStats(message: Message, participantCount: Int?, isMessag } func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, accountPeerId: PeerId) -> Bool { + if case let .customChatContents(contents) = chatPresentationInterfaceState.subject, case .hashTagSearch = contents.kind { + return false + } if case .customChatContents = chatPresentationInterfaceState.chatLocation { return true } - guard let peer = chatPresentationInterfaceState.renderedPeer?.peer else { return false } @@ -495,6 +504,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState isEmbeddedMode = true } + if case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject, case .hashTagSearch = customChatContents.kind { + isEmbeddedMode = true + } + var hasExpandedAudioTranscription = false if let messageNode = messageNode as? ChatMessageBubbleItemNode { hasExpandedAudioTranscription = messageNode.hasExpandedAudioTranscription() @@ -516,7 +529,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, textColor: .primary, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) }, iconSource: nil, iconPosition: .left, action: { c, _ in - c.popItems() + c?.popItems() }))) subItems.append(.separator) @@ -525,7 +538,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState subItems.append(.action(ContextMenuActionItem(text: sponsorInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in return nil }, iconSource: nil, action: { [weak controllerInteraction] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { UIPasteboard.general.string = sponsorInfo let content: UndoOverlayContent = .copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied) @@ -537,7 +550,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState subItems.append(.action(ContextMenuActionItem(text: additionalInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in return nil }, iconSource: nil, action: { [weak controllerInteraction] c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { UIPasteboard.general.string = additionalInfo let content: UndoOverlayContent = .copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied) @@ -546,7 +559,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState }))) } - c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) }))) actions.append(.separator) } @@ -594,7 +607,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_RemoveAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) }, iconSource: nil, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { controllerInteraction.openNoAdsDemo() }) }))) @@ -611,7 +624,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) }, iconSource: nil, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { var replaceImpl: ((ViewController) -> Void)? let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, forceDark: false, action: { let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil) @@ -1134,7 +1147,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.actionSheet.primaryTextColor) }, action: { c, _ in interfaceInteraction.setupReplyMessage(messages[0].id, { transition, completed in - c.dismiss(result: .custom(transition), completion: { + c?.dismiss(result: .custom(transition), completion: { completed() }) }) @@ -1476,7 +1489,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.append(.action(ContextMenuActionItem(text: text, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replies"), color: theme.actionSheet.primaryTextColor) }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { controllerInteraction.openMessageReplies(messages[0].id, true, true) }) }))) @@ -1768,7 +1781,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextViewStats, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.actionSheet.primaryTextColor) }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { controllerInteraction.openMessageStats(messages[0].id) }) }))) @@ -1777,11 +1790,56 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState clearCacheAsDelete = true } + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, canEditFactCheck(appConfig: appConfig) { + var canAddFactCheck = true + if message.media.contains(where: { $0 is TelegramMediaAction || $0 is TelegramMediaGiveaway }) { + canAddFactCheck = false + } + + if canAddFactCheck { + let sortedMessages = messages.sorted(by: { $0.id < $1.id }) + let hasFactCheck = sortedMessages[0].factCheckAttribute != nil + let title: String + if hasFactCheck { + title = chatPresentationInterfaceState.strings.Conversation_ContextMenuEditFactCheck + } else { + title = chatPresentationInterfaceState.strings.Conversation_ContextMenuAddFactCheck + } + actions.append(.action(ContextMenuActionItem(text: title, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/FactCheck"), color: theme.actionSheet.primaryTextColor) + }, action: { c, f in + c?.dismiss(completion: { + controllerInteraction.editMessageFactCheck(sortedMessages[0].id) + }) + }))) + } + } +// if message.id.peerId.isGroupOrChannel { +// //TODO:localize +// if message.isAgeRestricted() { +// actions.append(.action(ContextMenuActionItem(text: "Unmark as 18+", icon: { theme in +// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AgeUnmark"), color: theme.actionSheet.primaryTextColor) +// }, action: { c, _ in +// c?.dismiss(completion: { +// controllerInteraction.openMessageStats(messages[0].id) +// }) +// }))) +// } else { +// actions.append(.action(ContextMenuActionItem(text: "Mark as 18+", icon: { theme in +// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AgeMark"), color: theme.actionSheet.primaryTextColor) +// }, action: { c, _ in +// c?.dismiss(completion: { +// controllerInteraction.openMessageStats(messages[0].id) +// }) +// }))) +// } +// } + if isReplyThreadHead { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ViewInChannel, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.actionSheet.primaryTextColor) }, action: { c, _ in - c.dismiss(completion: { + c?.dismiss(completion: { guard let navigationController = controllerInteraction.navigationController() else { return } @@ -1903,7 +1961,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState }))) } - controller.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) + controller?.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) }))) } } @@ -2038,7 +2096,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } // - controller.setItems(.single(ContextController.Items(content: .list(ngContextItems))), minHeight: nil, animated: true) + controller?.setItems(.single(ContextController.Items(content: .list(ngContextItems))), minHeight: nil, animated: true) }))) // MARK: Nicegram SelectAllMessagesWithAuthor @@ -2104,7 +2162,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState case .needsPremium: message.removeSpeechToTextMeta(context: context) - PremiumUITgHelper.routeToPremium() + PremiumUITgHelper.routeToPremium( + source: .speechToText + ) case .error(let error): message.removeSpeechToTextMeta(context: context) @@ -2291,10 +2351,11 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } if let message = messages.first, case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject { - actions.removeAll() - switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: + actions.removeAll() if !messageText.isEmpty || (resourceAvailable && isImage) || diceEmoji != nil { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) @@ -2358,7 +2419,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState }))) } case .businessLinkSetup: - break + actions.removeAll() } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index dda878c56c1..76b38d024e4 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -61,7 +61,12 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState currentPanel.interfaceInteraction = interfaceInteraction return (currentPanel, selectionPanel) } else { - let panel = ChatTagSearchInputPanelNode(theme: chatPresentationInterfaceState.theme) + var alwaysShowTotalMessagesCount = false + if case let .customChatContents(contents) = chatPresentationInterfaceState.subject, case .hashTagSearch = contents.kind { + alwaysShowTotalMessagesCount = true + } + + let panel = ChatTagSearchInputPanelNode(theme: chatPresentationInterfaceState.theme, alwaysShowTotalMessagesCount: alwaysShowTotalMessagesCount) panel.context = context panel.interfaceInteraction = interfaceInteraction return (panel, selectionPanel) @@ -412,6 +417,8 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState if case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + displayInputTextPanel = false case .quickReplyMessageInput, .businessLinkSetup: displayInputTextPanel = true } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index b96441ca5fd..32d85e591d5 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -57,6 +57,8 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha if case let .customChatContents(customChatContents) = presentationInterfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput, .businessLinkSetup: if let currentButton = currentButton, currentButton.action == .dismiss { return currentButton @@ -124,6 +126,8 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present if case let .customChatContents(customChatContents) = presentationInterfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + return nil case let .quickReplyMessageInput(_, shortcutType): switch shortcutType { case .generic: diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 39a1b330776..784f45abf7c 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -62,6 +62,8 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat inhibitTitlePanelDisplay = true case let .customChatContents(customChatContents): switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: break case .businessLinkSetup: diff --git a/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift index 11b6798d26c..ca679407998 100644 --- a/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift @@ -361,6 +361,7 @@ final class ChatManagingBotTitlePanelNode: ChatTitleAccessoryPanelNode { }, sendFile: nil, sendSticker: nil, + sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak chatController] c, a in diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 3e20915deb9..d6630732859 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -13,7 +13,6 @@ import ChatControllerInteraction import FeaturedStickersScreen import ChatTextInputMediaRecordingButton import ReplyAccessoryPanelNode -import ChatMessageItemView import ChatMessageStickerItemNode import ChatMessageInstantVideoItemNode import ChatMessageAnimatedStickerItemNode @@ -21,6 +20,7 @@ import ChatMessageTransitionNode import ChatMessageBubbleItemNode import ChatEmptyNode import ChatMediaInputStickerGridItem +import AccountContext private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect { if let presentationLayer = fromView.layer.presentation() { @@ -241,7 +241,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } final class DecorationItemNodeImpl: ASDisplayNode, ChatMessageTransitionNode.DecorationItemNode { - let itemNode: ChatMessageItemView + let itemNode: ChatMessageItemNodeProtocol let contentView: UIView private let getContentAreaInScreenSpace: () -> CGRect @@ -251,7 +251,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran fileprivate weak var overlayController: OverlayTransitionContainerController? - init(itemNode: ChatMessageItemView, contentView: UIView, getContentAreaInScreenSpace: @escaping () -> CGRect) { + init(itemNode: ChatMessageItemNodeProtocol, contentView: UIView, getContentAreaInScreenSpace: @escaping () -> CGRect) { self.itemNode = itemNode self.contentView = contentView self.getContentAreaInScreenSpace = getContentAreaInScreenSpace @@ -291,17 +291,17 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } final class CustomOffsetHandlerImpl { - weak var itemNode: ChatMessageItemView? + weak var itemNode: ChatMessageItemNodeProtocol? let update: (CGFloat, ContainedViewLayoutTransition) -> Bool - init(itemNode: ChatMessageItemView, update: @escaping (CGFloat, ContainedViewLayoutTransition) -> Bool) { + init(itemNode: ChatMessageItemNodeProtocol, update: @escaping (CGFloat, ContainedViewLayoutTransition) -> Bool) { self.itemNode = itemNode self.update = update } } private final class AnimatingItemNode: ASDisplayNode { - let itemNode: ChatMessageItemView + let itemNode: ChatMessageItemNodeProtocol private let contextSourceNode: ContextExtractedContentContainingNode private let source: ChatMessageTransitionNodeImpl.Source private let getContentAreaInScreenSpace: () -> CGRect @@ -315,7 +315,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran var animationEnded: (() -> Void)? var updateAfterCompletion: Bool = false - init(itemNode: ChatMessageItemView, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNodeImpl.Source, getContentAreaInScreenSpace: @escaping () -> CGRect) { + init(itemNode: ChatMessageItemNodeProtocol, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNodeImpl.Source, getContentAreaInScreenSpace: @escaping () -> CGRect) { self.itemNode = itemNode self.getContentAreaInScreenSpace = getContentAreaInScreenSpace @@ -962,7 +962,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran self.listNode.setCurrentSendAnimationCorrelationIds(correlationIds) } - public func add(decorationView: UIView, itemNode: ChatMessageItemView) -> DecorationItemNode { + public func add(decorationView: UIView, itemNode: ChatMessageItemNodeProtocol) -> DecorationItemNode { let decorationItemNode = DecorationItemNodeImpl(itemNode: itemNode, contentView: decorationView, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace) decorationItemNode.updateLayout(size: self.bounds.size) @@ -980,7 +980,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } } - public func addCustomOffsetHandler(itemNode: ChatMessageItemView, update: @escaping (CGFloat, ContainedViewLayoutTransition) -> Bool) -> Disposable { + public func addCustomOffsetHandler(itemNode: ChatMessageItemNodeProtocol, update: @escaping (CGFloat, ContainedViewLayoutTransition) -> Bool) -> Disposable { let handler = CustomOffsetHandlerImpl(itemNode: itemNode, update: update) self.customOffsetHandlers.append(handler) @@ -994,7 +994,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } } - private func beginAnimation(itemNode: ChatMessageItemView, source: Source) { + private func beginAnimation(itemNode: ChatMessageItemNodeProtocol, source: Source) { var contextSourceNode: ContextExtractedContentContainingNode? if let itemNode = itemNode as? ChatMessageBubbleItemNode { contextSourceNode = itemNode.mainContextSourceNode @@ -1016,7 +1016,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran let overlayController = OverlayTransitionContainerController() overlayController.displayNode.addSubnode(animatingItemNode) animatingItemNode.overlayController = overlayController - itemNode.item?.context.sharedContext.mainWindow?.presentInGlobalOverlay(overlayController) + self.listNode.context.sharedContext.mainWindow?.presentInGlobalOverlay(overlayController) default: self.addSubnode(animatingItemNode) } @@ -1031,8 +1031,8 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran strongSelf.animatingItemNodes.remove(at: index) } - if animatingItemNode.updateAfterCompletion, let item = animatingItemNode.itemNode.item { - for (message, _) in item.content { + if animatingItemNode.updateAfterCompletion { + for message in animatingItemNode.itemNode.messages() { strongSelf.listNode.requestMessageUpdate(stableId: message.stableId) break } @@ -1081,14 +1081,9 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran var messageItemNode: ListViewItemNode? self.listNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - if let item = itemNode.item { - for (message, _) in item.content { - if message.id == messageId { - messageItemNode = itemNode - break - } - } + if let itemNode = itemNode as? ChatMessageItemNodeProtocol { + if itemNode.matchesMessage(id: messageId) { + messageItemNode = itemNode } } } @@ -1153,11 +1148,9 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran func isAnimatingMessage(stableId: UInt32) -> Bool { for itemNode in self.animatingItemNodes { - if let item = itemNode.itemNode.item { - for (message, _) in item.content { - if message.stableId == stableId { - return true - } + for message in itemNode.itemNode.messages() { + if message.stableId == stableId { + return true } } } @@ -1166,11 +1159,9 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran func scheduleUpdateMessageAfterAnimationCompleted(stableId: UInt32) { for itemNode in self.animatingItemNodes { - if let item = itemNode.itemNode.item { - for (message, _) in item.content { - if message.stableId == stableId { - itemNode.updateAfterCompletion = true - } + for message in itemNode.itemNode.messages() { + if message.stableId == stableId { + itemNode.updateAfterCompletion = true } } } @@ -1178,11 +1169,9 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran func hasScheduledUpdateMessageAfterAnimationCompleted(stableId: UInt32) -> Bool { for itemNode in self.animatingItemNodes { - if let item = itemNode.itemNode.item { - for (message, _) in item.content { - if message.stableId == stableId { - return itemNode.updateAfterCompletion - } + for message in itemNode.itemNode.messages() { + if message.stableId == stableId { + return itemNode.updateAfterCompletion } } } diff --git a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift index 5ba7e6c67b9..0e529f49c85 100644 --- a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift @@ -103,6 +103,8 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { } else if case let .customChatContents(customChatContents) = interfaceState.subject { let displayCount: Int switch customChatContents.kind { + case .hashTagSearch: + displayCount = 0 case .quickReplyMessageInput: displayCount = customChatContents.messageLimit ?? 20 case .businessLinkSetup: diff --git a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift index acbcf0c576d..3b9af15fca8 100644 --- a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift @@ -680,7 +680,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat return } - c.dismiss(completion: { [weak self] in + c?.dismiss(completion: { [weak self] in guard let self, let item = self.items.first(where: { $0.reaction == reaction }) else { return } diff --git a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift index 08755943c2e..0e5eaa3c476 100644 --- a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift @@ -91,6 +91,8 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { private var isUpdating: Bool = false + private var alwaysShowTotalMessagesCount = false + private var currentLayout: Layout? private var tagMessageCount: (tag: MemoryBuffer, count: Int?, disposable: Disposable?)? @@ -103,7 +105,9 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { } } - init(theme: PresentationTheme) { + init(theme: PresentationTheme, alwaysShowTotalMessagesCount: Bool) { + self.alwaysShowTotalMessagesCount = alwaysShowTotalMessagesCount + super.init() } @@ -224,7 +228,15 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) { canChangeListMode = true - if params.interfaceState.displayHistoryFilterAsList { + if self.alwaysShowTotalMessagesCount { + let value = presentationStringsFormattedNumber(Int32(displayTotalCount), params.interfaceState.dateTimeFormat.groupingSeparator) + let suffix = params.interfaceState.strings.Chat_BottomSearchPanel_MessageCount(Int32(displayTotalCount)) + resultsTextString = [AnimatedTextComponent.Item( + id: "text", + isUnbreakable: true, + content: .text(params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(value, suffix).string) + )] + } else if params.interfaceState.displayHistoryFilterAsList { resultsTextString = extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat( ".", "." @@ -332,36 +344,41 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { } } - var nextLeftX: CGFloat = 12.0 + var nextLeftX: CGFloat = 16.0 - let calendarButtonSize = self.calendarButton.update( - transition: .immediate, - component: AnyComponent(PlainButtonComponent( - content: AnyComponent(BundleIconComponent( - name: "Chat/Input/Search/Calendar", - tintColor: params.interfaceState.theme.rootController.navigationBar.accentTextColor - )), - effectAlignment: .center, - minSize: CGSize(width: 1.0, height: size.height), - contentInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), - action: { [weak self] in - guard let self else { - return + if !self.alwaysShowTotalMessagesCount { + nextLeftX = 12.0 + let calendarButtonSize = self.calendarButton.update( + transition: .immediate, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(BundleIconComponent( + name: "Chat/Input/Search/Calendar", + tintColor: params.interfaceState.theme.rootController.navigationBar.accentTextColor + )), + effectAlignment: .center, + minSize: CGSize(width: 1.0, height: size.height), + contentInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), + action: { [weak self] in + guard let self else { + return + } + self.interfaceInteraction?.openCalendarSearch() } - self.interfaceInteraction?.openCalendarSearch() + )), + environment: {}, + containerSize: size + ) + let calendarButtonFrame = CGRect(origin: CGPoint(x: nextLeftX, y: floor((size.height - calendarButtonSize.height) * 0.5)), size: calendarButtonSize) + if let calendarButtonView = self.calendarButton.view { + if calendarButtonView.superview == nil { + self.view.addSubview(calendarButtonView) } - )), - environment: {}, - containerSize: size - ) - let calendarButtonFrame = CGRect(origin: CGPoint(x: nextLeftX, y: floor((size.height - calendarButtonSize.height) * 0.5)), size: calendarButtonSize) - if let calendarButtonView = self.calendarButton.view { - if calendarButtonView.superview == nil { - self.view.addSubview(calendarButtonView) + transition.setFrame(view: calendarButtonView, frame: calendarButtonFrame) } - transition.setFrame(view: calendarButtonView, frame: calendarButtonFrame) + nextLeftX += calendarButtonSize.width + 8.0 + } else if let calendarButtonView = self.calendarButton.view { + calendarButtonView.removeFromSuperview() } - nextLeftX += calendarButtonSize.width + 8.0 if displaySearchMembers { let membersButton: ComponentView @@ -432,6 +449,10 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { self.resultsText = resultsText } + if self.alwaysShowTotalMessagesCount { + resultsTextTransition = .immediate + } + let resultsTextSize = resultsText.update( transition: resultsTextTransition, component: AnyComponent(AnimatedTextComponent( diff --git a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift index eb3d77fd2f1..09c63ace837 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift @@ -67,7 +67,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode { self.sendButton.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { - if strongSelf.sendButtonHasApplyIcon || !strongSelf.sendButtonLongPressEnabled { + if !strongSelf.sendButtonLongPressEnabled { if highlighted { strongSelf.sendContainerNode.layer.removeAnimation(forKey: "opacity") strongSelf.sendContainerNode.alpha = 0.4 @@ -109,9 +109,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode { guard let strongSelf = self else { return } - if !strongSelf.sendButtonHasApplyIcon { - strongSelf.sendButtonLongPressed?(strongSelf.sendContainerNode, recognizer) - } + strongSelf.sendButtonLongPressed?(strongSelf, recognizer) } self.micButtonPointerInteraction = PointerInteraction(view: self.micButton, style: .circle(36.0)) diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 684eee7399a..5b1bafba999 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -430,6 +430,7 @@ enum ChatTextInputPanelPasteData { case video(Data) case gif(Data) case sticker(UIImage, Bool) + case animatedSticker(Data) } final class ChatTextViewForOverlayContent: UIView, ChatInputPanelViewForOverlayContent { @@ -730,7 +731,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.loadTextInputNode() } - if let textInputNode = self.textInputNode, let _ = self.presentationInterfaceState { + if let textInputNode = self.textInputNode, let _ = self.presentationInterfaceState, let context = self.context { self.updatingInputState = true var textColor: UIColor = .black @@ -741,11 +742,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) } - textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider) + textInputNode.attributedText = textAttributedStringForStateText(context: context, stateText: state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count) if let presentationInterfaceState = self.presentationInterfaceState { - refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider) + refreshChatTextInputAttributes(context: context, textView: textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) } self.updatingInputState = false @@ -1243,6 +1248,59 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.textInputBackgroundNode.isUserInteractionEnabled = !textInputNode.isUserInteractionEnabled //self.textInputBackgroundNode.view.removeGestureRecognizer(self.textInputBackgroundNode.view.gestureRecognizers![0]) + textInputNode.textView.toggleQuoteCollapse = { [weak self] range in + guard let self else { + return + } + + self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in + let result = NSMutableAttributedString(attributedString: current.inputText) + var selectionRange = current.selectionRange + + if let _ = result.attribute(ChatTextInputAttributes.block, at: range.lowerBound, effectiveRange: nil) as? ChatTextInputTextQuoteAttribute { + let blockString = NSMutableAttributedString(attributedString: result.attributedSubstring(from: range)) + blockString.removeAttribute(ChatTextInputAttributes.block, range: NSRange(location: 0, length: blockString.length)) + + result.replaceCharacters(in: range, with: "") + result.insert(NSAttributedString(string: " ", attributes: [ + ChatTextInputAttributes.collapsedBlock: blockString + ]), at: range.lowerBound) + + if selectionRange.lowerBound >= range.lowerBound && selectionRange.upperBound < range.upperBound { + selectionRange = range.lowerBound ..< range.lowerBound + } else if selectionRange.lowerBound >= range.upperBound { + let deltaLength = 1 - range.length + selectionRange = (selectionRange.lowerBound + deltaLength) ..< (selectionRange.lowerBound + deltaLength) + } + } else if let current = result.attribute(ChatTextInputAttributes.collapsedBlock, at: range.lowerBound, effectiveRange: nil) as? NSAttributedString { + result.replaceCharacters(in: range, with: "") + + let updatedBlockString = NSMutableAttributedString(attributedString: current) + updatedBlockString.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false), range: NSRange(location: 0, length: updatedBlockString.length)) + + result.insert(updatedBlockString, at: range.lowerBound) + + if selectionRange.lowerBound >= range.upperBound { + let deltaLength = updatedBlockString.length - 1 + selectionRange = (selectionRange.lowerBound + deltaLength) ..< (selectionRange.lowerBound + deltaLength) + } + } + + let stateResult = stateAttributedStringForText(result) + if selectionRange.lowerBound < 0 { + selectionRange = 0 ..< selectionRange.upperBound + } + if selectionRange.upperBound > stateResult.length { + selectionRange = selectionRange.lowerBound ..< stateResult.length + } + + return (ChatTextInputState( + inputText: stateResult, + selectionRange: selectionRange + ), inputMode) + } + } + let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:))) recognizer.touchDown = { [weak self] in if let strongSelf = self { @@ -1540,6 +1598,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch var displayMediaButton = true if case let .customChatContents(customChatContents) = interfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: break case .businessLinkSetup: @@ -1906,6 +1966,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } if case let .customChatContents(customChatContents) = interfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + placeholder = "" case let .quickReplyMessageInput(_, shortcutType): switch shortcutType { case .generic: @@ -1938,6 +2000,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch if let interfaceState = self.presentationInterfaceState { if case let .customChatContents(customChatContents) = interfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: break case .businessLinkSetup: @@ -2935,9 +2999,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } func chatInputTextNodeDidUpdateText() { - if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState { + if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState, let context = self.context { let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) - refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider) + refreshChatTextInputAttributes(context: context, textView: textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) self.updateSpoiler() @@ -3097,7 +3163,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } private func updateInternalSpoilersRevealed(_ revealed: Bool, animated: Bool) { - guard self.spoilersRevealed == revealed, let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState else { + guard self.spoilersRevealed == revealed, let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState, let context = self.context else { return } @@ -3107,9 +3173,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch textInputNode.textView.isScrollEnabled = false - refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider) + refreshChatTextInputAttributes(context: context, textView: textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) - textInputNode.attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider) + textInputNode.attributedText = textAttributedStringForStateText(context: context, stateText: self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) if textInputNode.textView.subviews.count > 1, animated { let containerView = textInputNode.textView.subviews[1] @@ -3427,7 +3497,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch return (sourceView, sourceRect) }) //strongSelf.peekController = controller - strongSelf.interfaceInteraction?.presentController(controller, nil) + strongSelf.interfaceInteraction?.presentGlobalOverlayController(controller, nil) return controller }, updateContent: { [weak self] content in guard let strongSelf = self else { @@ -3723,6 +3793,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch if case let .customChatContents(customChatContents) = presentationInterfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: break case .businessLinkSetup: @@ -3841,6 +3913,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch if let interfaceState = self.presentationInterfaceState { if case let .customChatContents(customChatContents) = interfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: break case .businessLinkSetup: @@ -3944,6 +4018,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch var sendButtonHasApplyIcon = interfaceState.interfaceState.editMessage != nil if case let .customChatContents(customChatContents) = interfaceState.subject { switch customChatContents.kind { + case .hashTagSearch: + break case .quickReplyMessageInput: break case .businessLinkSetup: @@ -4300,7 +4376,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.inputMenu.back() self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in - return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote)), inputMode) + return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false)), inputMode) } } @@ -4308,7 +4384,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.inputMenu.back() self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in - return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: nil))), inputMode) + return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: nil), isCollapsed: false)), inputMode) } } @@ -4332,7 +4408,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } func chatInputTextNode(shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - guard let editableTextNode = self.textInputNode else { + guard let editableTextNode = self.textInputNode, let context = self.context else { return false } @@ -4366,7 +4442,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) } - let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider) + let cleanReplacementString = textAttributedStringForStateText(context: context, stateText: NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) string.replaceCharacters(in: range, with: cleanReplacementString) self.textInputNode?.attributedText = string self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0) @@ -4436,6 +4514,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } else if let data = pasteboard.data(forPasteboardType: "public.mpeg-4") { self.paste(.video(data)) return false + } else if let data = pasteboard.data(forPasteboardType: "public.heics") { + self.paste(.animatedSticker(data)) + return false } else { var isPNG = false var isMemoji = false diff --git a/submodules/TelegramUI/Sources/ChatTranslationPanelNode.swift b/submodules/TelegramUI/Sources/ChatTranslationPanelNode.swift index 5ca9802b749..32ca974cf6f 100644 --- a/submodules/TelegramUI/Sources/ChatTranslationPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTranslationPanelNode.swift @@ -298,7 +298,7 @@ final class ChatTranslationPanelNode: ASDisplayNode { } } - c.pushItems(items: .single(ContextController.Items( + c?.pushItems(items: .single(ContextController.Items( content: .custom( TranslationLanguagesContextMenuContent( context: self.context, @@ -322,7 +322,7 @@ final class ChatTranslationPanelNode: ASDisplayNode { items.append(.action(ContextMenuActionItem(text: doNotTranslateTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: nil) + c?.dismiss(completion: nil) self?.interfaceInteraction?.addDoNotTranslateLanguage(translationState.fromLang) }))) @@ -330,7 +330,7 @@ final class ChatTranslationPanelNode: ASDisplayNode { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_Translation_Hide, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: nil) + c?.dismiss(completion: nil) self?.interfaceInteraction?.hideTranslationPanel() }))) diff --git a/submodules/TelegramUI/Sources/ComposeController.swift b/submodules/TelegramUI/Sources/ComposeController.swift index d62b23f8683..8700b655e68 100644 --- a/submodules/TelegramUI/Sources/ComposeController.swift +++ b/submodules/TelegramUI/Sources/ComposeController.swift @@ -13,6 +13,7 @@ import SearchUI import TelegramPermissionsUI import AppBundle import DeviceAccess +import ShareController public class ComposeControllerImpl: ViewController, ComposeController { private let context: AccountContext @@ -157,7 +158,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { strongSelf.createActionDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak controller] result in - if let strongSelf = self, let (contactPeers, _, _, _, _) = result, case let .peer(peer, _, _) = contactPeers.first { + if let strongSelf = self, let (contactPeers, _, _, _, _, _) = result, case let .peer(peer, _, _) = contactPeers.first { controller?.dismissSearch() controller?.displayNavigationActivity = true strongSelf.createActionDisposable.set((strongSelf.context.engine.peers.createSecretChat(peerId: peer.id) |> deliverOnMainQueue).startStrict(next: { peerId in @@ -200,7 +201,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { switch status { case .allowed: let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in guard let strongSelf = self else { return } @@ -211,7 +212,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { } } } else { - (strongSelf.navigationController as? NavigationController)?.replaceAllButRootController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil), animated: true) + (strongSelf.navigationController as? NavigationController)?.replaceAllButRootController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil), animated: true) } }), completed: nil, cancelled: nil)) case .notDetermined: diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 65cf9921a35..7db9e4a61ea 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -12,6 +12,8 @@ import ContactListUI import SearchUI import AttachmentUI import SearchBarNode +import ChatSendAudioMessageContextPreview +import ChatSendMessageActionUI class ContactSelectionControllerImpl: ViewController, ContactSelectionController, PresentableController, AttachmentContainable { private let context: AccountContext @@ -46,8 +48,8 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController fileprivate var caption: NSAttributedString? - private let _result = Promise<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?)?>() - var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?)?, NoError> { + private let _result = Promise<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?>() + var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?, NoError> { return self._result.get() } @@ -87,6 +89,8 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController var isContainerPanning: () -> Bool = { return false } var isContainerExpanded: () -> Bool = { return false } + var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? + init(_ params: ContactSelectionControllerParams) { self.context = params.context self.autoDismiss = params.autoDismiss @@ -145,6 +149,24 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController if params.multipleSelection { self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.beginSearch)) } + + self.getCurrentSendMessageContextMediaPreview = { [weak self] in + guard let self else { + return nil + } + + let selectedPeers = self.contactsNode.contactListNode.selectedPeers + if selectedPeers.isEmpty { + return nil + } + + return ChatSendContactMessageContextPreview( + context: self.context, + presentationData: self.presentationData, + wallpaperBackgroundNode: nil, + contactPeers: selectedPeers + ) + } } required init(coder aDecoder: NSCoder) { @@ -235,10 +257,10 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController } } - self.contactsNode.requestMultipleAction = { [weak self] silent, scheduleTime in + self.contactsNode.requestMultipleAction = { [weak self] silent, scheduleTime, parameters in if let strongSelf = self { let selectedPeers = strongSelf.contactsNode.contactListNode.selectedPeers - strongSelf._result.set(.single((selectedPeers, .generic, silent, scheduleTime, strongSelf.caption))) + strongSelf._result.set(.single((selectedPeers, .generic, silent, scheduleTime, strongSelf.caption, parameters))) if strongSelf.autoDismiss { strongSelf.dismiss() } @@ -337,7 +359,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.confirmationDisposable.set((self.confirmation(peer) |> deliverOnMainQueue).startStrict(next: { [weak self] value in if let strongSelf = self { if value { - strongSelf._result.set(.single(([peer], action, false, nil, nil))) + strongSelf._result.set(.single(([peer], action, false, nil, nil, nil))) if strongSelf.autoDismiss { strongSelf.dismiss() } @@ -435,6 +457,17 @@ final class ContactsPickerContext: AttachmentMediaPickerContext { return .single(nil) } + var hasCaption: Bool { + return false + } + + var captionIsAboveMedia: Signal { + return .single(false) + } + + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { + } + public var loadingProgress: Signal { return .single(nil) } @@ -451,13 +484,13 @@ final class ContactsPickerContext: AttachmentMediaPickerContext { self.controller?.caption = caption } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { - self.controller?.contactsNode.requestMultipleAction?(mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil) + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { + self.controller?.contactsNode.requestMultipleAction?(mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, parameters) } - func schedule() { + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { self.controller?.presentScheduleTimePicker ({ time in - self.controller?.contactsNode.requestMultipleAction?(false, time) + self.controller?.contactsNode.requestMultipleAction?(false, time, parameters) }) } diff --git a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift index 3285c1f8246..cface4f8c96 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift @@ -38,7 +38,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { var requestDeactivateSearch: (() -> Void)? var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)? var requestOpenDisabledPeerFromSearch: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? - var requestMultipleAction: ((_ silent: Bool, _ scheduleTime: Int32?) -> Void)? + var requestMultipleAction: ((_ silent: Bool, _ scheduleTime: Int32?, _ parameters: ChatSendMessageActionSheetController.SendParameters?) -> Void)? var dismiss: (() -> Void)? var cancelSearch: (() -> Void)? @@ -110,7 +110,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { } shareImpl = { [weak self] in - self?.requestMultipleAction?(false, nil) + self?.requestMultipleAction?(false, nil, nil) } contextActionImpl = { [weak self] peer, node, gesture, _ in diff --git a/submodules/TelegramUI/Sources/CreateChannelController.swift b/submodules/TelegramUI/Sources/CreateChannelController.swift index 88091d0eb9a..c5f11b60484 100644 --- a/submodules/TelegramUI/Sources/CreateChannelController.swift +++ b/submodules/TelegramUI/Sources/CreateChannelController.swift @@ -191,7 +191,7 @@ private enum CreateChannelEntry: ItemListNodeEntry { let arguments = arguments as! CreateChannelArguments switch self { case let .channelInfo(_, _, dateTimeFormat, peer, state, avatar): - return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in + return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in arguments.updateEditingName(editingName) }, editingNameCompleted: { arguments.focusOnDescription() diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index a47f348bc96..d1108c444da 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -318,7 +318,7 @@ private enum CreateGroupEntry: ItemListNodeEntry { let arguments = arguments as! CreateGroupArguments switch self { case let .groupInfo(_, _, dateTimeFormat, peer, state, avatar): - return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in + return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in arguments.updateEditingName(editingName) }, editingNameCompleted: { arguments.done() diff --git a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift index cad3ddcb940..5bb15e1996f 100644 --- a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift @@ -172,7 +172,9 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { // MARK: Nicegram changes guard let peer = peer else { guard isPremium() else { - PremiumUITgHelper.routeToPremium() + PremiumUITgHelper.routeToPremium( + source: .mentionAll + ) return (textInputState, inputMode) } let addressNames = NSMutableAttributedString() diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index e9ced9f7f30..f99072a72a7 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -354,7 +354,7 @@ public func isOverlayControllerForChatNotificationOverlayPresentation(_ controll return false } -public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal { +public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack) -> Signal { return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: messageId, preload: false) |> deliverOnMainQueue |> beforeNext { [weak context, weak navigationController] result in @@ -375,7 +375,9 @@ public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePee chatLocationContextHolder: result.contextHolder, subject: messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil) }, activateInput: actualActivateInput, - keepStack: keepStack + keepStack: keepStack, + scrollToEndIfExists: scrollToEndIfExists, + animated: !scrollToEndIfExists ) ) } diff --git a/submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift b/submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift index 5381d7975cf..79e16163fa0 100644 --- a/submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift +++ b/submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift @@ -4,6 +4,7 @@ import Display import FeatAvatarGeneratorUI import FeatCardUI import FeatImagesHubUI +import FeatOnboarding import FeatPremiumUI import FeatRewardsUI import FeatTasks @@ -14,7 +15,6 @@ import NGAuth import NGCore import class NGCoreUI.SharedLoadingView import NGModels -import NGOnboarding import NGRemoteConfig import NGSpecialOffer import NGUI @@ -173,7 +173,9 @@ private extension NGDeeplinkHandler { } func handleNicegramPremium(url: URL) -> Bool { - PremiumUITgHelper.routeToPremium() + PremiumUITgHelper.routeToPremium( + source: .deeplink + ) return true } @@ -191,6 +193,10 @@ private extension NGDeeplinkHandler { } func handleOnboarding(url: URL) -> Bool { + guard #available(iOS 15.0, *) else { + return false + } + var dismissImpl: (() -> Void)? let c = onboardingController() { diff --git a/submodules/TelegramUI/Sources/Nicegram/Wallet/ContactImageProviderImpl.swift b/submodules/TelegramUI/Sources/Nicegram/Wallet/ContactImageProviderImpl.swift new file mode 100644 index 00000000000..0e5032e9c0b --- /dev/null +++ b/submodules/TelegramUI/Sources/Nicegram/Wallet/ContactImageProviderImpl.swift @@ -0,0 +1,32 @@ +import AccountContext +import NGUtils +import NicegramWallet +import Postbox +import TelegramCore +import UIKit + +struct ContactImageProviderImpl { + static func image( + context: AccountContext, + contact: WalletContact + ) async -> UIImage? { + guard let peerId = WalletTgUtils.contactIdToPeerId(contact.id) else { + return nil + } + + guard let peer = await WalletTgUtils.peerById( + peerId, + context: context + ) else { + return nil + } + + let imageData = try? await fetchAvatarImage( + peer: peer._asPeer(), + context: context + ).awaitForFirstValue() + guard let imageData else { return nil } + + return UIImage(data: imageData) + } +} diff --git a/submodules/TelegramUI/Sources/Nicegram/Wallet/ContactMessageSenderImpl.swift b/submodules/TelegramUI/Sources/Nicegram/Wallet/ContactMessageSenderImpl.swift new file mode 100644 index 00000000000..1756949b890 --- /dev/null +++ b/submodules/TelegramUI/Sources/Nicegram/Wallet/ContactMessageSenderImpl.swift @@ -0,0 +1,35 @@ +import AccountContext +import NGUtils +import NicegramWallet +import TelegramCore + +struct ContactMessageSenderImpl { + static func send( + context: AccountContext, + text: String, + contactId: WalletContactId + ) { + guard let peerId = WalletTgUtils.contactIdToPeerId(contactId) else { + return + } + + let message = EnqueueMessage.message( + text: text, + attributes: [], + inlineStickers: [:], + mediaReference: nil, + threadId: nil, + replyToMessageId: nil, + replyToStoryId: nil, + localGroupingKey: nil, + correlationId: nil, + bubbleUpEmojiOrStickersets: [] + ) + + let _ = enqueueMessages( + account: context.account, + peerId: peerId, + messages: [message] + ).start() + } +} diff --git a/submodules/TelegramUI/Sources/Nicegram/Wallet/ContactsRetrieverImpl.swift b/submodules/TelegramUI/Sources/Nicegram/Wallet/ContactsRetrieverImpl.swift new file mode 100644 index 00000000000..7aca581eda6 --- /dev/null +++ b/submodules/TelegramUI/Sources/Nicegram/Wallet/ContactsRetrieverImpl.swift @@ -0,0 +1,27 @@ +import AccountContext +import NGUtils +import NicegramWallet +import TelegramCore + +struct ContactsRetrieverImpl { + static func getContacts( + context: AccountContext + ) async -> [WalletContact] { + do { + return try await context.engine.data + .get( + TelegramEngine.EngineData.Item.Contacts.List(includePresences: false) + ) + .awaitForFirstValue() + .peers + .map { peer in + WalletTgUtils.peerToWalletContact( + peer: peer + ) + } + .filter { !$0.name.isEmpty } + } catch { + return [] + } + } +} diff --git a/submodules/TelegramUI/Sources/Nicegram/Wallet/WalletVerificationInterceptorImpl.swift b/submodules/TelegramUI/Sources/Nicegram/Wallet/WalletVerificationInterceptorImpl.swift new file mode 100644 index 00000000000..aefa0a4b6a5 --- /dev/null +++ b/submodules/TelegramUI/Sources/Nicegram/Wallet/WalletVerificationInterceptorImpl.swift @@ -0,0 +1,27 @@ +import AccountContext + +struct WalletVerificationInterceptorImpl { + static func shouldVerifyOnApplicationResignActive( + context: AccountContext + ) async -> Bool { + do { + let accountManager = context.sharedContext.accountManager + + let accessChallengeData = try await accountManager + .accessChallengeData() + .awaitForFirstValue() + .data + + let hasTelegramPasscode = switch accessChallengeData { + case .none: + false + default: + true + } + + return !hasTelegramPasscode + } catch { + return false + } + } +} diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift index 3d7b08a1d80..b6e15e4263c 100644 --- a/submodules/TelegramUI/Sources/NotificationContentContext.swift +++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift @@ -142,7 +142,7 @@ public final class NotificationViewControllerImpl { return nil }) // MARK: Nicegram DB Changes, openDoubleBottomFlow added - sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, openDoubleBottomFlow: { _ in }, appDelegate: nil) + sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), externalRequestVerificationStream: .never(), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, openDoubleBottomFlow: { _ in }, appDelegate: nil) presentationDataPromise.set(sharedAccountContext!.presentationData) } diff --git a/submodules/TelegramUI/Sources/OpenAddContact.swift b/submodules/TelegramUI/Sources/OpenAddContact.swift index b4c860dbc9f..75b46220055 100644 --- a/submodules/TelegramUI/Sources/OpenAddContact.swift +++ b/submodules/TelegramUI/Sources/OpenAddContact.swift @@ -7,6 +7,7 @@ import AccountContext import AlertUI import PresentationDataUtils import PeerInfoUI +import ShareController func openAddContactImpl(context: AccountContext, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!!$_", present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void = {}) { let _ = (DeviceAccess.authorizationStatus(subject: .contacts) @@ -15,13 +16,13 @@ func openAddContactImpl(context: AccountContext, firstName: String = "", lastNam switch value { case .allowed: let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: label, value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - present(deviceContactInfoController(context: context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + present(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in if let peer = peer { if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushController(infoController) } } else { - pushController(deviceContactInfoController(context: context, subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) + pushController(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) } }), completed: completed, cancelled: nil), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) case .notDetermined: diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index b22296c21a1..cbb4c970176 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -186,7 +186,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { if actions.count > 1, let first = actions.first { if case .add = first.2 { - params.navigationController?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.EmojiPackActionInfo_AddedTitle, text: presentationData.strings.EmojiPackActionInfo_MultipleAddedText(Int32(actions.count)), undo: false, info: first.0, topItem: first.1.first, context: params.context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in + params.navigationController?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.EmojiPackActionInfo_AddedTitle, text: presentationData.strings.EmojiPackActionInfo_MultipleAddedText(Int32(actions.count)), undo: false, info: first.0, topItem: first.1.first, context: params.context), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return true })) } @@ -204,16 +204,18 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } switch action { case .add: - params.navigationController?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedTitle : presentationData.strings.StickerPackActionInfo_AddedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string : presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: params.context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in + let controller = UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedTitle : presentationData.strings.StickerPackActionInfo_AddedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string : presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: params.context), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return true - })) + }) + (params.navigationController?.topViewController as? ViewController)?.present(controller, in: .current) case let .remove(positionInList): - params.navigationController?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedText(info.title).string : presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: params.context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in + let controller = UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedText(info.title).string : presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: params.context), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { action in if case .undo = action { let _ = params.context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).startStandalone() } return true - })) + }) + (params.navigationController?.topViewController as? ViewController)?.present(controller, in: .current) } } }, getSourceRect: params.getSourceRect) @@ -328,7 +330,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } else { contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName, lastName: contact.lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: contact.phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") } - let controller = deviceContactInfoController(context: params.context, updatedPresentationData: params.updatedPresentationData, subject: .vcard(peer?._asPeer(), nil, contactData), completed: nil, cancelled: nil) + let controller = deviceContactInfoController(context: ShareControllerAppAccountContext(context: params.context), environment: ShareControllerAppEnvironment(sharedContext: params.context.sharedContext), updatedPresentationData: params.updatedPresentationData, subject: .vcard(peer?._asPeer(), nil, contactData), completed: nil, cancelled: nil) params.navigationController?.pushViewController(controller) }) return true diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index cbc80a7cf36..3395f95322a 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -60,6 +60,7 @@ func openResolvedUrlImpl( openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, + sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)? = nil, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, @@ -215,12 +216,12 @@ func openResolvedUrlImpl( } case let .replyThread(messageId): if let navigationController = navigationController { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).startStandalone() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone() } case let .stickerPack(name, _): dismissInput() - let controller = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: .name(name), stickerPacks: [.name(name)], parentNavigationController: navigationController, sendSticker: sendSticker, actionPerformed: { actions in + let controller = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: .name(name), stickerPacks: [.name(name)], parentNavigationController: navigationController, sendSticker: sendSticker, sendEmoji: sendEmoji, actionPerformed: { actions in if actions.count > 1, let first = actions.first { if case .add = first.2 { present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.EmojiPackActionInfo_AddedTitle, text: presentationData.strings.EmojiPackActionInfo_MultipleAddedText(Int32(actions.count)), undo: false, info: first.0, topItem: first.1.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in @@ -232,11 +233,11 @@ func openResolvedUrlImpl( switch action { case .add: - present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedTitle : presentationData.strings.StickerPackActionInfo_AddedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string : presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in + present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedTitle : presentationData.strings.StickerPackActionInfo_AddedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string : presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return true }), nil) case let .remove(positionInList): - present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedText(info.title).string : presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { action in + present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedText(info.title).string : presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: false, animateInAsReplacement: false, action: { action in if case .undo = action { let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).startStandalone() } @@ -250,9 +251,46 @@ func openResolvedUrlImpl( navigationController?.pushViewController(InstantPageController(context: context, webPage: webpage, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .channel), anchor: anchor)) case let .join(link): dismissInput() - present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in - openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: peekData)) - }, parentNavigationController: navigationController), nil) + + if let progress { + let progressSignal = Signal { subscriber in + progress.set(.single(true)) + return ActionDisposable { + Queue.mainQueue().async() { + progress.set(.single(false)) + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.1, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.startStrict() + + var signal = context.engine.peers.joinLinkInformation(link) + signal = signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + + let _ = (signal + |> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedState in + switch resolvedState { + case let .alreadyJoined(peer): + openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) + case let .peek(peer, deadline): + openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: ChatPeekTimeout(deadline: deadline, linkData: link))) + default: + present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in + openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: peekData)) + }, parentNavigationController: navigationController, resolvedState: resolvedState), nil) + } + }) + } else { + present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in + openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: peekData)) + }, parentNavigationController: navigationController), nil) + } case let .localization(identifier): dismissInput() present(LanguageLinkPreviewController(context: context, identifier: identifier), nil) @@ -782,25 +820,43 @@ 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 starsContext = context.starsContext { + let starsInputData = combineLatest( + inputData.get(), + starsContext.state + ) + |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in + if let data, let state { + return (state, data.form, data.botPeer) + } else { + return nil + } + } + let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { _ in + let controller = context.sharedContext.makeStarsTransferScreen(context: context, starsContext: starsContext, invoice: invoice, source: .slug(slug), inputData: starsInputData, completion: { _ in }) + 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/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 6dfd49f5047..7805c572b8b 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -1,3 +1,6 @@ +// MARK: Nicegram Deeplink +import NicegramWallet +// import Foundation import Display import SafariServices @@ -176,6 +179,11 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur if nicegramHandler.handle(url: url) { return } + + let walletDeeplinksManager = NicegramWallet.DeeplinksModule.shared.deeplinksManager() + if walletDeeplinksManager.handle(url) { + return + } // if forceExternal || url.lowercased().hasPrefix("tel:") || url.lowercased().hasPrefix("calshow:") { @@ -265,8 +273,10 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur default: break } - }, sendFile: nil, + }, + sendFile: nil, sendSticker: nil, + sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: { peerId, invite, call in diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index b6382e73963..116f1af799f 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -86,7 +86,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, navigateToThreadMessage: { _, _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in - }, sendCurrentMessage: { _ in + }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false @@ -132,9 +132,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, requestSelectMessagePollOptions: { _, _ in }, requestOpenMessagePollResults: { _, _ in }, openAppStorePage: { - }, displayMessageTooltip: { _, _, _, _ in + }, displayMessageTooltip: { _, _, _, _, _ in }, seekToTimecode: { _, _, _ in - }, scheduleCurrentMessage: { + }, scheduleCurrentMessage: { _ in }, sendScheduledMessagesNow: { _ in }, editScheduledMessagesTime: { _ in }, performTextSelectionAction: { _, _, _, _ in @@ -165,7 +165,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in }, openWebView: { _, _, _, _ in - }, activateAdAction: { _ in + }, activateAdAction: { _, _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { @@ -175,6 +175,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, openRecommendedChannelContextMenu: { _, _, _ in }, openGroupBoostInfo: { _, _ in }, openStickerEditor: { + }, openPhoneContextMenu: { _ in + }, openAgeRestrictedMessageMedia: { _, _ in + }, playMessageEffect: { _ in + }, editMessageFactCheck: { _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { @@ -220,7 +224,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu self.isGlobalSearch = false } - self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: .tag(tagMask), source: source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), messageTransitionNode: { return nil }) + self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: .tag(tagMask), source: source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil }) self.historyNode.clipsToBounds = true super.init() @@ -562,7 +566,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu } let chatLocationContextHolder = Atomic(value: nil) - let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: .tag(tagMask), source: .default, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), messageTransitionNode: { return nil }) + let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: .tag(tagMask), source: .default, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil }) historyNode.clipsToBounds = true historyNode.preloadPages = true historyNode.stackFromBottom = true diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 488d4e27490..4f00441a9ec 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -65,6 +65,9 @@ import TelegramNotices import BotSettingsScreen import CameraScreen import BirthdayPickerScreen +import StarsTransactionsScreen +import StarsPurchaseScreen +import StarsTransferScreen import NGCore import NGData @@ -104,6 +107,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { public let applicationBindings: TelegramApplicationBindings public let sharedContainerPath: String public let basePath: String + public let networkArguments: NetworkInitializationArguments public let accountManager: AccountManager public let appLockContext: AppLockContext public var notificationController: NotificationContainerController? { @@ -241,7 +245,6 @@ public final class SharedAccountContextImpl: SharedAccountContext { private var widgetDataContext: WidgetDataContext? // MARK: Nicegram DB Changes, additional properties - private let networkArguments: NetworkInitializationArguments private let encryptionParameters: ValueBoxEncryptionParameters private let rootPath: String @@ -269,6 +272,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.applicationBindings = applicationBindings self.sharedContainerPath = sharedContainerPath self.basePath = basePath + self.networkArguments = networkArguments self.accountManager = accountManager self.navigateToChatImpl = navigateToChat self.displayUpgradeProgress = displayUpgradeProgress @@ -280,7 +284,6 @@ public final class SharedAccountContextImpl: SharedAccountContext { return fetchCachedSharedResourceRepresentation(accountManager: accountManager, resource: resource, representation: representation) } // MARK: Nicegram DB Changes - self.networkArguments = networkArguments self.encryptionParameters = encryptionParameters self.rootPath = rootPath @@ -1659,8 +1662,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { navigateToForumChannelImpl(context: context, peerId: peerId, navigationController: navigationController) } - public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal { - return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: activateInput, keepStack: keepStack) + public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack) -> Signal { + return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: activateInput, scrollToEndIfExists: scrollToEndIfExists, keepStack: keepStack) } public func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal { @@ -1727,12 +1730,12 @@ public final class SharedAccountContextImpl: SharedAccountContext { return resolveUrlImpl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth) } - public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?, progress: Promise?, completion: (() -> Void)?) { - openResolvedUrlImpl(resolvedUrl, context: context, urlContext: urlContext, navigationController: navigationController, forceExternal: forceExternal, openPeer: openPeer, sendFile: sendFile, sendSticker: sendSticker, requestMessageActionUrlAuth: requestMessageActionUrlAuth, joinVoiceChat: joinVoiceChat, present: present, dismissInput: dismissInput, contentContext: contentContext, progress: progress, completion: completion) + public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?, progress: Promise?, completion: (() -> Void)?) { + openResolvedUrlImpl(resolvedUrl, context: context, urlContext: urlContext, navigationController: navigationController, forceExternal: forceExternal, openPeer: openPeer, sendFile: sendFile, sendSticker: sendSticker, sendEmoji: sendEmoji, requestMessageActionUrlAuth: requestMessageActionUrlAuth, joinVoiceChat: joinVoiceChat, present: present, dismissInput: dismissInput, contentContext: contentContext, progress: progress, completion: completion) } - public func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { - return deviceContactInfoController(context: context, subject: subject, completed: completed, cancelled: cancelled) + public func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { + return deviceContactInfoController(context: context, environment: environment, subject: subject, completed: completed, cancelled: cancelled) } public func makePeersNearbyController(context: AccountContext) -> ViewController { @@ -1766,6 +1769,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { controllerInteraction: controllerInteraction as! ChatControllerInteraction, selectedMessages: selectedMessages, mode: mode, + isChatPreview: false, messageTransitionNode: { return nil } ) } @@ -1838,7 +1842,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { tapMessage?(message) }, clickThroughMessage: { clickThroughMessage?() - }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in + }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in @@ -1859,9 +1863,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, requestSelectMessagePollOptions: { _, _ in }, requestOpenMessagePollResults: { _, _ in }, openAppStorePage: { - }, displayMessageTooltip: { _, _, _, _ in + }, displayMessageTooltip: { _, _, _, _, _ in }, seekToTimecode: { _, _, _ in - }, scheduleCurrentMessage: { + }, scheduleCurrentMessage: { _ in }, sendScheduledMessagesNow: { _ in }, editScheduledMessagesTime: { _ in }, performTextSelectionAction: { _, _, _, _ in @@ -1892,7 +1896,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in }, openWebView: { _, _, _, _ in - }, activateAdAction: { _ in + }, activateAdAction: { _, _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { @@ -1902,6 +1906,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, openRecommendedChannelContextMenu: { _, _, _ in }, openGroupBoostInfo: { _, _ in }, openStickerEditor: { + }, openPhoneContextMenu: { _ in + }, openAgeRestrictedMessageMedia: { _, _ in + }, playMessageEffect: { _ in + }, editMessageFactCheck: { _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { @@ -1924,7 +1932,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { chatLocation = .peer(id: messages.first!.id.peerId) } - return ChatMessageItemImpl(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: isPreview), context: context, chatLocation: chatLocation, associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: false, subject: nil, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus, availableReactions: availableReactions, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: accountPeer.flatMap(EnginePeer.init), forceInlineReactions: true, isStandalone: isStandalone), controllerInteraction: controllerInteraction, content: content, disableDate: true, additionalContent: nil) + return ChatMessageItemImpl(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: isPreview), context: context, chatLocation: chatLocation, associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: false, subject: nil, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus, availableReactions: availableReactions, availableMessageEffects: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: accountPeer.flatMap(EnginePeer.init), forceInlineReactions: true, isStandalone: isStandalone), controllerInteraction: controllerInteraction, content: content, disableDate: true, additionalContent: nil) } public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader { @@ -2012,11 +2020,12 @@ public final class SharedAccountContextImpl: SharedAccountContext { return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, send: send) } - public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? { + public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? { let inputPanelNode = LegacyMessageInputPanelNode( context: context, chatLocation: chatLocation, isScheduledMessages: isScheduledMessages, + isFile: isFile, present: present, presentInGlobalOverlay: presentInGlobalOverlay, makeEntityInputView: { @@ -2030,6 +2039,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { return HashtagSearchController(context: context, peer: peer, query: query, all: all) } + public func makeStorySearchController(context: AccountContext, query: String) -> ViewController { + return StorySearchGridScreen(context: context, searchQuery: query) + } + public func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController { return PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: isArchive ? .archive : .saved) } @@ -2613,13 +2626,22 @@ public final class SharedAccountContextImpl: SharedAccountContext { return controller } - public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController { - return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, isEditing: isEditing, expandIfNeeded: expandIfNeeded, parentNavigationController: parentNavigationController, sendSticker: sendSticker) + public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, actionPerformed: ((Bool) -> Void)?) -> ViewController { + return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, isEditing: isEditing, expandIfNeeded: expandIfNeeded, parentNavigationController: parentNavigationController, sendSticker: sendSticker, actionPerformed: { actions in + if let (_, _, action) = actions.first { + switch action { + case .add: + actionPerformed?(true) + case .remove: + actionPerformed?(false) + } + } + }) } - public func makeStickerEditorScreen(context: AccountContext, source: Any?, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController { + public func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController { let subject: Signal - let mode: MediaEditorScreen.Mode.StickerEditorMode + var mode: MediaEditorScreen.Mode.StickerEditorMode var fromCamera = false if let (file, emoji) = source as? (TelegramMediaFile, [String]) { subject = .single(.sticker(file, emoji)) @@ -2648,6 +2670,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { subject = .single(.empty(PixelDimensions(width: 1080, height: 1920))) mode = .addingToPack } + if intro { + mode = .businessIntro + } let editorController = MediaEditorScreen( context: context, mode: .stickerEditor(mode: mode), @@ -2724,6 +2749,26 @@ 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: Int64?, completion: @escaping (Int64) -> Void) -> ViewController { + return StarsPurchaseScreen(context: context, starsContext: starsContext, options: options, peerId: peerId, requiredStars: requiredStars, modal: true, completion: completion) + } + + public func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController { + return StarsTransferScreen(context: context, starsContext: starsContext, invoice: invoice, source: source, inputData: inputData, completion: completion) + } + + public func makeStarsTransactionScreen(context: AccountContext, transaction: StarsContext.State.Transaction) -> ViewController { + return StarsTransactionScreen(context: context, subject: .transaction(transaction), action: {}) + } + + public func makeStarsReceiptScreen(context: AccountContext, receipt: BotPaymentReceipt) -> ViewController { + return StarsTransactionScreen(context: context, subject: .receipt(receipt), action: {}) + } } private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { @@ -2747,7 +2792,8 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation var callMessages: [Message] = [] var hintGroupInCommon: PeerId? var forumTopicThread: ChatReplyThreadMessage? - + var isMyProfile = false + switch mode { case let .nearbyPeer(distance): nearbyPeerDistance = distance @@ -2761,10 +2807,12 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation reactionSourceMessageId = messageId case let .forumTopic(thread): forumTopicThread = thread + case .myProfile: + isMyProfile = true default: break } - return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread) + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread) } else if peer is TelegramSecretChat { return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: []) } diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 0a78bbd80b2..38bd5ea5728 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -295,6 +295,20 @@ public final class TelegramRootController: NavigationController, TelegramRootCon ) } } + + tabBarController.willSelect = { [weak self] index in + guard let self, let rootTabController else { + return + } + + let assistantIndex = rootTabController.controllers.firstIndex { + $0 === assistantController + } + + if index == assistantIndex { + AssistantUITgHelper.assistantSource = .tabBar + } + } } // diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index 6218df5b100..3dbfd4f567b 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -50,8 +50,10 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n default: break } - }, sendFile: nil, + }, + sendFile: nil, sendSticker: nil, + sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: presentImpl, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) @@ -80,7 +82,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n } case let .replyThread(messageId): if let navigationController = controller.navigationController as? NavigationController { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).start() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).start() } case let .stickerPack(name, _): let packReference: StickerPackReference = .name(name) @@ -95,7 +97,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n if let navigationController = controller.navigationController as? NavigationController { openResolvedUrlImpl(result, context: context, urlContext: peerId.flatMap { .chat(peerId: $0, message: nil, updatedPresentationData: nil) } ?? .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigateToPeer in openResolvedPeerImpl(peer, navigateToPeer) - }, sendFile: nil, sendSticker: nil, joinVoiceChat: nil, present: { c, a in }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, joinVoiceChat: nil, present: { c, a in }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) } default: break diff --git a/submodules/TelegramUIPreferences/BUILD b/submodules/TelegramUIPreferences/BUILD index 5a106b25ca9..f0ac8d5218c 100644 --- a/submodules/TelegramUIPreferences/BUILD +++ b/submodules/TelegramUIPreferences/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift index c734108683c..cba2c06b8c0 100644 --- a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift +++ b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift @@ -103,6 +103,7 @@ private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 { case localThemes = 3 case storyDrafts = 4 case storySources = 5 + case hashtagSearchRecentQueries = 6 } public struct ApplicationSpecificOrderedItemListCollectionId { @@ -112,4 +113,5 @@ public struct ApplicationSpecificOrderedItemListCollectionId { public static let localThemes = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.localThemes.rawValue) public static let storyDrafts = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.storyDrafts.rawValue) public static let storySources = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.storySources.rawValue) + public static let hashtagSearchRecentQueries = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.hashtagSearchRecentQueries.rawValue) } diff --git a/submodules/TelegramUniversalVideoContent/BUILD b/submodules/TelegramUniversalVideoContent/BUILD index 78bb6c366e2..eaa02aa5591 100644 --- a/submodules/TelegramUniversalVideoContent/BUILD +++ b/submodules/TelegramUniversalVideoContent/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TelegramUpdateUI/BUILD b/submodules/TelegramUpdateUI/BUILD index 2a82a25c16d..566abb3af38 100644 --- a/submodules/TelegramUpdateUI/BUILD +++ b/submodules/TelegramUpdateUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramVoip/BUILD b/submodules/TelegramVoip/BUILD index ad9406ad934..f60bbab91f5 100644 --- a/submodules/TelegramVoip/BUILD +++ b/submodules/TelegramVoip/BUILD @@ -9,7 +9,7 @@ swift_library( "Sources/macOS/**/*", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index db7467dd217..da23074d610 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -958,9 +958,20 @@ public final class OngoingCallContext { directConnection = nil } - #if DEBUG - //var customParameters = customParameters - //customParameters["v9_reflector_shortcircuit"] = true as NSNumber + #if DEBUG && false + var customParameters = customParameters + if let initialCustomParameters = try? JSONSerialization.jsonObject(with: (customParameters ?? "{}").data(using: .utf8)!) as? [String: Any] { + var customParametersValue: [String: Any] + customParametersValue = initialCustomParameters + customParametersValue["network_standalone_reflectors"] = true as NSNumber + customParametersValue["network_use_mtproto"] = true as NSNumber + customParametersValue["network_skip_initial_ping"] = true as NSNumber + customParameters = String(data: try! JSONSerialization.data(withJSONObject: customParametersValue), encoding: .utf8)! + + if let reflector = filteredConnections.first(where: { $0.username == "reflector" && $0.reflectorId == 1 }) { + filteredConnections = [reflector] + } + } #endif let context = OngoingCallThreadLocalContextWebrtc( diff --git a/submodules/TemporaryCachedPeerDataManager/BUILD b/submodules/TemporaryCachedPeerDataManager/BUILD index ecb048b8a6f..b7ef69323b1 100644 --- a/submodules/TemporaryCachedPeerDataManager/BUILD +++ b/submodules/TemporaryCachedPeerDataManager/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramStringFormatting:TelegramStringFormatting", diff --git a/submodules/TextFormat/BUILD b/submodules/TextFormat/BUILD index 08d1dba76c2..bbf3066ce6d 100644 --- a/submodules/TextFormat/BUILD +++ b/submodules/TextFormat/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index b4036b80078..52dc6e72924 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -20,8 +20,9 @@ public struct ChatTextInputAttributes { public static let spoiler = NSAttributedString.Key(rawValue: "Attribute__Spoiler") public static let customEmoji = NSAttributedString.Key(rawValue: "Attribute__CustomEmoji") public static let block = NSAttributedString.Key(rawValue: "Attribute__Blockquote") + public static let collapsedBlock = NSAttributedString.Key(rawValue: "Attribute__CollapsedBlockquote") - public static let allAttributes = [ChatTextInputAttributes.bold, ChatTextInputAttributes.italic, ChatTextInputAttributes.monospace, ChatTextInputAttributes.strikethrough, ChatTextInputAttributes.underline, ChatTextInputAttributes.textMention, ChatTextInputAttributes.textUrl, ChatTextInputAttributes.spoiler, ChatTextInputAttributes.customEmoji, ChatTextInputAttributes.block] + public static let allAttributes = [ChatTextInputAttributes.bold, ChatTextInputAttributes.italic, ChatTextInputAttributes.monospace, ChatTextInputAttributes.strikethrough, ChatTextInputAttributes.underline, ChatTextInputAttributes.textMention, ChatTextInputAttributes.textUrl, ChatTextInputAttributes.spoiler, ChatTextInputAttributes.customEmoji, ChatTextInputAttributes.block, ChatTextInputAttributes.collapsedBlock] } public let originalTextAttributeKey = NSAttributedString.Key(rawValue: "Attribute__OriginalText") @@ -35,6 +36,67 @@ public final class OriginalTextAttribute: NSObject { } } +public final class ChatInputTextCollapsedQuoteAttributes: Equatable { + public let context: AnyObject + public let fontSize: CGFloat + public let textColor: UIColor + public let accentTextColor: UIColor + + public init( + context: AnyObject, + fontSize: CGFloat, + textColor: UIColor, + accentTextColor: UIColor + ) { + self.context = context + self.fontSize = fontSize + self.textColor = textColor + self.accentTextColor = accentTextColor + } + + public static func ==(lhs: ChatInputTextCollapsedQuoteAttributes, rhs: ChatInputTextCollapsedQuoteAttributes) -> Bool { + if lhs === rhs { + return true + } + if lhs.fontSize != rhs.fontSize { + return false + } + if !lhs.textColor.isEqual(rhs.textColor) { + return false + } + if !lhs.accentTextColor.isEqual(rhs.accentTextColor) { + return false + } + + return true + } +} + +public protocol ChatInputTextCollapsedQuoteAttachment: NSTextAttachment { + var text: NSAttributedString { get } +} + +public func expandedInputStateAttributedString(_ text: NSAttributedString) -> NSAttributedString { + let sourceString = NSMutableAttributedString(attributedString: text) + while true { + var found = false + let fullRange = NSRange(sourceString.string.startIndex ..< sourceString.string.endIndex, in: sourceString.string) + sourceString.enumerateAttribute(ChatTextInputAttributes.collapsedBlock, in: fullRange, options: [.longestEffectiveRangeNotRequired], using: { value, range, stop in + if let value = value as? NSAttributedString { + let updatedBlockString = NSMutableAttributedString(attributedString: value) + updatedBlockString.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: true), range: NSRange(location: 0, length: updatedBlockString.length)) + sourceString.replaceCharacters(in: range, with: updatedBlockString) + stop.pointee = true + found = true + } + }) + if !found { + break + } + } + return sourceString +} + public func stateAttributedStringForText(_ text: NSAttributedString) -> NSAttributedString { let sourceString = NSMutableAttributedString(attributedString: text) while true { @@ -45,6 +107,10 @@ public func stateAttributedStringForText(_ text: NSAttributedString) -> NSAttrib sourceString.replaceCharacters(in: range, with: NSAttributedString(string: value.text, attributes: [ChatTextInputAttributes.customEmoji: value.emoji])) stop.pointee = true found = true + } else if let value = value as? ChatInputTextCollapsedQuoteAttachment { + sourceString.replaceCharacters(in: range, with: NSAttributedString(string: " ", attributes: [ChatTextInputAttributes.collapsedBlock: value.text])) + stop.pointee = true + found = true } }) if !found { @@ -57,7 +123,15 @@ public func stateAttributedStringForText(_ text: NSAttributedString) -> NSAttrib sourceString.enumerateAttributes(in: fullRange, options: [], using: { attributes, range, _ in for (key, value) in attributes { - if ChatTextInputAttributes.allAttributes.contains(key) || key == NSAttributedString.Key.attachment { + var matchAttribute = false + if ChatTextInputAttributes.allAttributes.contains(key) { + matchAttribute = true + } else if key == NSAttributedString.Key.attachment { + if value is EmojiTextAttachment { + matchAttribute = true + } + } + if matchAttribute { result.addAttribute(key, value: value, range: range) } } @@ -102,7 +176,52 @@ public struct ChatTextFontAttributes: OptionSet, Hashable, Sequence { } } -public func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor, writingDirection: NSWritingDirection?, spoilersRevealed: Bool, availableEmojis: Set, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?) -> NSAttributedString { +public func textAttributedStringForStateText(context: AnyObject, stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor, writingDirection: NSWritingDirection?, spoilersRevealed: Bool, availableEmojis: Set, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, makeCollapsedQuoteAttachment: ((NSAttributedString, ChatInputTextCollapsedQuoteAttributes) -> ChatInputTextCollapsedQuoteAttachment)?) -> NSAttributedString { + let quoteAttributes = ChatInputTextCollapsedQuoteAttributes( + context: context, + fontSize: round(fontSize * 0.8235294117647058), + textColor: textColor, + accentTextColor: accentTextColor + ) + + let stateText = NSMutableAttributedString(attributedString: stateText) + + /*while true { + var found = false + stateText.enumerateAttribute(ChatTextInputAttributes.block, in: NSRange(location: 0, length: stateText.length), options: [.longestEffectiveRangeNotRequired], using: { value, range, stop in + if let value = value as? ChatTextInputTextQuoteAttribute { + if value.isCollapsed, let makeCollapsedQuoteAttachment { + found = true + stop.pointee = true + + let quoteText = stateText.attributedSubstring(from: range) + stateText.replaceCharacters(in: range, with: "") + stateText.insert(NSAttributedString(attachment: makeCollapsedQuoteAttachment(quoteText, quoteAttributes)), at: range.lowerBound) + } + } + }) + if !found { + break + } + }*/ + while true { + var found = false + stateText.enumerateAttribute(ChatTextInputAttributes.collapsedBlock, in: NSRange(location: 0, length: stateText.length), options: [.longestEffectiveRangeNotRequired], using: { value, range, stop in + if let value = value as? NSAttributedString { + if let makeCollapsedQuoteAttachment { + found = true + stop.pointee = true + + stateText.replaceCharacters(in: range, with: "") + stateText.insert(NSAttributedString(attachment: makeCollapsedQuoteAttachment(value, quoteAttributes)), at: range.lowerBound) + } + } + }) + if !found { + break + } + } + let result = NSMutableAttributedString(string: stateText.string) let fullRange = NSRange(location: 0, length: result.length) @@ -157,6 +276,8 @@ public func textAttributedStringForStateText(_ stateText: NSAttributedString, fo fontAttributes.insert(.monospace) } result.addAttribute(key, value: value, range: range) + } else if key == .attachment, value is ChatInputTextCollapsedQuoteAttachment { + result.addAttribute(key, value: value, range: range) } } @@ -249,9 +370,11 @@ public final class ChatTextInputTextQuoteAttribute: NSObject { } public let kind: Kind + public let isCollapsed: Bool - public init(kind: Kind) { + public init(kind: Kind, isCollapsed: Bool) { self.kind = kind + self.isCollapsed = isCollapsed super.init() } @@ -264,6 +387,9 @@ public final class ChatTextInputTextQuoteAttribute: NSObject { if self.kind != other.kind { return false } + if self.isCollapsed != other.isCollapsed { + return false + } return true } @@ -281,6 +407,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable { public enum Custom: Codable { case topic(id: Int64, info: EngineMessageHistoryThread.Info) case nameColors([UInt32]) + case stars } public let interactivelySelectedFromPackId: ItemCollectionId? @@ -646,16 +773,16 @@ private func refreshBlockQuotes(text: NSString, initialAttributedText: NSAttribu if !quoteRangesEqual(quoteRanges, initialQuoteRanges) { attributedText.removeAttribute(ChatTextInputAttributes.block, range: fullRange) for (range, attribute) in quoteRanges { - attributedText.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: attribute.kind), range: range) + attributedText.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: attribute.kind, isCollapsed: attribute.isCollapsed), range: range) } } } -public func refreshChatTextInputAttributes(_ textView: UITextView, theme: PresentationTheme, baseFontSize: CGFloat, spoilersRevealed: Bool, availableEmojis: Set, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?) { - refreshChatTextInputAttributes(textView: textView, primaryTextColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, baseFontSize: baseFontSize, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider) +public func refreshChatTextInputAttributes(context: AnyObject, textView: UITextView, theme: PresentationTheme, baseFontSize: CGFloat, spoilersRevealed: Bool, availableEmojis: Set, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, makeCollapsedQuoteAttachment: ((NSAttributedString, ChatInputTextCollapsedQuoteAttributes) -> ChatInputTextCollapsedQuoteAttachment)?) { + refreshChatTextInputAttributes(context: context, textView: textView, primaryTextColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, baseFontSize: baseFontSize, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider, makeCollapsedQuoteAttachment: makeCollapsedQuoteAttachment) } -public func refreshChatTextInputAttributes(textView: UITextView, primaryTextColor: UIColor, accentTextColor: UIColor, baseFontSize: CGFloat, spoilersRevealed: Bool, availableEmojis: Set, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?) { +public func refreshChatTextInputAttributes(context: AnyObject, textView: UITextView, primaryTextColor: UIColor, accentTextColor: UIColor, baseFontSize: CGFloat, spoilersRevealed: Bool, availableEmojis: Set, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, makeCollapsedQuoteAttachment: ((NSAttributedString, ChatInputTextCollapsedQuoteAttributes) -> ChatInputTextCollapsedQuoteAttachment)?) { guard let initialAttributedText = textView.attributedText, initialAttributedText.length != 0 else { return } @@ -672,21 +799,21 @@ public func refreshChatTextInputAttributes(textView: UITextView, primaryTextColo var attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(initialAttributedText)) refreshTextMentions(text: text, initialAttributedText: initialAttributedText, attributedText: attributedText, fullRange: fullRange) - var resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: primaryTextColor, accentTextColor: accentTextColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider) + var resultAttributedText = textAttributedStringForStateText(context: context, stateText: attributedText, fontSize: baseFontSize, textColor: primaryTextColor, accentTextColor: accentTextColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider, makeCollapsedQuoteAttachment: makeCollapsedQuoteAttachment) text = resultAttributedText.string as NSString fullRange = NSRange(location: 0, length: text.length) attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(resultAttributedText)) refreshTextUrls(text: text, initialAttributedText: resultAttributedText, attributedText: attributedText, fullRange: fullRange) - resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: primaryTextColor, accentTextColor: accentTextColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider) + resultAttributedText = textAttributedStringForStateText(context: context, stateText: attributedText, fontSize: baseFontSize, textColor: primaryTextColor, accentTextColor: accentTextColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider, makeCollapsedQuoteAttachment: makeCollapsedQuoteAttachment) text = resultAttributedText.string as NSString fullRange = NSRange(location: 0, length: text.length) attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(resultAttributedText)) refreshBlockQuotes(text: text, initialAttributedText: resultAttributedText, attributedText: attributedText, fullRange: fullRange) - resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: primaryTextColor, accentTextColor: accentTextColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider) + resultAttributedText = textAttributedStringForStateText(context: context, stateText: attributedText, fontSize: baseFontSize, textColor: primaryTextColor, accentTextColor: accentTextColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider, makeCollapsedQuoteAttachment: makeCollapsedQuoteAttachment) if !resultAttributedText.isEqual(to: initialAttributedText) { fullRange = NSRange(location: 0, length: textView.textStorage.length) @@ -745,13 +872,15 @@ public func refreshChatTextInputAttributes(textView: UITextView, primaryTextColo textView.textStorage.addAttribute(key, value: value, range: range) textView.textStorage.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.clear, range: range) } else if key == ChatTextInputAttributes.block, let value = value as? ChatTextInputTextQuoteAttribute { - switch value.kind { - case .quote: - fontAttributes.insert(.blockQuote) - case .code: - fontAttributes.insert(.monospace) + if !value.isCollapsed { + switch value.kind { + case .quote: + fontAttributes.insert(.blockQuote) + case .code: + fontAttributes.insert(.monospace) + } + textView.textStorage.addAttribute(key, value: value, range: range) } - textView.textStorage.addAttribute(key, value: value, range: range) } } @@ -794,7 +923,7 @@ public func refreshChatTextInputAttributes(textView: UITextView, primaryTextColo textView.textStorage.endEditing() } -public func refreshGenericTextInputAttributes(_ textView: UITextView, theme: PresentationTheme, baseFontSize: CGFloat, availableEmojis: Set, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, spoilersRevealed: Bool = false) { +public func refreshGenericTextInputAttributes(context: AnyObject, textView: UITextView, theme: PresentationTheme, baseFontSize: CGFloat, availableEmojis: Set, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, makeCollapsedQuoteAttachment: ((NSAttributedString, ChatInputTextCollapsedQuoteAttributes) -> ChatInputTextCollapsedQuoteAttachment)?, spoilersRevealed: Bool = false) { guard let initialAttributedText = textView.attributedText, initialAttributedText.length != 0 else { return } @@ -807,14 +936,14 @@ public func refreshGenericTextInputAttributes(_ textView: UITextView, theme: Pre var text: NSString = initialAttributedText.string as NSString var fullRange = NSRange(location: 0, length: initialAttributedText.length) var attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(initialAttributedText)) - var resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider) + var resultAttributedText = textAttributedStringForStateText(context: context, stateText: attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider, makeCollapsedQuoteAttachment: makeCollapsedQuoteAttachment) text = resultAttributedText.string as NSString fullRange = NSRange(location: 0, length: initialAttributedText.length) attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(resultAttributedText)) refreshTextUrls(text: text, initialAttributedText: resultAttributedText, attributedText: attributedText, fullRange: fullRange) - resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider) + resultAttributedText = textAttributedStringForStateText(context: context, stateText: attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed, availableEmojis: availableEmojis, emojiViewProvider: emojiViewProvider, makeCollapsedQuoteAttachment: makeCollapsedQuoteAttachment) if !resultAttributedText.isEqual(to: initialAttributedText) { textView.textStorage.removeAttribute(NSAttributedString.Key.font, range: fullRange) @@ -1067,7 +1196,7 @@ public func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttribu substring = substring.substring(with: NSRange(location: 0, length: substring.length - 1)) as NSString } - result.append(NSAttributedString(string: substring as String, attributes: [ChatTextInputAttributes.block: ChatTextInputTextQuoteAttribute(kind: .code(language: language))])) + result.append(NSAttributedString(string: substring as String, attributes: [ChatTextInputAttributes.block: ChatTextInputTextQuoteAttribute(kind: .code(language: language), isCollapsed: false)])) offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 1).length, text.count), 6)) } } diff --git a/submodules/TextFormat/Sources/GenerateTextEntities.swift b/submodules/TextFormat/Sources/GenerateTextEntities.swift index bb7f39b3a63..4079103b324 100644 --- a/submodules/TextFormat/Sources/GenerateTextEntities.swift +++ b/submodules/TextFormat/Sources/GenerateTextEntities.swift @@ -171,7 +171,7 @@ public func generateChatInputTextEntities(_ text: NSAttributedString, maxAnimate } else if key == ChatTextInputAttributes.block, let value = value as? ChatTextInputTextQuoteAttribute { switch value.kind { case .quote: - entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .BlockQuote)) + entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .BlockQuote(isCollapsed: value.isCollapsed))) case let .code(language): entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .Pre(language: language))) } diff --git a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift index 6cf40ed8185..093f02443c2 100644 --- a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift +++ b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift @@ -49,13 +49,32 @@ public func chatInputStateStringWithAppliedEntities(_ text: String, entities: [M case let .CustomEmoji(_, fileId): string.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil), range: range) case let .Pre(language): - string.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: language)), range: range) - case .BlockQuote: - string.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote), range: range) + string.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: language), isCollapsed: false), range: range) + case let .BlockQuote(isCollapsed): + string.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: isCollapsed), range: range) default: break } } + + while true { + var found = false + string.enumerateAttribute(ChatTextInputAttributes.block, in: NSRange(location: 0, length: string.length), using: { value, range, stop in + if let value = value as? ChatTextInputTextQuoteAttribute, value.isCollapsed { + found = true + let blockString = string.attributedSubstring(from: range) + string.replaceCharacters(in: range, with: "") + string.insert(NSAttributedString(string: " ", attributes: [ + ChatTextInputAttributes.collapsedBlock: blockString + ]), at: range.lowerBound) + stop.pointee = true + } + }) + if !found { + break + } + } + return string } @@ -219,12 +238,12 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti if let language, !language.isEmpty { title = NSAttributedString(string: language.capitalized, font: boldFont.withSize(round(boldFont.pointSize * 0.8235294117647058)), textColor: codeBlockTitleColor) } - string.addAttribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), value: TextNodeBlockQuoteData(kind: .code(language: language), title: title, color: codeBlockAccentColor, secondaryColor: nil, tertiaryColor: nil, backgroundColor: codeBlockBackgroundColor), range: range) + string.addAttribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), value: TextNodeBlockQuoteData(kind: .code(language: language), title: title, color: codeBlockAccentColor, secondaryColor: nil, tertiaryColor: nil, backgroundColor: codeBlockBackgroundColor, isCollapsible: false), range: range) } - case .BlockQuote: + case let .BlockQuote(isCollapsed): addFontAttributes(range, .blockQuote) - string.addAttribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), value: TextNodeBlockQuoteData(kind: .quote, title: nil, color: baseQuoteTintColor, secondaryColor: baseQuoteSecondaryTintColor, tertiaryColor: baseQuoteTertiaryTintColor, backgroundColor: baseQuoteTintColor.withMultipliedAlpha(0.1)), range: range) + string.addAttribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), value: TextNodeBlockQuoteData(kind: .quote, title: nil, color: baseQuoteTintColor, secondaryColor: baseQuoteSecondaryTintColor, tertiaryColor: baseQuoteTertiaryTintColor, backgroundColor: baseQuoteTintColor.withMultipliedAlpha(0.1), isCollapsible: isCollapsed), range: range) case .BankCard: string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) if underlineLinks && underlineAllLinks { diff --git a/submodules/TextInputMenu/BUILD b/submodules/TextInputMenu/BUILD index 6ff751ff57c..eb6018b8ce9 100644 --- a/submodules/TextInputMenu/BUILD +++ b/submodules/TextInputMenu/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TextSelectionNode/BUILD b/submodules/TextSelectionNode/BUILD index 2bb977e11c4..e5f0c97a119 100644 --- a/submodules/TextSelectionNode/BUILD +++ b/submodules/TextSelectionNode/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift index f212669410e..f29ff1ef8a1 100644 --- a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift +++ b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift @@ -218,7 +218,7 @@ public enum TextSelectionAction: Equatable { public final class TextSelectionNode: ASDisplayNode { private let theme: TextSelectionTheme private let strings: PresentationStrings - private let textNode: TextNode + private let textNode: TextNodeProtocol private let updateIsActive: (Bool) -> Void public var canBeginSelection: (CGPoint) -> Bool = { _ in true } public var updateRange: ((NSRange?) -> Void)? @@ -252,7 +252,7 @@ public final class TextSelectionNode: ASDisplayNode { private weak var contextMenu: ContextMenuController? - public init(theme: TextSelectionTheme, strings: PresentationStrings, textNode: TextNode, updateIsActive: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, rootNode: @escaping () -> ASDisplayNode?, externalKnobSurface: UIView? = nil, performAction: @escaping (NSAttributedString, TextSelectionAction) -> Void) { + public init(theme: TextSelectionTheme, strings: PresentationStrings, textNode: TextNodeProtocol, updateIsActive: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, rootNode: @escaping () -> ASDisplayNode?, externalKnobSurface: UIView? = nil, performAction: @escaping (NSAttributedString, TextSelectionAction) -> Void) { self.theme = theme self.strings = strings self.textNode = textNode @@ -302,7 +302,7 @@ public final class TextSelectionNode: ASDisplayNode { return self?.knobAtPoint(point) } recognizer.moveKnob = { [weak self] knob, point in - guard let strongSelf = self, let cachedLayout = strongSelf.textNode.cachedLayout, let _ = cachedLayout.attributedString, let currentRange = strongSelf.currentRange else { + guard let strongSelf = self, let currentRange = strongSelf.currentRange else { return } @@ -335,7 +335,7 @@ public final class TextSelectionNode: ASDisplayNode { strongSelf.displayMenu() } recognizer.beginSelection = { [weak self] point in - guard let strongSelf = self, let cachedLayout = strongSelf.textNode.cachedLayout, let attributedString = cachedLayout.attributedString else { + guard let strongSelf = self, let attributedString = strongSelf.textNode.currentText else { return } @@ -398,7 +398,7 @@ public final class TextSelectionNode: ASDisplayNode { } public func pretendInitiateSelection() { - guard let cachedLayout = self.textNode.cachedLayout, let attributedString = cachedLayout.attributedString else { + guard let attributedString = self.textNode.currentText else { return } @@ -432,7 +432,7 @@ public final class TextSelectionNode: ASDisplayNode { } public func pretendExtendSelection(to index: Int) { - guard let cachedLayout = self.textNode.cachedLayout, let _ = cachedLayout.attributedString, let endRangeRect = cachedLayout.rangeRects(in: NSRange(location: index, length: 1))?.rects.first else { + guard let endRangeRect = self.textNode.textRangeRects(in: NSRange(location: index, length: 1))?.rects.first else { return } let startPoint = self.rightKnob.frame.center @@ -449,7 +449,7 @@ public final class TextSelectionNode: ASDisplayNode { } public func setSelection(range: NSRange, displayMenu: Bool) { - guard let cachedLayout = self.textNode.cachedLayout, let attributedString = cachedLayout.attributedString else { + guard let attributedString = self.textNode.currentText else { return } let range = self.convertSelectionFromOriginalText(attributedString: attributedString, range: range) @@ -561,7 +561,7 @@ public final class TextSelectionNode: ASDisplayNode { } public func getSelection() -> NSRange? { - guard let currentRange = self.currentRange, let cachedLayout = self.textNode.cachedLayout, let attributedString = cachedLayout.attributedString else { + guard let currentRange = self.currentRange, let attributedString = self.textNode.currentText else { return nil } let range = NSRange(location: min(currentRange.0, currentRange.1), length: max(currentRange.0, currentRange.1) - min(currentRange.0, currentRange.1)) @@ -573,8 +573,24 @@ public final class TextSelectionNode: ASDisplayNode { var rects: (rects: [CGRect], start: TextRangeRectEdge, end: TextRangeRectEdge)? - if let range = range { - rects = self.textNode.rangeRects(in: range) + if let range { + if var rectsValue = self.textNode.textRangeRects(in: range) { + var rectList = rectsValue.rects + if rectList.count > 1 { + for i in 0 ..< rectList.count - 1 { + let deltaY = rectList[i + 1].minY - rectList[i].maxY + if deltaY > 0.0 && deltaY <= 4.0 { + rectList[i].size.height += deltaY * 0.5 + rectList[i + 1].size.height += deltaY * 0.5 + rectList[i + 1].origin.y -= deltaY * 0.5 + } + } + } + rectsValue.rects = rectList + rects = rectsValue + } else { + rects = nil + } } self.currentRects = rects?.rects @@ -667,7 +683,7 @@ public final class TextSelectionNode: ASDisplayNode { } private func displayMenu() { - guard let currentRects = self.currentRects, !currentRects.isEmpty, let currentRange = self.currentRange, let cachedLayout = self.textNode.cachedLayout, let attributedString = cachedLayout.attributedString else { + guard let currentRects = self.currentRects, !currentRects.isEmpty, let currentRange = self.currentRange, let attributedString = self.textNode.currentText else { return } let range = NSRange(location: min(currentRange.0, currentRange.1), length: max(currentRange.0, currentRange.1) - min(currentRange.0, currentRange.1)) diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 814089b5931..8721352f452 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 814089b59311c0d59e22eb72e6cd070fbfbeb1d7 +Subproject commit 8721352f452128adec41c254b8407a4cb18cbbeb diff --git a/submodules/TinyThumbnail/BUILD b/submodules/TinyThumbnail/BUILD index cbedb8d1e95..909915d9b95 100644 --- a/submodules/TinyThumbnail/BUILD +++ b/submodules/TinyThumbnail/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/TooltipUI/BUILD b/submodules/TooltipUI/BUILD index 551a211a51d..10c969e4492 100644 --- a/submodules/TooltipUI/BUILD +++ b/submodules/TooltipUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", diff --git a/submodules/TouchDownGesture/BUILD b/submodules/TouchDownGesture/BUILD index 13657370f23..3359ea93e54 100644 --- a/submodules/TouchDownGesture/BUILD +++ b/submodules/TouchDownGesture/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/TranslateUI/BUILD b/submodules/TranslateUI/BUILD index e96f5e190cd..b73e8d872f7 100644 --- a/submodules/TranslateUI/BUILD +++ b/submodules/TranslateUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", @@ -27,6 +27,7 @@ swift_library( "//submodules/ComponentFlow:ComponentFlow", "//submodules/Components/ViewControllerComponent:ViewControllerComponent", "//submodules/Components/MultilineTextComponent:MultilineTextComponent", + "//submodules/Components/MultilineTextWithEntitiesComponent:MultilineTextWithEntitiesComponent", "//submodules/Components/BundleIconComponent:BundleIconComponent", "//submodules/UndoUI:UndoUI", "//submodules/ActivityIndicator:ActivityIndicator", diff --git a/submodules/TranslateUI/Sources/ChatTranslation.swift b/submodules/TranslateUI/Sources/ChatTranslation.swift index 606b23013be..1d0d9e70204 100644 --- a/submodules/TranslateUI/Sources/ChatTranslation.swift +++ b/submodules/TranslateUI/Sources/ChatTranslation.swift @@ -109,8 +109,8 @@ public func updateChatTranslationStateInteractively(engine: TelegramEngine, peer @available(iOS 12.0, *) private let languageRecognizer = NLLanguageRecognizer() -public func translateMessageIds(context: AccountContext, messageIds: [EngineMessage.Id], toLang: String) -> Signal { - return context.account.postbox.transaction { transaction -> Signal in +public func translateMessageIds(context: AccountContext, messageIds: [EngineMessage.Id], toLang: String) -> Signal { + return context.account.postbox.transaction { transaction -> Signal in var messageIdsToTranslate: [EngineMessage.Id] = [] var messageIdsSet = Set() for messageId in messageIds { @@ -126,13 +126,22 @@ public func translateMessageIds(context: AccountContext, messageIds: [EngineMess } } } - if !message.text.isEmpty && message.author?.id != context.account.peerId { - if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == toLang { - } else { - if !messageIdsSet.contains(messageId) { - messageIdsToTranslate.append(messageId) - messageIdsSet.insert(messageId) - } + guard message.author?.id != context.account.peerId else { + continue + } + if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == toLang { + continue + } + + if !message.text.isEmpty { + if !messageIdsSet.contains(messageId) { + messageIdsToTranslate.append(messageId) + messageIdsSet.insert(messageId) + } + } else if let _ = message.media.first(where: { $0 is TelegramMediaPoll }) { + if !messageIdsSet.contains(messageId) { + messageIdsToTranslate.append(messageId) + messageIdsSet.insert(messageId) } } } else { @@ -143,7 +152,7 @@ public func translateMessageIds(context: AccountContext, messageIds: [EngineMess } } return context.engine.messages.translateMessages(messageIds: messageIdsToTranslate, toLang: toLang) - |> `catch` { _ -> Signal in + |> `catch` { _ -> Signal in return .complete() } } |> switchToLatest diff --git a/submodules/TranslateUI/Sources/TranslateScreen.swift b/submodules/TranslateUI/Sources/TranslateScreen.swift index 477f9d40142..1cc8c69c9d9 100644 --- a/submodules/TranslateUI/Sources/TranslateScreen.swift +++ b/submodules/TranslateUI/Sources/TranslateScreen.swift @@ -11,6 +11,7 @@ import Speak import ComponentFlow import ViewControllerComponent import MultilineTextComponent +import MultilineTextWithEntitiesComponent import BundleIconComponent import UndoUI @@ -35,15 +36,17 @@ private final class TranslateScreenComponent: CombinedComponent { let context: AccountContext let text: String + let entities: [MessageTextEntity] let fromLanguage: String? let toLanguage: String let copyTranslation: ((String) -> Void)? let changeLanguage: (String, String, @escaping (String, String) -> Void) -> Void let expand: () -> Void - init(context: AccountContext, text: String, fromLanguage: String?, toLanguage: String, copyTranslation: ((String) -> Void)?, changeLanguage: @escaping (String, String, @escaping (String, String) -> Void) -> Void, expand: @escaping () -> Void) { + init(context: AccountContext, text: String, entities: [MessageTextEntity], fromLanguage: String?, toLanguage: String, copyTranslation: ((String) -> Void)?, changeLanguage: @escaping (String, String, @escaping (String, String) -> Void) -> Void, expand: @escaping () -> Void) { self.context = context self.text = text + self.entities = entities self.fromLanguage = fromLanguage self.toLanguage = toLanguage self.copyTranslation = copyTranslation @@ -58,6 +61,9 @@ private final class TranslateScreenComponent: CombinedComponent { if lhs.text != rhs.text { return false } + if lhs.entities != rhs.entities { + return false + } if lhs.fromLanguage != rhs.fromLanguage { return false } @@ -102,7 +108,7 @@ private final class TranslateScreenComponent: CombinedComponent { guard let strongSelf = self else { return } - strongSelf.translatedText = text + strongSelf.translatedText = text?.0 strongSelf.updated(transition: .immediate) }, error: { error in @@ -127,7 +133,7 @@ private final class TranslateScreenComponent: CombinedComponent { guard let strongSelf = self else { return } - strongSelf.translatedText = text + strongSelf.translatedText = text?.0 strongSelf.updated(transition: .immediate) }, error: { error in @@ -995,7 +1001,7 @@ public class TranslateScreen: ViewController { public var wasDismissed: (() -> Void)? - public convenience init(context: AccountContext, forceTheme: PresentationTheme? = nil, text: String, canCopy: Bool, fromLanguage: String?, toLanguage: String? = nil, isExpanded: Bool = false, ignoredLanguages: [String]? = nil) { + public convenience init(context: AccountContext, forceTheme: PresentationTheme? = nil, text: String, entities: [MessageTextEntity] = [], canCopy: Bool, fromLanguage: String?, toLanguage: String? = nil, isExpanded: Bool = false, ignoredLanguages: [String]? = nil) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } var baseLanguageCode = presentationData.strings.baseLanguageCode @@ -1024,7 +1030,7 @@ public class TranslateScreen: ViewController { var copyTranslationImpl: ((String) -> Void)? var changeLanguageImpl: ((String, String, @escaping (String, String) -> Void) -> Void)? var expandImpl: (() -> Void)? - self.init(context: context, component: TranslateScreenComponent(context: context, text: text, fromLanguage: fromLanguage, toLanguage: toLanguage, copyTranslation: !canCopy ? nil : { text in + self.init(context: context, component: TranslateScreenComponent(context: context, text: text, entities: entities, fromLanguage: fromLanguage, toLanguage: toLanguage, copyTranslation: !canCopy ? nil : { text in copyTranslationImpl?(text) }, changeLanguage: { fromLang, toLang, completion in changeLanguageImpl?(fromLang, toLang, completion) diff --git a/submodules/Tuples/BUILD b/submodules/Tuples/BUILD index a1390c56ead..932761fa1f1 100644 --- a/submodules/Tuples/BUILD +++ b/submodules/Tuples/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/UndoUI/BUILD b/submodules/UndoUI/BUILD index ce70e46883b..1b33b8e165e 100644 --- a/submodules/UndoUI/BUILD +++ b/submodules/UndoUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/UrlEscaping/BUILD b/submodules/UrlEscaping/BUILD index ac14d9b3ff8..e15236c3ad6 100644 --- a/submodules/UrlEscaping/BUILD +++ b/submodules/UrlEscaping/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/UrlHandling/BUILD b/submodules/UrlHandling/BUILD index 4d6dcfb0a77..4ce8a9df300 100644 --- a/submodules/UrlHandling/BUILD +++ b/submodules/UrlHandling/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index e55b397eecc..5b9c30e7d5e 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -1204,7 +1204,11 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String var url = url let lowercasedUrl = url.lowercased() if (lowercasedUrl.hasPrefix(scheme) && (lowercasedUrl.hasSuffix(".\(basePath)") || lowercasedUrl.contains(".\(basePath)/") || lowercasedUrl.contains(".\(basePath)?"))) { - url = basePrefix + String(url[scheme.endIndex...]).replacingOccurrences(of: ".\(basePath)/", with: "").replacingOccurrences(of: ".\(basePath)", with: "") + let restUrl = String(url[scheme.endIndex...]) + if let slashRange = restUrl.range(of: "/"), let baseRange = restUrl.range(of: basePath), slashRange.lowerBound < baseRange.lowerBound { + } else { + url = basePrefix + restUrl.replacingOccurrences(of: ".\(basePath)/", with: "/").replacingOccurrences(of: ".\(basePath)", with: "") + } } if url.lowercased().hasPrefix(basePrefix) { if let internalUrl = parseInternalUrl(sharedContext: context.sharedContext, query: String(url[basePrefix.endIndex...])) { diff --git a/submodules/UrlWhitelist/BUILD b/submodules/UrlWhitelist/BUILD index c68f3d9b497..96d1827be81 100644 --- a/submodules/UrlWhitelist/BUILD +++ b/submodules/UrlWhitelist/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/Utils/RangeSet/BUILD b/submodules/Utils/RangeSet/BUILD index 6e55c966035..45905efc380 100644 --- a/submodules/Utils/RangeSet/BUILD +++ b/submodules/Utils/RangeSet/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/Utils/VolumeButtons/BUILD b/submodules/Utils/VolumeButtons/BUILD index 5abc27fbf29..8ce16f3e746 100644 --- a/submodules/Utils/VolumeButtons/BUILD +++ b/submodules/Utils/VolumeButtons/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/WallpaperBackgroundNode/BUILD b/submodules/WallpaperBackgroundNode/BUILD index ccd9dff8703..32458a1f933 100644 --- a/submodules/WallpaperBackgroundNode/BUILD +++ b/submodules/WallpaperBackgroundNode/BUILD @@ -47,7 +47,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], data = [ ":WallpaperBackgroundNodeBundle", diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index f4de1aa7756..0e69daa3c4d 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -59,6 +59,8 @@ public protocol WallpaperBubbleBackgroundNode: ASDisplayNode { func update(rect: CGRect, within containerSize: CGSize, animator: ControlledTransitionAnimator) func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) + + func reloadBindings() } public enum WallpaperDisplayMode { @@ -323,7 +325,7 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver } } -final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode { +public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode { final class BubbleBackgroundNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode { var implicitContentUpdate: Bool = true @@ -631,6 +633,9 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode gradientWallpaperNode.layer.animateSpring(from: NSValue(cgPoint: scaledOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.position", duration: duration, initialVelocity: 0.0, damping: damping, additive: true) } } + + func reloadBindings() { + } } final class BubbleBackgroundPortalNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode { @@ -679,6 +684,10 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { } + + func reloadBindings() { + self.portalView.reloadPortal() + } } private final class BubbleBackgroundNodeReference { @@ -812,7 +821,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } } - var rotation: CGFloat = 0.0 { + public var rotation: CGFloat = 0.0 { didSet { var fromValue: CGFloat = 0.0 if let value = (self.layer.value(forKeyPath: "transform.rotation.z") as? NSNumber)?.floatValue { @@ -845,7 +854,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode private static var cachedSharedPattern: (PatternKey, UIImage)? private let _isReady = ValuePromise(false, ignoreRepeated: true) - var isReady: Signal { + public var isReady: Signal { return self._isReady.get() } @@ -920,7 +929,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.dimLayer.opacity = dimAlpha } - func update(wallpaper: TelegramWallpaper, animated: Bool) { + public func update(wallpaper: TelegramWallpaper, animated: Bool) { if self.wallpaper == wallpaper { return } @@ -1074,7 +1083,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.updateDimming() } - func _internalUpdateIsSettingUpWallpaper() { + public func _internalUpdateIsSettingUpWallpaper() { self.isSettingUpWallpaper = true } @@ -1301,7 +1310,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(), size: size)) } - func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition) { + public func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.validLayout == nil self.validLayout = (size, displayMode) @@ -1357,7 +1366,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode private var isAnimating = false private var isLooping = false - func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) { + public func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) { guard !(self.isLooping && self.isAnimating) else { return } @@ -1373,7 +1382,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.outgoingBubbleGradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: {}) } - func updateIsLooping(_ isLooping: Bool) { + public func updateIsLooping(_ isLooping: Bool) { let wasLooping = self.isLooping self.isLooping = isLooping @@ -1382,7 +1391,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } } - func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) { + public func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) { if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners { self.bubbleTheme = bubbleTheme self.bubbleCorners = bubbleCorners @@ -1430,7 +1439,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } } - func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool { + public func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool { guard let bubbleTheme = self.bubbleTheme, let bubbleCorners = self.bubbleCorners else { return false } @@ -1474,13 +1483,18 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode return false } + + public func makeLegacyBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? { + let node = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type) + node.updateContents() + return node + } - func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? { + public func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? { if !self.hasBubbleBackground(for: type) { return nil } - #if true var sourceView: PortalSourceView? switch type { case .free: @@ -1499,14 +1513,9 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode let node = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type) return node } - #else - let node = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type) - node.updateContents() - return node - #endif } - func makeFreeBackground() -> PortalView? { + public func makeFreeBackground() -> PortalView? { if !self.hasBubbleBackground(for: .free) { return nil } @@ -1519,7 +1528,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } } - func hasExtraBubbleBackground() -> Bool { + public func hasExtraBubbleBackground() -> Bool { var isInvertedGradient = false switch self.wallpaper { case let .file(file): @@ -1532,7 +1541,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode return isInvertedGradient } - func makeDimmedNode() -> ASDisplayNode? { + public func makeDimmedNode() -> ASDisplayNode? { if let gradientBackgroundNode = self.gradientBackgroundNode { return GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode) } else { diff --git a/submodules/WallpaperResources/BUILD b/submodules/WallpaperResources/BUILD index 8fcb8fa8c2a..c0e632d10cd 100644 --- a/submodules/WallpaperResources/BUILD +++ b/submodules/WallpaperResources/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/WatchBridge/BUILD b/submodules/WatchBridge/BUILD index 5766afcb8fa..c018f0d704d 100644 --- a/submodules/WatchBridge/BUILD +++ b/submodules/WatchBridge/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/WatchBridgeAudio/BUILD b/submodules/WatchBridgeAudio/BUILD index e3994dc62d4..18cc576c929 100644 --- a/submodules/WatchBridgeAudio/BUILD +++ b/submodules/WatchBridgeAudio/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/WebSearchUI/BUILD b/submodules/WebSearchUI/BUILD index e8b8681c499..1b8f820ce4d 100644 --- a/submodules/WebSearchUI/BUILD +++ b/submodules/WebSearchUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index cbd8e9554bd..079887e0f86 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -35,14 +35,14 @@ final class WebSearchControllerInteraction { let setSearchQuery: (String) -> Void let deleteRecentQuery: (String) -> Void let toggleSelection: (ChatContextResult, Bool) -> Bool - let sendSelected: (ChatContextResult?, Bool, Int32?) -> Void - let schedule: () -> Void + let sendSelected: (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?) -> Void + let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void let avatarCompleted: (UIImage) -> Void let selectionState: TGMediaSelectionContext? let editingState: TGMediaEditingContext var hiddenMediaId: String? - init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?) -> Void, schedule: @escaping () -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { + init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { self.openResult = openResult self.setSearchQuery = setSearchQuery self.deleteRecentQuery = deleteRecentQuery @@ -254,7 +254,7 @@ public final class WebSearchController: ViewController { } else { return false } - }, sendSelected: { [weak self] current, silently, scheduleTime in + }, sendSelected: { [weak self] current, silently, scheduleTime, messageEffect in if let selectionState = selectionState, let results = self?.controllerNode.currentExternalResults { if let current = current { let currentItem = LegacyWebSearchItem(result: current) @@ -264,10 +264,10 @@ public final class WebSearchController: ViewController { sendSelected(results, selectionState, editingState, false) } } - }, schedule: { [weak self] in + }, schedule: { [weak self] messageEffect in if let strongSelf = self { strongSelf.presentSchedulePicker(false, { [weak self] time in - self?.controllerInteraction?.sendSelected(nil, false, time) + self?.controllerInteraction?.sendSelected(nil, false, time, nil) }) } }, avatarCompleted: { result in @@ -589,6 +589,17 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext { } } } + + public var hasCaption: Bool { + return false + } + + public var captionIsAboveMedia: Signal { + return .single(false) + } + + public func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { + } public var loadingProgress: Signal { return .single(nil) @@ -606,12 +617,12 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext { self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true) } - public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { - self.interaction?.sendSelected(nil, mode == .silently, nil) + public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { + self.interaction?.sendSelected(nil, mode == .silently, nil, parameters) } - public func schedule() { - self.interaction?.schedule() + public func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { + self.interaction?.schedule(parameters) } public func mainButtonAction() { diff --git a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift index 77f646c76d0..b6ca23e4bbe 100644 --- a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift +++ b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift @@ -717,7 +717,7 @@ class WebSearchControllerNode: ASDisplayNode { } @objc private func sendPressed() { - self.controllerInteraction.sendSelected(nil, false, nil) + self.controllerInteraction.sendSelected(nil, false, nil, nil) self.cancel?() } @@ -740,7 +740,7 @@ class WebSearchControllerNode: ASDisplayNode { return self?.transitionNode(for: result)?.transitionView() }, completed: { [weak self] result in if let strongSelf = self { - strongSelf.controllerInteraction.sendSelected(result, false, nil) + strongSelf.controllerInteraction.sendSelected(result, false, nil, nil) strongSelf.cancel?() } }, getCaptionPanelView: self.getCaptionPanelView, present: present) @@ -760,7 +760,7 @@ class WebSearchControllerNode: ASDisplayNode { }, baseNavigationController: nil, sendCurrent: { [weak self] result in if let strongSelf = self { - strongSelf.controllerInteraction.sendSelected(result, false, nil) + strongSelf.controllerInteraction.sendSelected(result, false, nil, nil) strongSelf.cancel?() } }) diff --git a/submodules/WebUI/BUILD b/submodules/WebUI/BUILD index ae4a1350887..f24a3459d0c 100644 --- a/submodules/WebUI/BUILD +++ b/submodules/WebUI/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index fbef04c2fb8..98769d41dab 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -234,24 +234,22 @@ public struct WebAppParameters { } } -public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> [String: Any] { - let backgroundColor = presentationTheme.list.plainBackgroundColor.rgb - let secondaryBackgroundColor = presentationTheme.list.blocksBackgroundColor.rgb +public func generateWebAppThemeParams(_ theme: PresentationTheme) -> [String: Any] { return [ - "bg_color": Int32(bitPattern: backgroundColor), - "secondary_bg_color": Int32(bitPattern: secondaryBackgroundColor), - "text_color": Int32(bitPattern: presentationTheme.list.itemPrimaryTextColor.rgb), - "hint_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb), - "link_color": Int32(bitPattern: presentationTheme.list.itemAccentColor.rgb), - "button_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.fillColor.rgb), - "button_text_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.foregroundColor.rgb), - "header_bg_color": Int32(bitPattern: presentationTheme.rootController.navigationBar.opaqueBackgroundColor.rgb), - "accent_text_color": Int32(bitPattern: presentationTheme.list.itemAccentColor.rgb), - "section_bg_color": Int32(bitPattern: presentationTheme.list.itemBlocksBackgroundColor.rgb), - "section_header_text_color": Int32(bitPattern: presentationTheme.list.freeTextColor.rgb), - "subtitle_text_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb), - "destructive_text_color": Int32(bitPattern: presentationTheme.list.itemDestructiveColor.rgb), - "section_separator_color": Int32(bitPattern: presentationTheme.list.itemBlocksSeparatorColor.rgb) + "bg_color": Int32(bitPattern: theme.list.plainBackgroundColor.rgb), + "secondary_bg_color": Int32(bitPattern: theme.list.blocksBackgroundColor.rgb), + "text_color": Int32(bitPattern: theme.list.itemPrimaryTextColor.rgb), + "hint_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), + "link_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), + "button_color": Int32(bitPattern: theme.list.itemCheckColors.fillColor.rgb), + "button_text_color": Int32(bitPattern: theme.list.itemCheckColors.foregroundColor.rgb), + "header_bg_color": Int32(bitPattern: theme.rootController.navigationBar.opaqueBackgroundColor.rgb), + "accent_text_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), + "section_bg_color": Int32(bitPattern: theme.list.itemBlocksBackgroundColor.rgb), + "section_header_text_color": Int32(bitPattern: theme.list.freeTextColor.rgb), + "subtitle_text_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), + "destructive_text_color": Int32(bitPattern: theme.list.itemDestructiveColor.rgb), + "section_separator_color": Int32(bitPattern: theme.list.itemBlocksSeparatorColor.rgb) ] } @@ -859,14 +857,42 @@ 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 starsContext = strongSelf.context.starsContext { + let starsInputData = combineLatest( + inputData.get(), + starsContext.state + ) + |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in + if let data, let state { + return (state, data.form, data.botPeer) + } else { + return nil + } + } + let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { _ in + let controller = strongSelf.context.sharedContext.makeStarsTransferScreen( + context: strongSelf.context, + starsContext: starsContext, + invoice: invoice, + source: .slug(slug), + inputData: starsInputData, + completion: { [weak self] paid in + guard let self else { + return + } + self.sendInvoiceClosedEvent(slug: slug, result: paid ? .paid : .cancelled) + } + ) + 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 @@ -1912,7 +1938,7 @@ public final class WebAppController: ViewController, AttachmentContainable { items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_Settings, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: nil) + c?.dismiss(completion: nil) if let strongSelf = self { strongSelf.controllerNode.sendSettingsButtonEvent() @@ -1924,7 +1950,7 @@ public final class WebAppController: ViewController, AttachmentContainable { items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_OpenBot, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Bots"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: nil) + c?.dismiss(completion: nil) guard let strongSelf = self else { return @@ -1948,7 +1974,7 @@ public final class WebAppController: ViewController, AttachmentContainable { items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_ReloadPage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reload"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: nil) + c?.dismiss(completion: nil) self?.controllerNode.webView?.reload() }))) @@ -1956,7 +1982,7 @@ public final class WebAppController: ViewController, AttachmentContainable { items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_TermsOfUse, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: nil) + c?.dismiss(completion: nil) guard let self, let navigationController = self.getNavigationController() else { return @@ -1966,7 +1992,7 @@ public final class WebAppController: ViewController, AttachmentContainable { let _ = (cachedWebAppTermsPage(context: context) |> deliverOnMainQueue).startStandalone(next: { resolvedUrl in context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: true, openPeer: { peer, navigation in - }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak self] c, arguments in + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak self] c, arguments in self?.push(c) }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) }) @@ -1976,7 +2002,7 @@ public final class WebAppController: ViewController, AttachmentContainable { items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_RemoveBot, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in - c.dismiss(completion: nil) + c?.dismiss(completion: nil) if let strongSelf = self { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -2077,6 +2103,17 @@ final class WebAppPickerContext: AttachmentMediaPickerContext { return .single(nil) } + var hasCaption: Bool { + return false + } + + var captionIsAboveMedia: Signal { + return .single(false) + } + + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { + } + public var loadingProgress: Signal { return self.controller?.controllerNode.loadingProgressPromise.get() ?? .single(nil) } @@ -2092,10 +2129,10 @@ final class WebAppPickerContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { } - func schedule() { + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { } func mainButtonAction() { diff --git a/submodules/WebsiteType/BUILD b/submodules/WebsiteType/BUILD index 27297a6dcf7..376115a9b5f 100644 --- a/submodules/WebsiteType/BUILD +++ b/submodules/WebsiteType/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/TelegramCore:TelegramCore", diff --git a/submodules/WidgetItems/BUILD b/submodules/WidgetItems/BUILD index 97cf2126e78..306df560145 100644 --- a/submodules/WidgetItems/BUILD +++ b/submodules/WidgetItems/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", @@ -21,7 +21,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], visibility = [ "//visibility:public", diff --git a/submodules/WidgetItemsUtils/BUILD b/submodules/WidgetItemsUtils/BUILD index f9bef457472..62752f75e0f 100644 --- a/submodules/WidgetItemsUtils/BUILD +++ b/submodules/WidgetItemsUtils/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", diff --git a/submodules/lottie-ios/BUILD b/submodules/lottie-ios/BUILD index 433dc9b8b8c..acf1fd91a2d 100644 --- a/submodules/lottie-ios/BUILD +++ b/submodules/lottie-ios/BUILD @@ -7,7 +7,7 @@ swift_library( "Sources/**/*.swift", ]), copts = [ - "-warnings-as-errors", + #"-warnings-as-errors", ], deps = [ "//submodules/Display" diff --git a/submodules/lottie-ios/Sources/Private/Utility/Extensions/MathKit.swift b/submodules/lottie-ios/Sources/Private/Utility/Extensions/MathKit.swift index 0cf2e727fa4..22382bc6996 100644 --- a/submodules/lottie-ios/Sources/Private/Utility/Extensions/MathKit.swift +++ b/submodules/lottie-ios/Sources/Private/Utility/Extensions/MathKit.swift @@ -370,7 +370,7 @@ extension CGPoint { while foundPoint == false { refineIterations = refineIterations + 1 /// First see if the next point is still less than the projected length. - let nextPoint = points[closestPoint + 1] + let nextPoint = points[min(closestPoint + 1, points.indices.last!)] if nextPoint.distance < accurateDistance { point = nextPoint closestPoint = closestPoint + 1 diff --git a/submodules/sqlcipher/BUILD b/submodules/sqlcipher/BUILD index cce98273608..0df7412fc11 100644 --- a/submodules/sqlcipher/BUILD +++ b/submodules/sqlcipher/BUILD @@ -28,6 +28,7 @@ objc_library( "-DSQLITE_OMIT_DEPRECATED", "-DNDEBUG=1", "-DSQLITE_MAX_MMAP_SIZE=0", + "-Wno-all", ], sdk_frameworks = [ "Foundation", diff --git a/swift_deps.bzl b/swift_deps.bzl index 5355c3be238..1191de33110 100644 --- a/swift_deps.bzl +++ b/swift_deps.bzl @@ -1,18 +1,74 @@ load("@rules_swift_package_manager//swiftpkg:defs.bzl", "swift_package") def swift_dependencies(): + # version: 0.6.7 + swift_package( + name = "swiftpkg_anycodable", + commit = "862808b2070cd908cb04f9aafe7de83d35f81b05", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/Flight-School/AnyCodable", + ) + + # version: 1.0.2 + swift_package( + name = "swiftpkg_bigdecimal", + commit = "04d17040e4615fbfda3a882b9881f6841f4bf557", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/Zollerboy1/BigDecimal.git", + ) + + # version: 5.3.0 + swift_package( + name = "swiftpkg_bigint", + commit = "0ed110f7555c34ff468e72e1686e59721f2b0da6", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/attaswift/BigInt", + ) + + # branch: release/1.0.0 + swift_package( + name = "swiftpkg_core_swift", + commit = "20b7275f60ad80634f056905d7f18292294cd510", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/denis15yo/core-swift.git", + ) + + # version: 1.8.2 + swift_package( + name = "swiftpkg_cryptoswift", + commit = "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/krzyzanowskim/CryptoSwift.git", + ) + + # version: 0.1.2 + swift_package( + name = "swiftpkg_curvelib.swift", + commit = "7dad3bf1793de263f83406c08c18c9316abf082f", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/tkey/curvelib.swift", + ) + # version: 2.1.3 swift_package( name = "swiftpkg_factory", commit = "587995f7d5cc667951d635fbf6b4252324ba0439", dependencies_index = "@//:swift_deps_index.json", - remote = "https://github.com/hmlongco/Factory", + remote = "https://github.com/hmlongco/Factory.git", + ) + + # version: 5.2.0 + swift_package( + name = "swiftpkg_fetch_node_details_swift", + commit = "bf2f0759da5c5c80765773b08c2756045edf608f", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/torusresearch/fetch-node-details-swift.git", ) # version: 2.6.1 swift_package( name = "swiftpkg_floatingpanel", - commit = "8f2be39bf49b4d5e22bbf7bdde69d5b76d0ecd2a", + commit = "22d46c526084724a718b8c39ab77f12452712cc7", dependencies_index = "@//:swift_deps_index.json", remote = "https://github.com/scenee/FloatingPanel", ) @@ -25,6 +81,14 @@ def swift_dependencies(): remote = "https://github.com/denis15yo/GRDB.swift.git", ) + # version: 20.0.0 + swift_package( + name = "swiftpkg_keychain_swift", + commit = "d108a1fa6189e661f91560548ef48651ed8d93b9", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/evgenyneu/keychain-swift.git", + ) + # version: 1.2.0 swift_package( name = "swiftpkg_lnextensionexecutor", @@ -41,14 +105,30 @@ def swift_dependencies(): remote = "https://github.com/denis15yo/navigation-stack-backport.git", ) - # branch: avatar-generator + # branch: develop swift_package( name = "swiftpkg_nicegram_assistant_ios", - commit = "667bceccb7102dff96ba018b22c5fa1a6d30c9c6", + commit = "0985fd5dfae1676121c54c31fe2817059d5bf784", dependencies_index = "@//:swift_deps_index.json", remote = "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", ) + # branch: develop + swift_package( + name = "swiftpkg_nicegram_wallet_ios", + commit = "241a210ee1b5cb9cb95d9245a4ed384973ee2ef2", + dependencies_index = "@//:swift_deps_index.json", + remote = "git@bitbucket.org:mobyrix/nicegram-wallet-ios.git", + ) + + # version: 14.3.1 + swift_package( + name = "swiftpkg_qrcode", + commit = "263f280d2c8144adfb0b6676109846cfc8dd552b", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/WalletConnect/QRCode", + ) + # version: 7.3.2 swift_package( name = "swiftpkg_r.swift", @@ -60,11 +140,27 @@ def swift_dependencies(): # version: 5.15.5 swift_package( name = "swiftpkg_sdwebimage", - commit = "f6afa0132961d593f07970d84e2d8b588c29ea04", + commit = "b8523c1642f3c142b06dd98443ea7c48343a4dfd", dependencies_index = "@//:swift_deps_index.json", remote = "https://github.com/SDWebImage/SDWebImage.git", ) + # version: 3.1.1 + swift_package( + name = "swiftpkg_session_manager_swift", + commit = "c89d9205a1ce38cd6c6374b906a9039d9cc03f05", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/Web3Auth/session-manager-swift.git", + ) + + # version: 4.0.0 + swift_package( + name = "swiftpkg_single_factor_auth_swift", + commit = "8baa2b8cf55b0a38cb98c412bea1c6597adb78ba", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/Web3Auth/single-factor-auth-swift.git", + ) + # version: 5.6.0 swift_package( name = "swiftpkg_snapkit", @@ -73,6 +169,14 @@ def swift_dependencies(): remote = "https://github.com/SnapKit/SnapKit.git", ) + # version: 4.0.8 + swift_package( + name = "swiftpkg_starscream", + commit = "c6bfd1af48efcc9a9ad203665db12375ba6b145a", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/daltoniam/Starscream.git", + ) + # version: 0.4.3 swift_package( name = "swiftpkg_subscriptionanalytics_ios", @@ -84,11 +188,67 @@ def swift_dependencies(): # version: 1.2.3 swift_package( name = "swiftpkg_swift_argument_parser", - commit = "46989693916f56d1186bd59ac15124caef896560", + commit = "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", dependencies_index = "@//:swift_deps_index.json", remote = "https://github.com/apple/swift-argument-parser", ) + # version: 1.1.0 + swift_package( + name = "swiftpkg_swift_collections", + commit = "ee97538f5b81ae89698fd95938896dec5217b148", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/apple/swift-collections", + ) + + # version: 1.0.3 + swift_package( + name = "swiftpkg_swift_http_types", + commit = "1ddbea1ee34354a6a2532c60f98501c35ae8edfa", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/apple/swift-http-types", + ) + + # version: 1.0.2 + swift_package( + name = "swiftpkg_swift_numerics", + commit = "0a5bc04095a675662cf24757cc0640aa2204253b", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/apple/swift-numerics.git", + ) + + # version: 1.4.0 + swift_package( + name = "swiftpkg_swift_openapi_runtime", + commit = "a51b3bd6f2151e9a6f792ca6937a7242c4758768", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/apple/swift-openapi-runtime", + ) + + # version: 1.0.1 + swift_package( + name = "swiftpkg_swift_openapi_urlsession", + commit = "9229842c63e9fc3bbd32c661d8274b4d9d8715f1", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/apple/swift-openapi-urlsession.git", + ) + + # version: 1.0.3 + swift_package( + name = "swiftpkg_swift_qrcode_generator", + commit = "5ca09b6a2ad190f94aa3d6ddef45b187f8c0343b", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/dagronf/swift-qrcode-generator", + ) + + # version: 1.1.6 + swift_package( + name = "swiftpkg_swiftimagereadwrite", + commit = "5596407d1cf61b953b8e658fa8636a471df3c509", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/dagronf/SwiftImageReadWrite", + ) + # version: 0.16.4 swift_package( name = "swiftpkg_swiftystorekit", @@ -97,6 +257,62 @@ def swift_dependencies(): remote = "https://github.com/bizz84/SwiftyStoreKit.git", ) + # version: 0.2.1 + swift_package( + name = "swiftpkg_tkey_ios", + commit = "c107450f0675351a9a1eaaefe60bcfa285ff1f9e", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/tkey/tkey-ios.git", + ) + + # version: 0.1.5 + swift_package( + name = "swiftpkg_ton_api_swift", + commit = "1988939fe0ce6db6bc587cfe7c9d15dc3bca1d69", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/tonkeeper/ton-api-swift", + ) + + # branch: main + swift_package( + name = "swiftpkg_ton_swift", + commit = "e4c3def222afc125f7ee83c1569004e31f0cd05c", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/denis15yo/ton-swift.git", + ) + + # version: 8.0.1 + swift_package( + name = "swiftpkg_torus_utils_swift", + commit = "4c17ef5166c162455d0a37115c033eeff8cb282d", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/torusresearch/torus-utils-swift.git", + ) + + # version: 1.1.0 + swift_package( + name = "swiftpkg_tweetnacl_swiftwrap", + commit = "f8fd111642bf2336b11ef9ea828510693106e954", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/bitmark-inc/tweetnacl-swiftwrap", + ) + + # version: 4.0.36 + swift_package( + name = "swiftpkg_wallet_core", + commit = "94116a24445c2052edbc7203baf68296c68ce8f4", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/trustwallet/wallet-core.git", + ) + + # branch: develop + swift_package( + name = "swiftpkg_walletconnectswiftv2", + commit = "1eacd732e321c9511859d7e73303d61d82af4d46", + dependencies_index = "@//:swift_deps_index.json", + remote = "https://github.com/denis15yo/WalletConnectSwiftV2.git", + ) + # version: 2.9.0 swift_package( name = "swiftpkg_xcodeedit", diff --git a/swift_deps_index.json b/swift_deps_index.json index f9639611620..c5b0831bccf 100644 --- a/swift_deps_index.json +++ b/swift_deps_index.json @@ -3,6 +3,133 @@ "nicegram-assistant-ios" ], "modules": [ + { + "name": "AnyCodable", + "c99name": "AnyCodable", + "src_type": "swift", + "label": "@swiftpkg_anycodable//:AnyCodable.rspm", + "package_identity": "anycodable", + "product_memberships": [ + "AnyCodable" + ] + }, + { + "name": "BigDecimal", + "c99name": "BigDecimal", + "src_type": "swift", + "label": "@swiftpkg_bigdecimal//:BigDecimal.rspm", + "package_identity": "bigdecimal", + "product_memberships": [ + "BigDecimal" + ] + }, + { + "name": "BigInt", + "c99name": "BigInt", + "src_type": "swift", + "label": "@swiftpkg_bigint//:BigInt.rspm", + "package_identity": "bigint", + "product_memberships": [ + "BigInt" + ] + }, + { + "name": "TonConnectAPI", + "c99name": "TonConnectAPI", + "src_type": "swift", + "label": "@swiftpkg_core_swift//:TonConnectAPI.rspm", + "package_identity": "core-swift", + "product_memberships": [ + "TonKeeperWalletCore", + "WalletCoreKeeper" + ] + }, + { + "name": "TonKeeperWalletCore", + "c99name": "TonKeeperWalletCore", + "src_type": "swift", + "label": "@swiftpkg_core_swift//:TonKeeperWalletCore.rspm", + "package_identity": "core-swift", + "product_memberships": [ + "TonKeeperWalletCore" + ] + }, + { + "name": "WalletCoreCore", + "c99name": "WalletCoreCore", + "src_type": "swift", + "label": "@swiftpkg_core_swift//:WalletCoreCore.rspm", + "package_identity": "core-swift", + "product_memberships": [ + "TonKeeperWalletCore", + "WalletCoreCore", + "WalletCoreKeeper" + ] + }, + { + "name": "WalletCoreKeeper", + "c99name": "WalletCoreKeeper", + "src_type": "swift", + "label": "@swiftpkg_core_swift//:WalletCoreKeeper.rspm", + "package_identity": "core-swift", + "product_memberships": [ + "TonKeeperWalletCore", + "WalletCoreKeeper" + ] + }, + { + "name": "CryptoSwift", + "c99name": "CryptoSwift", + "src_type": "swift", + "label": "@swiftpkg_cryptoswift//:CryptoSwift.rspm", + "package_identity": "cryptoswift", + "product_memberships": [ + "CryptoSwift" + ] + }, + { + "name": "curveSecp256k1", + "c99name": "curveSecp256k1", + "src_type": "swift", + "label": "@swiftpkg_curvelib.swift//:curveSecp256k1.rspm", + "package_identity": "curvelib.swift", + "product_memberships": [ + "curveSecp256k1", + "encryption_aes_cbc_sha512" + ] + }, + { + "name": "curve_secp256k1", + "c99name": "curve_secp256k1", + "src_type": "binary", + "label": "@swiftpkg_curvelib.swift//:curve_secp256k1.rspm", + "package_identity": "curvelib.swift", + "product_memberships": [ + "curveSecp256k1", + "encryption_aes_cbc_sha512" + ] + }, + { + "name": "curvelib", + "c99name": "curvelib", + "src_type": "clang", + "label": "@swiftpkg_curvelib.swift//:curvelib.rspm", + "package_identity": "curvelib.swift", + "product_memberships": [ + "curveSecp256k1", + "encryption_aes_cbc_sha512" + ] + }, + { + "name": "encryption_aes_cbc_sha512", + "c99name": "encryption_aes_cbc_sha512", + "src_type": "swift", + "label": "@swiftpkg_curvelib.swift//:encryption_aes_cbc_sha512.rspm", + "package_identity": "curvelib.swift", + "product_memberships": [ + "encryption_aes_cbc_sha512" + ] + }, { "name": "Factory", "c99name": "Factory", @@ -13,6 +140,38 @@ "Factory" ] }, + { + "name": "CommonSources", + "c99name": "CommonSources", + "src_type": "swift", + "label": "@swiftpkg_fetch_node_details_swift//:CommonSources.rspm", + "package_identity": "fetch-node-details-swift", + "product_memberships": [ + "FetchNodeDetails", + "FndBase" + ] + }, + { + "name": "FetchNodeDetails", + "c99name": "FetchNodeDetails", + "src_type": "swift", + "label": "@swiftpkg_fetch_node_details_swift//:FetchNodeDetails.rspm", + "package_identity": "fetch-node-details-swift", + "product_memberships": [ + "FetchNodeDetails" + ] + }, + { + "name": "FndBase", + "c99name": "FndBase", + "src_type": "swift", + "label": "@swiftpkg_fetch_node_details_swift//:FndBase.rspm", + "package_identity": "fetch-node-details-swift", + "product_memberships": [ + "FetchNodeDetails", + "FndBase" + ] + }, { "name": "FloatingPanel", "c99name": "FloatingPanel", @@ -36,6 +195,16 @@ "GRDB-dynamic" ] }, + { + "name": "KeychainSwift", + "c99name": "KeychainSwift", + "src_type": "swift", + "label": "@swiftpkg_keychain_swift//:KeychainSwift.rspm", + "package_identity": "keychain-swift", + "product_memberships": [ + "KeychainSwift" + ] + }, { "name": "LNExtensionExecutor", "c99name": "LNExtensionExecutor", @@ -68,6 +237,7 @@ "FeatChatBanner", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPhoneEntryBanner", "FeatPremiumUI", "NGAssistantUI", @@ -116,8 +286,10 @@ "FeatCardUI", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPremiumUI", "FeatSpeechToText", + "FeatTgChatButton", "NGAiChatUI", "NGAssistantUI", "NGAuth", @@ -135,7 +307,9 @@ "FeatCardUI", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPremiumUI", + "FeatTgChatButton", "NGAiChatUI", "NGAssistantUI", "NGAuth", @@ -234,6 +408,16 @@ "NGEntryPoint" ] }, + { + "name": "FeatOnboarding", + "c99name": "FeatOnboarding", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatOnboarding.rspm", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "FeatOnboarding" + ] + }, { "name": "FeatPersistentStorage", "c99name": "FeatPersistentStorage", @@ -245,7 +429,9 @@ "FeatCardUI", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPremiumUI", + "FeatTgChatButton", "NGAiChat", "NGAiChatUI", "NGAssistantUI", @@ -285,11 +471,13 @@ "FeatChatBanner", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPinnedChats", "FeatPremium", "FeatPremiumUI", "FeatSpeechToText", "FeatTasks", + "FeatTgChatButton", "NGAiChat", "NGAiChatUI", "NGAnalytics", @@ -309,6 +497,7 @@ "product_memberships": [ "FeatAvatarGeneratorUI", "FeatImagesHubUI", + "FeatOnboarding", "FeatPremiumUI", "NGAssistantUI", "NGEntryPoint" @@ -325,8 +514,10 @@ "FeatCardUI", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPremiumUI", "FeatTasks", + "FeatTgChatButton", "NGAiChatUI", "NGAssistantUI", "NGAuth", @@ -375,6 +566,31 @@ "NGEntryPoint" ] }, + { + "name": "FeatTgChatButton", + "c99name": "FeatTgChatButton", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatTgChatButton.rspm", + "modulemap_label": "@swiftpkg_nicegram_assistant_ios//:FeatTgChatButton.rspm_modulemap", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "FeatTgChatButton" + ] + }, + { + "name": "FeatWallet", + "c99name": "FeatWallet", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatWallet.rspm", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "FeatAvatarGeneratorUI", + "FeatImagesHubUI", + "FeatTgChatButton", + "NGAiChatUI", + "NGAssistantUI" + ] + }, { "name": "NGAiChat", "c99name": "NGAiChat", @@ -386,7 +602,9 @@ "FeatCardUI", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPremiumUI", + "FeatTgChatButton", "NGAiChat", "NGAiChatUI", "NGAssistantUI", @@ -403,6 +621,7 @@ "product_memberships": [ "FeatAvatarGeneratorUI", "FeatImagesHubUI", + "FeatTgChatButton", "NGAiChatUI", "NGAssistantUI" ] @@ -419,9 +638,11 @@ "FeatChatBanner", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPinnedChats", "FeatPremiumUI", "FeatTasks", + "FeatTgChatButton", "NGAiChat", "NGAiChatUI", "NGAnalytics", @@ -443,10 +664,12 @@ "FeatChatBanner", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPinnedChats", "FeatPremiumUI", "FeatSpeechToText", "FeatTasks", + "FeatTgChatButton", "NGAiChat", "NGAiChatUI", "NGAnalytics", @@ -467,6 +690,7 @@ "product_memberships": [ "FeatAvatarGeneratorUI", "FeatImagesHubUI", + "FeatTgChatButton", "NGAiChatUI", "NGAssistantUI", "NGEntryPoint" @@ -494,7 +718,9 @@ "FeatCardUI", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPremiumUI", + "FeatTgChatButton", "NGAiChatUI", "NGAssistantUI", "NGAuth", @@ -514,12 +740,14 @@ "FeatHiddenChats", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPhoneEntryBanner", "FeatPinnedChats", "FeatPremium", "FeatPremiumUI", "FeatSpeechToText", "FeatTasks", + "FeatTgChatButton", "NGAiChat", "NGAiChatUI", "NGAnalytics", @@ -548,9 +776,11 @@ "FeatChatBanner", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPhoneEntryBanner", "FeatPinnedChats", "FeatPremiumUI", + "FeatTgChatButton", "NGAiChatUI", "NGAssistantUI", "NGCoreUI", @@ -592,12 +822,14 @@ "FeatHiddenChats", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPhoneEntryBanner", "FeatPinnedChats", "FeatPremium", "FeatPremiumUI", "FeatSpeechToText", "FeatTasks", + "FeatTgChatButton", "NGAiChat", "NGAiChatUI", "NGAnalytics", @@ -625,7 +857,9 @@ "FeatCardUI", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPremiumUI", + "FeatTgChatButton", "NGAiChatUI", "NGAssistantUI", "NGAuth", @@ -645,10 +879,12 @@ "FeatChatBanner", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPinnedChats", "FeatPremiumUI", "FeatSpeechToText", "FeatTasks", + "FeatTgChatButton", "NGAiChat", "NGAiChatUI", "NGAnalytics", @@ -682,8 +918,10 @@ "FeatCardUI", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPremiumUI", "FeatTasks", + "FeatTgChatButton", "NGAiChatUI", "NGAssistantUI", "NGAuth", @@ -702,10 +940,12 @@ "FeatChatBanner", "FeatImagesHubUI", "FeatNicegramHub", + "FeatOnboarding", "FeatPinnedChats", "FeatPremiumUI", "FeatSpeechToText", "FeatTasks", + "FeatTgChatButton", "NGAiChat", "NGAiChatUI", "NGAssistantUI", @@ -715,6 +955,30 @@ "NGSpecialOffer" ] }, + { + "name": "NicegramWallet", + "c99name": "NicegramWallet", + "src_type": "swift", + "label": "@swiftpkg_nicegram_wallet_ios//:NicegramWallet.rspm", + "modulemap_label": "@swiftpkg_nicegram_wallet_ios//:NicegramWallet.rspm_modulemap", + "package_identity": "nicegram-wallet-ios", + "product_memberships": [ + "NicegramWallet" + ] + }, + { + "name": "QRCode", + "c99name": "QRCode", + "src_type": "swift", + "label": "@swiftpkg_qrcode//:QRCode.rspm", + "modulemap_label": "@swiftpkg_qrcode//:QRCode.rspm_modulemap", + "package_identity": "qrcode", + "product_memberships": [ + "QRCode", + "QRCodeStatic", + "QRCodeDynamic" + ] + }, { "name": "RswiftGenerateInternalResources", "c99name": "RswiftGenerateInternalResources", @@ -834,6 +1098,26 @@ "SDWebImageMapKit" ] }, + { + "name": "SessionManager", + "c99name": "SessionManager", + "src_type": "swift", + "label": "@swiftpkg_session_manager_swift//:SessionManager.rspm", + "package_identity": "session-manager-swift", + "product_memberships": [ + "SessionManager" + ] + }, + { + "name": "SingleFactorAuth", + "c99name": "SingleFactorAuth", + "src_type": "swift", + "label": "@swiftpkg_single_factor_auth_swift//:SingleFactorAuth.rspm", + "package_identity": "single-factor-auth-swift", + "product_memberships": [ + "SingleFactorAuth" + ] + }, { "name": "SnapKit", "c99name": "SnapKit", @@ -845,6 +1129,16 @@ "SnapKit-Dynamic" ] }, + { + "name": "Starscream", + "c99name": "Starscream", + "src_type": "swift", + "label": "@swiftpkg_starscream//:Starscream.rspm", + "package_identity": "starscream", + "product_memberships": [ + "Starscream" + ] + }, { "name": "SubscriptionAnalytics", "c99name": "SubscriptionAnalytics", @@ -898,131 +1192,838 @@ ] }, { - "name": "SwiftyStoreKit", - "c99name": "SwiftyStoreKit", + "name": "BitCollections", + "c99name": "BitCollections", "src_type": "swift", - "label": "@swiftpkg_swiftystorekit//:SwiftyStoreKit.rspm", - "package_identity": "swiftystorekit", + "label": "@swiftpkg_swift_collections//:BitCollections.rspm", + "package_identity": "swift-collections", "product_memberships": [ - "SwiftyStoreKit" + "BitCollections", + "Collections" ] }, { - "name": "XcodeEdit", - "c99name": "XcodeEdit", + "name": "Collections", + "c99name": "Collections", "src_type": "swift", - "label": "@swiftpkg_xcodeedit//:XcodeEdit.rspm", - "package_identity": "xcodeedit", + "label": "@swiftpkg_swift_collections//:Collections.rspm", + "package_identity": "swift-collections", "product_memberships": [ - "XcodeEdit" + "Collections" ] - } - ], - "products": [ - { - "identity": "factory", - "name": "Factory", - "type": "library", - "label": "@swiftpkg_factory//:Factory" }, { - "identity": "floatingpanel", - "name": "FloatingPanel", - "type": "library", - "label": "@swiftpkg_floatingpanel//:FloatingPanel" + "name": "DequeModule", + "c99name": "DequeModule", + "src_type": "swift", + "label": "@swiftpkg_swift_collections//:DequeModule.rspm", + "package_identity": "swift-collections", + "product_memberships": [ + "DequeModule", + "Collections" + ] }, { - "identity": "grdb.swift", - "name": "GRDB", - "type": "library", - "label": "@swiftpkg_grdb.swift//:GRDB" + "name": "HashTreeCollections", + "c99name": "HashTreeCollections", + "src_type": "swift", + "label": "@swiftpkg_swift_collections//:HashTreeCollections.rspm", + "package_identity": "swift-collections", + "product_memberships": [ + "HashTreeCollections", + "Collections" + ] }, { - "identity": "grdb.swift", - "name": "GRDB-dynamic", - "type": "library", - "label": "@swiftpkg_grdb.swift//:GRDB-dynamic" + "name": "HeapModule", + "c99name": "HeapModule", + "src_type": "swift", + "label": "@swiftpkg_swift_collections//:HeapModule.rspm", + "package_identity": "swift-collections", + "product_memberships": [ + "HeapModule", + "Collections" + ] }, { - "identity": "lnextensionexecutor", - "name": "LNExtensionExecutor", - "type": "library", - "label": "@swiftpkg_lnextensionexecutor//:LNExtensionExecutor" + "name": "InternalCollectionsUtilities", + "c99name": "InternalCollectionsUtilities", + "src_type": "swift", + "label": "@swiftpkg_swift_collections//:InternalCollectionsUtilities.rspm", + "package_identity": "swift-collections", + "product_memberships": [ + "BitCollections", + "DequeModule", + "HashTreeCollections", + "HeapModule", + "OrderedCollections", + "_RopeModule", + "Collections" + ] }, { - "identity": "lnextensionexecutor", - "name": "LNExtensionExecutor-Static", - "type": "library", - "label": "@swiftpkg_lnextensionexecutor//:LNExtensionExecutor-Static" + "name": "OrderedCollections", + "c99name": "OrderedCollections", + "src_type": "swift", + "label": "@swiftpkg_swift_collections//:OrderedCollections.rspm", + "package_identity": "swift-collections", + "product_memberships": [ + "OrderedCollections", + "Collections" + ] }, { - "identity": "navigation-stack-backport", - "name": "NavigationStackBackport", - "type": "library", - "label": "@swiftpkg_navigation_stack_backport//:NavigationStackBackport" + "name": "_RopeModule", + "c99name": "_RopeModule", + "src_type": "swift", + "label": "@swiftpkg_swift_collections//:_RopeModule.rspm", + "package_identity": "swift-collections", + "product_memberships": [ + "_RopeModule", + "Collections" + ] }, { - "identity": "nicegram-assistant-ios", - "name": "FeatAvatarGeneratorUI", - "type": "library", - "label": "@swiftpkg_nicegram_assistant_ios//:FeatAvatarGeneratorUI" + "name": "HTTPTypes", + "c99name": "HTTPTypes", + "src_type": "swift", + "label": "@swiftpkg_swift_http_types//:HTTPTypes.rspm", + "package_identity": "swift-http-types", + "product_memberships": [ + "HTTPTypes", + "HTTPTypesFoundation" + ] }, { - "identity": "nicegram-assistant-ios", - "name": "FeatCardUI", - "type": "library", - "label": "@swiftpkg_nicegram_assistant_ios//:FeatCardUI" + "name": "HTTPTypesFoundation", + "c99name": "HTTPTypesFoundation", + "src_type": "swift", + "label": "@swiftpkg_swift_http_types//:HTTPTypesFoundation.rspm", + "package_identity": "swift-http-types", + "product_memberships": [ + "HTTPTypesFoundation" + ] }, { - "identity": "nicegram-assistant-ios", - "name": "FeatChatBanner", - "type": "library", - "label": "@swiftpkg_nicegram_assistant_ios//:FeatChatBanner" + "name": "ComplexModule", + "c99name": "ComplexModule", + "src_type": "swift", + "label": "@swiftpkg_swift_numerics//:ComplexModule.rspm", + "package_identity": "swift-numerics", + "product_memberships": [ + "ComplexModule", + "Numerics" + ] }, { - "identity": "nicegram-assistant-ios", - "name": "FeatHiddenChats", - "type": "library", - "label": "@swiftpkg_nicegram_assistant_ios//:FeatHiddenChats" + "name": "Numerics", + "c99name": "Numerics", + "src_type": "swift", + "label": "@swiftpkg_swift_numerics//:Numerics.rspm", + "package_identity": "swift-numerics", + "product_memberships": [ + "Numerics" + ] }, { - "identity": "nicegram-assistant-ios", - "name": "FeatImagesHubUI", - "type": "library", - "label": "@swiftpkg_nicegram_assistant_ios//:FeatImagesHubUI" + "name": "RealModule", + "c99name": "RealModule", + "src_type": "swift", + "label": "@swiftpkg_swift_numerics//:RealModule.rspm", + "package_identity": "swift-numerics", + "product_memberships": [ + "ComplexModule", + "Numerics", + "RealModule" + ] }, { - "identity": "nicegram-assistant-ios", - "name": "FeatNicegramHub", - "type": "library", - "label": "@swiftpkg_nicegram_assistant_ios//:FeatNicegramHub" + "name": "_NumericsShims", + "c99name": "_NumericsShims", + "src_type": "clang", + "label": "@swiftpkg_swift_numerics//:_NumericsShims.rspm", + "package_identity": "swift-numerics", + "product_memberships": [ + "ComplexModule", + "Numerics", + "RealModule" + ] }, { - "identity": "nicegram-assistant-ios", - "name": "FeatPhoneEntryBanner", - "type": "library", - "label": "@swiftpkg_nicegram_assistant_ios//:FeatPhoneEntryBanner" + "name": "OpenAPIRuntime", + "c99name": "OpenAPIRuntime", + "src_type": "swift", + "label": "@swiftpkg_swift_openapi_runtime//:OpenAPIRuntime.rspm", + "package_identity": "swift-openapi-runtime", + "product_memberships": [ + "OpenAPIRuntime" + ] }, { - "identity": "nicegram-assistant-ios", - "name": "FeatPinnedChats", - "type": "library", - "label": "@swiftpkg_nicegram_assistant_ios//:FeatPinnedChats" + "name": "OpenAPIURLSession", + "c99name": "OpenAPIURLSession", + "src_type": "swift", + "label": "@swiftpkg_swift_openapi_urlsession//:OpenAPIURLSession.rspm", + "package_identity": "swift-openapi-urlsession", + "product_memberships": [ + "OpenAPIURLSession" + ] }, { - "identity": "nicegram-assistant-ios", - "name": "FeatPremium", - "type": "library", - "label": "@swiftpkg_nicegram_assistant_ios//:FeatPremium" + "name": "QRCodeGenerator", + "c99name": "QRCodeGenerator", + "src_type": "swift", + "label": "@swiftpkg_swift_qrcode_generator//:QRCodeGenerator.rspm", + "package_identity": "swift-qrcode-generator", + "product_memberships": [ + "QRCodeGenerator" + ] }, { - "identity": "nicegram-assistant-ios", - "name": "FeatPremiumUI", - "type": "library", - "label": "@swiftpkg_nicegram_assistant_ios//:FeatPremiumUI" + "name": "SwiftImageReadWrite", + "c99name": "SwiftImageReadWrite", + "src_type": "swift", + "label": "@swiftpkg_swiftimagereadwrite//:SwiftImageReadWrite.rspm", + "package_identity": "swiftimagereadwrite", + "product_memberships": [ + "SwiftImageReadWrite" + ] }, { - "identity": "nicegram-assistant-ios", + "name": "SwiftyStoreKit", + "c99name": "SwiftyStoreKit", + "src_type": "swift", + "label": "@swiftpkg_swiftystorekit//:SwiftyStoreKit.rspm", + "package_identity": "swiftystorekit", + "product_memberships": [ + "SwiftyStoreKit" + ] + }, + { + "name": "lib", + "c99name": "lib", + "src_type": "clang", + "label": "@swiftpkg_tkey_ios//:lib.rspm", + "package_identity": "tkey-ios", + "product_memberships": [ + "tkey-swift" + ] + }, + { + "name": "libtkey", + "c99name": "libtkey", + "src_type": "binary", + "label": "@swiftpkg_tkey_ios//:libtkey.rspm", + "package_identity": "tkey-ios", + "product_memberships": [ + "tkey-swift" + ] + }, + { + "name": "tkey-swift", + "c99name": "tkey_swift", + "src_type": "swift", + "label": "@swiftpkg_tkey_ios//:tkey-swift.rspm", + "package_identity": "tkey-ios", + "product_memberships": [ + "tkey-swift" + ] + }, + { + "name": "EventSource", + "c99name": "EventSource", + "src_type": "swift", + "label": "@swiftpkg_ton_api_swift//:EventSource.rspm", + "package_identity": "ton-api-swift", + "product_memberships": [ + "EventSource" + ] + }, + { + "name": "StreamURLSessionTransport", + "c99name": "StreamURLSessionTransport", + "src_type": "swift", + "label": "@swiftpkg_ton_api_swift//:StreamURLSessionTransport.rspm", + "package_identity": "ton-api-swift", + "product_memberships": [ + "StreamURLSessionTransport" + ] + }, + { + "name": "TonAPI", + "c99name": "TonAPI", + "src_type": "swift", + "label": "@swiftpkg_ton_api_swift//:TonAPI.rspm", + "package_identity": "ton-api-swift", + "product_memberships": [ + "TonAPI" + ] + }, + { + "name": "TonStreamingAPI", + "c99name": "TonStreamingAPI", + "src_type": "swift", + "label": "@swiftpkg_ton_api_swift//:TonStreamingAPI.rspm", + "package_identity": "ton-api-swift", + "product_memberships": [ + "TonStreamingAPI" + ] + }, + { + "name": "TonSwift", + "c99name": "TonSwift", + "src_type": "swift", + "label": "@swiftpkg_ton_swift//:TonSwift.rspm", + "package_identity": "ton-swift", + "product_memberships": [ + "TonSwift" + ] + }, + { + "name": "TorusUtils", + "c99name": "TorusUtils", + "src_type": "swift", + "label": "@swiftpkg_torus_utils_swift//:TorusUtils.rspm", + "package_identity": "torus-utils-swift", + "product_memberships": [ + "TorusUtils" + ] + }, + { + "name": "CTweetNacl", + "c99name": "CTweetNacl", + "src_type": "clang", + "label": "@swiftpkg_tweetnacl_swiftwrap//:CTweetNacl.rspm", + "package_identity": "tweetnacl-swiftwrap", + "product_memberships": [ + "TweetNacl" + ] + }, + { + "name": "TweetNacl", + "c99name": "TweetNacl", + "src_type": "swift", + "label": "@swiftpkg_tweetnacl_swiftwrap//:TweetNacl.rspm", + "package_identity": "tweetnacl-swiftwrap", + "product_memberships": [ + "TweetNacl" + ] + }, + { + "name": "SwiftProtobuf", + "c99name": "SwiftProtobuf", + "src_type": "binary", + "label": "@swiftpkg_wallet_core//:SwiftProtobuf.rspm", + "package_identity": "wallet-core", + "product_memberships": [ + "SwiftProtobuf" + ] + }, + { + "name": "WalletCore", + "c99name": "WalletCore", + "src_type": "binary", + "label": "@swiftpkg_wallet_core//:WalletCore.rspm", + "package_identity": "wallet-core", + "product_memberships": [ + "WalletCore" + ] + }, + { + "name": "Auth", + "c99name": "Auth", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:Auth.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnectAuth" + ] + }, + { + "name": "Commons", + "c99name": "Commons", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:Commons.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectPairing", + "WalletConnectNotify", + "WalletConnectPush", + "WalletConnectNetworking", + "WalletConnectVerify", + "WalletConnectModal", + "WalletConnectIdentity" + ] + }, + { + "name": "Database", + "c99name": "Database", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:Database.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnectNotify" + ] + }, + { + "name": "HTTPClient", + "c99name": "HTTPClient", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:HTTPClient.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectPairing", + "WalletConnectNotify", + "WalletConnectPush", + "WalletConnectNetworking", + "WalletConnectVerify", + "WalletConnectModal", + "WalletConnectIdentity" + ] + }, + { + "name": "JSONRPC", + "c99name": "JSONRPC", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:JSONRPC.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectPairing", + "WalletConnectNotify", + "WalletConnectPush", + "WalletConnectNetworking", + "WalletConnectVerify", + "WalletConnectModal", + "WalletConnectIdentity" + ] + }, + { + "name": "WalletConnectIdentity", + "c99name": "WalletConnectIdentity", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectIdentity.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnectNotify", + "WalletConnectIdentity" + ] + }, + { + "name": "WalletConnectJWT", + "c99name": "WalletConnectJWT", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectJWT.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectPairing", + "WalletConnectNotify", + "WalletConnectPush", + "WalletConnectNetworking", + "WalletConnectVerify", + "WalletConnectModal", + "WalletConnectIdentity" + ] + }, + { + "name": "WalletConnectKMS", + "c99name": "WalletConnectKMS", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectKMS.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectPairing", + "WalletConnectNotify", + "WalletConnectPush", + "WalletConnectNetworking", + "WalletConnectVerify", + "WalletConnectModal", + "WalletConnectIdentity" + ] + }, + { + "name": "WalletConnectModal", + "c99name": "WalletConnectModal", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectModal.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnectModal" + ] + }, + { + "name": "WalletConnectNetworking", + "c99name": "WalletConnectNetworking", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectNetworking.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectPairing", + "WalletConnectNotify", + "WalletConnectPush", + "WalletConnectNetworking", + "WalletConnectVerify", + "WalletConnectModal", + "WalletConnectIdentity" + ] + }, + { + "name": "WalletConnectNotify", + "c99name": "WalletConnectNotify", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectNotify.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnectNotify" + ] + }, + { + "name": "WalletConnectPairing", + "c99name": "WalletConnectPairing", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectPairing.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectPairing", + "WalletConnectNotify", + "WalletConnectModal" + ] + }, + { + "name": "WalletConnectPush", + "c99name": "WalletConnectPush", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectPush.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "Web3Wallet", + "WalletConnectNotify", + "WalletConnectPush" + ] + }, + { + "name": "WalletConnectRelay", + "c99name": "WalletConnectRelay", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectRelay.rspm", + "modulemap_label": "@swiftpkg_walletconnectswiftv2//:WalletConnectRelay.rspm_modulemap", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectPairing", + "WalletConnectNotify", + "WalletConnectPush", + "WalletConnectNetworking", + "WalletConnectVerify", + "WalletConnectModal", + "WalletConnectIdentity" + ] + }, + { + "name": "WalletConnectRouter", + "c99name": "WalletConnectRouter", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectRouter.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnectRouter" + ] + }, + { + "name": "WalletConnectRouterLegacy", + "c99name": "WalletConnectRouterLegacy", + "src_type": "objc", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectRouterLegacy.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnectRouter" + ] + }, + { + "name": "WalletConnectSign", + "c99name": "WalletConnectSign", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectSign.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "Web3Wallet", + "WalletConnectModal" + ] + }, + { + "name": "WalletConnectSigner", + "c99name": "WalletConnectSigner", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectSigner.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectNotify", + "WalletConnectModal" + ] + }, + { + "name": "WalletConnectUtils", + "c99name": "WalletConnectUtils", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectUtils.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectPairing", + "WalletConnectNotify", + "WalletConnectPush", + "WalletConnectNetworking", + "WalletConnectVerify", + "WalletConnectModal", + "WalletConnectIdentity" + ] + }, + { + "name": "WalletConnectVerify", + "c99name": "WalletConnectVerify", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectVerify.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "WalletConnect", + "WalletConnectAuth", + "Web3Wallet", + "WalletConnectVerify", + "WalletConnectModal" + ] + }, + { + "name": "Web3Wallet", + "c99name": "Web3Wallet", + "src_type": "swift", + "label": "@swiftpkg_walletconnectswiftv2//:Web3Wallet.rspm", + "package_identity": "walletconnectswiftv2", + "product_memberships": [ + "Web3Wallet" + ] + }, + { + "name": "XcodeEdit", + "c99name": "XcodeEdit", + "src_type": "swift", + "label": "@swiftpkg_xcodeedit//:XcodeEdit.rspm", + "package_identity": "xcodeedit", + "product_memberships": [ + "XcodeEdit" + ] + } + ], + "products": [ + { + "identity": "anycodable", + "name": "AnyCodable", + "type": "library", + "label": "@swiftpkg_anycodable//:AnyCodable" + }, + { + "identity": "bigdecimal", + "name": "BigDecimal", + "type": "library", + "label": "@swiftpkg_bigdecimal//:BigDecimal" + }, + { + "identity": "bigint", + "name": "BigInt", + "type": "library", + "label": "@swiftpkg_bigint//:BigInt" + }, + { + "identity": "core-swift", + "name": "TonKeeperWalletCore", + "type": "library", + "label": "@swiftpkg_core_swift//:TonKeeperWalletCore" + }, + { + "identity": "core-swift", + "name": "WalletCoreCore", + "type": "library", + "label": "@swiftpkg_core_swift//:WalletCoreCore" + }, + { + "identity": "core-swift", + "name": "WalletCoreKeeper", + "type": "library", + "label": "@swiftpkg_core_swift//:WalletCoreKeeper" + }, + { + "identity": "cryptoswift", + "name": "CryptoSwift", + "type": "library", + "label": "@swiftpkg_cryptoswift//:CryptoSwift" + }, + { + "identity": "curvelib.swift", + "name": "curveSecp256k1", + "type": "library", + "label": "@swiftpkg_curvelib.swift//:curveSecp256k1" + }, + { + "identity": "curvelib.swift", + "name": "encryption_aes_cbc_sha512", + "type": "library", + "label": "@swiftpkg_curvelib.swift//:encryption_aes_cbc_sha512" + }, + { + "identity": "factory", + "name": "Factory", + "type": "library", + "label": "@swiftpkg_factory//:Factory" + }, + { + "identity": "fetch-node-details-swift", + "name": "FetchNodeDetails", + "type": "library", + "label": "@swiftpkg_fetch_node_details_swift//:FetchNodeDetails" + }, + { + "identity": "fetch-node-details-swift", + "name": "FndBase", + "type": "library", + "label": "@swiftpkg_fetch_node_details_swift//:FndBase" + }, + { + "identity": "floatingpanel", + "name": "FloatingPanel", + "type": "library", + "label": "@swiftpkg_floatingpanel//:FloatingPanel" + }, + { + "identity": "grdb.swift", + "name": "GRDB", + "type": "library", + "label": "@swiftpkg_grdb.swift//:GRDB" + }, + { + "identity": "grdb.swift", + "name": "GRDB-dynamic", + "type": "library", + "label": "@swiftpkg_grdb.swift//:GRDB-dynamic" + }, + { + "identity": "keychain-swift", + "name": "KeychainSwift", + "type": "library", + "label": "@swiftpkg_keychain_swift//:KeychainSwift" + }, + { + "identity": "lnextensionexecutor", + "name": "LNExtensionExecutor", + "type": "library", + "label": "@swiftpkg_lnextensionexecutor//:LNExtensionExecutor" + }, + { + "identity": "lnextensionexecutor", + "name": "LNExtensionExecutor-Static", + "type": "library", + "label": "@swiftpkg_lnextensionexecutor//:LNExtensionExecutor-Static" + }, + { + "identity": "navigation-stack-backport", + "name": "NavigationStackBackport", + "type": "library", + "label": "@swiftpkg_navigation_stack_backport//:NavigationStackBackport" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatAvatarGeneratorUI", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatAvatarGeneratorUI" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatCardUI", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatCardUI" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatChatBanner", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatChatBanner" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatHiddenChats", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatHiddenChats" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatImagesHubUI", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatImagesHubUI" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatNicegramHub", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatNicegramHub" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatOnboarding", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatOnboarding" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatPhoneEntryBanner", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatPhoneEntryBanner" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatPinnedChats", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatPinnedChats" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatPremium", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatPremium" + }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatPremiumUI", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatPremiumUI" + }, + { + "identity": "nicegram-assistant-ios", "name": "FeatSpeechToText", "type": "library", "label": "@swiftpkg_nicegram_assistant_ios//:FeatSpeechToText" @@ -1033,6 +2034,12 @@ "type": "library", "label": "@swiftpkg_nicegram_assistant_ios//:FeatTasks" }, + { + "identity": "nicegram-assistant-ios", + "name": "FeatTgChatButton", + "type": "library", + "label": "@swiftpkg_nicegram_assistant_ios//:FeatTgChatButton" + }, { "identity": "nicegram-assistant-ios", "name": "NGAiChat", @@ -1117,6 +2124,30 @@ "type": "library", "label": "@swiftpkg_nicegram_assistant_ios//:_NGRemoteConfig" }, + { + "identity": "nicegram-wallet-ios", + "name": "NicegramWallet", + "type": "library", + "label": "@swiftpkg_nicegram_wallet_ios//:NicegramWallet" + }, + { + "identity": "qrcode", + "name": "QRCode", + "type": "library", + "label": "@swiftpkg_qrcode//:QRCode" + }, + { + "identity": "qrcode", + "name": "QRCodeDynamic", + "type": "library", + "label": "@swiftpkg_qrcode//:QRCodeDynamic" + }, + { + "identity": "qrcode", + "name": "QRCodeStatic", + "type": "library", + "label": "@swiftpkg_qrcode//:QRCodeStatic" + }, { "identity": "r.swift", "name": "RswiftGenerateInternalResources", @@ -1165,6 +2196,18 @@ "type": "library", "label": "@swiftpkg_sdwebimage//:SDWebImageMapKit" }, + { + "identity": "session-manager-swift", + "name": "SessionManager", + "type": "library", + "label": "@swiftpkg_session_manager_swift//:SessionManager" + }, + { + "identity": "single-factor-auth-swift", + "name": "SingleFactorAuth", + "type": "library", + "label": "@swiftpkg_single_factor_auth_swift//:SingleFactorAuth" + }, { "identity": "snapkit", "name": "SnapKit", @@ -1177,6 +2220,12 @@ "type": "library", "label": "@swiftpkg_snapkit//:SnapKit-Dynamic" }, + { + "identity": "starscream", + "name": "Starscream", + "type": "library", + "label": "@swiftpkg_starscream//:Starscream" + }, { "identity": "subscriptionanalytics-ios", "name": "SubscriptionAnalytics", @@ -1195,12 +2244,234 @@ "type": "plugin", "label": "@swiftpkg_swift_argument_parser//:GenerateManual" }, + { + "identity": "swift-collections", + "name": "BitCollections", + "type": "library", + "label": "@swiftpkg_swift_collections//:BitCollections" + }, + { + "identity": "swift-collections", + "name": "Collections", + "type": "library", + "label": "@swiftpkg_swift_collections//:Collections" + }, + { + "identity": "swift-collections", + "name": "DequeModule", + "type": "library", + "label": "@swiftpkg_swift_collections//:DequeModule" + }, + { + "identity": "swift-collections", + "name": "HashTreeCollections", + "type": "library", + "label": "@swiftpkg_swift_collections//:HashTreeCollections" + }, + { + "identity": "swift-collections", + "name": "HeapModule", + "type": "library", + "label": "@swiftpkg_swift_collections//:HeapModule" + }, + { + "identity": "swift-collections", + "name": "OrderedCollections", + "type": "library", + "label": "@swiftpkg_swift_collections//:OrderedCollections" + }, + { + "identity": "swift-collections", + "name": "_RopeModule", + "type": "library", + "label": "@swiftpkg_swift_collections//:_RopeModule" + }, + { + "identity": "swift-http-types", + "name": "HTTPTypes", + "type": "library", + "label": "@swiftpkg_swift_http_types//:HTTPTypes" + }, + { + "identity": "swift-http-types", + "name": "HTTPTypesFoundation", + "type": "library", + "label": "@swiftpkg_swift_http_types//:HTTPTypesFoundation" + }, + { + "identity": "swift-numerics", + "name": "ComplexModule", + "type": "library", + "label": "@swiftpkg_swift_numerics//:ComplexModule" + }, + { + "identity": "swift-numerics", + "name": "Numerics", + "type": "library", + "label": "@swiftpkg_swift_numerics//:Numerics" + }, + { + "identity": "swift-numerics", + "name": "RealModule", + "type": "library", + "label": "@swiftpkg_swift_numerics//:RealModule" + }, + { + "identity": "swift-openapi-runtime", + "name": "OpenAPIRuntime", + "type": "library", + "label": "@swiftpkg_swift_openapi_runtime//:OpenAPIRuntime" + }, + { + "identity": "swift-openapi-urlsession", + "name": "OpenAPIURLSession", + "type": "library", + "label": "@swiftpkg_swift_openapi_urlsession//:OpenAPIURLSession" + }, + { + "identity": "swift-qrcode-generator", + "name": "QRCodeGenerator", + "type": "library", + "label": "@swiftpkg_swift_qrcode_generator//:QRCodeGenerator" + }, + { + "identity": "swiftimagereadwrite", + "name": "SwiftImageReadWrite", + "type": "library", + "label": "@swiftpkg_swiftimagereadwrite//:SwiftImageReadWrite" + }, { "identity": "swiftystorekit", "name": "SwiftyStoreKit", "type": "library", "label": "@swiftpkg_swiftystorekit//:SwiftyStoreKit" }, + { + "identity": "tkey-ios", + "name": "tkey-swift", + "type": "library", + "label": "@swiftpkg_tkey_ios//:tkey-swift" + }, + { + "identity": "ton-api-swift", + "name": "EventSource", + "type": "library", + "label": "@swiftpkg_ton_api_swift//:EventSource" + }, + { + "identity": "ton-api-swift", + "name": "StreamURLSessionTransport", + "type": "library", + "label": "@swiftpkg_ton_api_swift//:StreamURLSessionTransport" + }, + { + "identity": "ton-api-swift", + "name": "TonAPI", + "type": "library", + "label": "@swiftpkg_ton_api_swift//:TonAPI" + }, + { + "identity": "ton-api-swift", + "name": "TonStreamingAPI", + "type": "library", + "label": "@swiftpkg_ton_api_swift//:TonStreamingAPI" + }, + { + "identity": "ton-swift", + "name": "TonSwift", + "type": "library", + "label": "@swiftpkg_ton_swift//:TonSwift" + }, + { + "identity": "torus-utils-swift", + "name": "TorusUtils", + "type": "library", + "label": "@swiftpkg_torus_utils_swift//:TorusUtils" + }, + { + "identity": "tweetnacl-swiftwrap", + "name": "TweetNacl", + "type": "library", + "label": "@swiftpkg_tweetnacl_swiftwrap//:TweetNacl" + }, + { + "identity": "wallet-core", + "name": "SwiftProtobuf", + "type": "library", + "label": "@swiftpkg_wallet_core//:SwiftProtobuf" + }, + { + "identity": "wallet-core", + "name": "WalletCore", + "type": "library", + "label": "@swiftpkg_wallet_core//:WalletCore" + }, + { + "identity": "walletconnectswiftv2", + "name": "WalletConnect", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnect" + }, + { + "identity": "walletconnectswiftv2", + "name": "WalletConnectAuth", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectAuth" + }, + { + "identity": "walletconnectswiftv2", + "name": "WalletConnectIdentity", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectIdentity" + }, + { + "identity": "walletconnectswiftv2", + "name": "WalletConnectModal", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectModal" + }, + { + "identity": "walletconnectswiftv2", + "name": "WalletConnectNetworking", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectNetworking" + }, + { + "identity": "walletconnectswiftv2", + "name": "WalletConnectNotify", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectNotify" + }, + { + "identity": "walletconnectswiftv2", + "name": "WalletConnectPairing", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectPairing" + }, + { + "identity": "walletconnectswiftv2", + "name": "WalletConnectPush", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectPush" + }, + { + "identity": "walletconnectswiftv2", + "name": "WalletConnectRouter", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectRouter" + }, + { + "identity": "walletconnectswiftv2", + "name": "WalletConnectVerify", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:WalletConnectVerify" + }, + { + "identity": "walletconnectswiftv2", + "name": "Web3Wallet", + "type": "library", + "label": "@swiftpkg_walletconnectswiftv2//:Web3Wallet" + }, { "identity": "xcodeedit", "name": "XcodeEdit", @@ -1209,22 +2480,85 @@ } ], "packages": [ + { + "name": "swiftpkg_anycodable", + "identity": "anycodable", + "remote": { + "commit": "862808b2070cd908cb04f9aafe7de83d35f81b05", + "remote": "https://github.com/Flight-School/AnyCodable", + "version": "0.6.7" + } + }, + { + "name": "swiftpkg_bigdecimal", + "identity": "bigdecimal", + "remote": { + "commit": "04d17040e4615fbfda3a882b9881f6841f4bf557", + "remote": "https://github.com/Zollerboy1/BigDecimal.git", + "version": "1.0.2" + } + }, + { + "name": "swiftpkg_bigint", + "identity": "bigint", + "remote": { + "commit": "0ed110f7555c34ff468e72e1686e59721f2b0da6", + "remote": "https://github.com/attaswift/BigInt", + "version": "5.3.0" + } + }, + { + "name": "swiftpkg_core_swift", + "identity": "core-swift", + "remote": { + "commit": "20b7275f60ad80634f056905d7f18292294cd510", + "remote": "https://github.com/denis15yo/core-swift.git", + "branch": "release/1.0.0" + } + }, + { + "name": "swiftpkg_cryptoswift", + "identity": "cryptoswift", + "remote": { + "commit": "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", + "remote": "https://github.com/krzyzanowskim/CryptoSwift.git", + "version": "1.8.2" + } + }, + { + "name": "swiftpkg_curvelib.swift", + "identity": "curvelib.swift", + "remote": { + "commit": "7dad3bf1793de263f83406c08c18c9316abf082f", + "remote": "https://github.com/tkey/curvelib.swift", + "version": "0.1.2" + } + }, { "name": "swiftpkg_factory", "identity": "factory", "remote": { "commit": "587995f7d5cc667951d635fbf6b4252324ba0439", - "remote": "https://github.com/hmlongco/Factory", + "remote": "https://github.com/hmlongco/Factory.git", "version": "2.3.2" } }, + { + "name": "swiftpkg_fetch_node_details_swift", + "identity": "fetch-node-details-swift", + "remote": { + "commit": "bf2f0759da5c5c80765773b08c2756045edf608f", + "remote": "https://github.com/torusresearch/fetch-node-details-swift.git", + "version": "5.2.0" + } + }, { "name": "swiftpkg_floatingpanel", "identity": "floatingpanel", "remote": { - "commit": "8f2be39bf49b4d5e22bbf7bdde69d5b76d0ecd2a", + "commit": "22d46c526084724a718b8c39ab77f12452712cc7", "remote": "https://github.com/scenee/FloatingPanel", - "version": "2.8.2" + "version": "2.8.3" } }, { @@ -1236,6 +2570,15 @@ "branch": "master" } }, + { + "name": "swiftpkg_keychain_swift", + "identity": "keychain-swift", + "remote": { + "commit": "d108a1fa6189e661f91560548ef48651ed8d93b9", + "remote": "https://github.com/evgenyneu/keychain-swift.git", + "version": "20.0.0" + } + }, { "name": "swiftpkg_lnextensionexecutor", "identity": "lnextensionexecutor", @@ -1258,11 +2601,29 @@ "name": "swiftpkg_nicegram_assistant_ios", "identity": "nicegram-assistant-ios", "remote": { - "commit": "667bceccb7102dff96ba018b22c5fa1a6d30c9c6", + "commit": "0985fd5dfae1676121c54c31fe2817059d5bf784", "remote": "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", "branch": "develop" } }, + { + "name": "swiftpkg_nicegram_wallet_ios", + "identity": "nicegram-wallet-ios", + "remote": { + "commit": "241a210ee1b5cb9cb95d9245a4ed384973ee2ef2", + "remote": "git@bitbucket.org:mobyrix/nicegram-wallet-ios.git", + "branch": "develop" + } + }, + { + "name": "swiftpkg_qrcode", + "identity": "qrcode", + "remote": { + "commit": "263f280d2c8144adfb0b6676109846cfc8dd552b", + "remote": "https://github.com/WalletConnect/QRCode", + "version": "14.3.1" + } + }, { "name": "swiftpkg_r.swift", "identity": "r.swift", @@ -1276,9 +2637,27 @@ "name": "swiftpkg_sdwebimage", "identity": "sdwebimage", "remote": { - "commit": "f6afa0132961d593f07970d84e2d8b588c29ea04", + "commit": "b8523c1642f3c142b06dd98443ea7c48343a4dfd", "remote": "https://github.com/SDWebImage/SDWebImage.git", - "version": "5.19.1" + "version": "5.19.3" + } + }, + { + "name": "swiftpkg_session_manager_swift", + "identity": "session-manager-swift", + "remote": { + "commit": "c89d9205a1ce38cd6c6374b906a9039d9cc03f05", + "remote": "https://github.com/Web3Auth/session-manager-swift.git", + "version": "3.1.1" + } + }, + { + "name": "swiftpkg_single_factor_auth_swift", + "identity": "single-factor-auth-swift", + "remote": { + "commit": "8baa2b8cf55b0a38cb98c412bea1c6597adb78ba", + "remote": "https://github.com/Web3Auth/single-factor-auth-swift.git", + "version": "4.0.0" } }, { @@ -1290,6 +2669,15 @@ "version": "5.7.1" } }, + { + "name": "swiftpkg_starscream", + "identity": "starscream", + "remote": { + "commit": "c6bfd1af48efcc9a9ad203665db12375ba6b145a", + "remote": "https://github.com/daltoniam/Starscream.git", + "version": "4.0.8" + } + }, { "name": "swiftpkg_subscriptionanalytics_ios", "identity": "subscriptionanalytics-ios", @@ -1303,9 +2691,72 @@ "name": "swiftpkg_swift_argument_parser", "identity": "swift-argument-parser", "remote": { - "commit": "46989693916f56d1186bd59ac15124caef896560", + "commit": "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", "remote": "https://github.com/apple/swift-argument-parser", - "version": "1.3.1" + "version": "1.4.0" + } + }, + { + "name": "swiftpkg_swift_collections", + "identity": "swift-collections", + "remote": { + "commit": "ee97538f5b81ae89698fd95938896dec5217b148", + "remote": "https://github.com/apple/swift-collections", + "version": "1.1.1" + } + }, + { + "name": "swiftpkg_swift_http_types", + "identity": "swift-http-types", + "remote": { + "commit": "1ddbea1ee34354a6a2532c60f98501c35ae8edfa", + "remote": "https://github.com/apple/swift-http-types", + "version": "1.2.0" + } + }, + { + "name": "swiftpkg_swift_numerics", + "identity": "swift-numerics", + "remote": { + "commit": "0a5bc04095a675662cf24757cc0640aa2204253b", + "remote": "https://github.com/apple/swift-numerics.git", + "version": "1.0.2" + } + }, + { + "name": "swiftpkg_swift_openapi_runtime", + "identity": "swift-openapi-runtime", + "remote": { + "commit": "a51b3bd6f2151e9a6f792ca6937a7242c4758768", + "remote": "https://github.com/apple/swift-openapi-runtime", + "version": "0.3.6" + } + }, + { + "name": "swiftpkg_swift_openapi_urlsession", + "identity": "swift-openapi-urlsession", + "remote": { + "commit": "9229842c63e9fc3bbd32c661d8274b4d9d8715f1", + "remote": "https://github.com/apple/swift-openapi-urlsession.git", + "version": "0.3.1" + } + }, + { + "name": "swiftpkg_swift_qrcode_generator", + "identity": "swift-qrcode-generator", + "remote": { + "commit": "5ca09b6a2ad190f94aa3d6ddef45b187f8c0343b", + "remote": "https://github.com/dagronf/swift-qrcode-generator", + "version": "1.0.3" + } + }, + { + "name": "swiftpkg_swiftimagereadwrite", + "identity": "swiftimagereadwrite", + "remote": { + "commit": "5596407d1cf61b953b8e658fa8636a471df3c509", + "remote": "https://github.com/dagronf/SwiftImageReadWrite", + "version": "1.1.6" } }, { @@ -1317,6 +2768,69 @@ "version": "0.16.4" } }, + { + "name": "swiftpkg_tkey_ios", + "identity": "tkey-ios", + "remote": { + "commit": "c107450f0675351a9a1eaaefe60bcfa285ff1f9e", + "remote": "https://github.com/tkey/tkey-ios.git", + "version": "0.2.1" + } + }, + { + "name": "swiftpkg_ton_api_swift", + "identity": "ton-api-swift", + "remote": { + "commit": "1988939fe0ce6db6bc587cfe7c9d15dc3bca1d69", + "remote": "https://github.com/tonkeeper/ton-api-swift", + "version": "0.1.5" + } + }, + { + "name": "swiftpkg_ton_swift", + "identity": "ton-swift", + "remote": { + "commit": "e4c3def222afc125f7ee83c1569004e31f0cd05c", + "remote": "https://github.com/denis15yo/ton-swift.git", + "branch": "main" + } + }, + { + "name": "swiftpkg_torus_utils_swift", + "identity": "torus-utils-swift", + "remote": { + "commit": "4c17ef5166c162455d0a37115c033eeff8cb282d", + "remote": "https://github.com/torusresearch/torus-utils-swift.git", + "version": "8.0.1" + } + }, + { + "name": "swiftpkg_tweetnacl_swiftwrap", + "identity": "tweetnacl-swiftwrap", + "remote": { + "commit": "f8fd111642bf2336b11ef9ea828510693106e954", + "remote": "https://github.com/bitmark-inc/tweetnacl-swiftwrap", + "version": "1.1.0" + } + }, + { + "name": "swiftpkg_wallet_core", + "identity": "wallet-core", + "remote": { + "commit": "94116a24445c2052edbc7203baf68296c68ce8f4", + "remote": "https://github.com/trustwallet/wallet-core.git", + "version": "4.0.46" + } + }, + { + "name": "swiftpkg_walletconnectswiftv2", + "identity": "walletconnectswiftv2", + "remote": { + "commit": "1eacd732e321c9511859d7e73303d61d82af4d46", + "remote": "https://github.com/denis15yo/WalletConnectSwiftV2.git", + "branch": "develop" + } + }, { "name": "swiftpkg_xcodeedit", "identity": "xcodeedit", diff --git a/versions.json b/versions.json index 136c34488f8..674962c5c36 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "1.6.2", + "app": "1.6.4", "xcode": "15.2", "bazel": "7.1.1", "macos": "13.0"